import { useCallback, useEffect, useMemo, useState } from "react";
import {
  Session,
  aggregate,
  createRow,
  deleteRow,
  exportRows,
  importRows,
  listRows,
  updateRow,
} from "./api";
import { BarChart, KpiCard, LineChart, PieChart, StatusStrip } from "./Charts";
import { BlockMachineTag, PageMachineTag } from "./PageMachineTag";
import { ScreenshotFaithfulDashboard } from "./ScreenshotFaithfulDashboard";

type Blueprint = any;
type PageDef = {
  id: string;
  title: string;
  route: string;
  type: string;
  entity: string;
  layout?: {
    columns?: string[];
    filters?: string[];
    actions?: string[];
    action_labels?: Record<string, string>;
    form_fields?: string[];
    widgets?: WidgetDef[];
    preset?: string;
    filter_style?: string;
  };
};

/** LLM 常把 nav_items / shell_links 写成 {label,route}；React 不能直接渲染对象，会白屏。 */
function asUiLabel(v: unknown): string {
  if (typeof v === "string") return v.trim();
  if (v && typeof v === "object") {
    const o = v as Record<string, unknown>;
    for (const k of ["label", "title", "name", "text"]) {
      if (typeof o[k] === "string" && String(o[k]).trim()) return String(o[k]).trim();
    }
  }
  return "";
}

function asUiRoute(v: unknown): string {
  if (!v || typeof v !== "object") return "";
  const o = v as Record<string, unknown>;
  for (const k of ["route", "path", "href", "url"]) {
    if (typeof o[k] === "string" && String(o[k]).trim()) return String(o[k]).trim();
  }
  return "";
}

function normalizeUiStrings(raw: unknown, fallback: string[] = []): string[] {
  const arr = Array.isArray(raw) ? raw : fallback;
  return arr.map(asUiLabel).filter(Boolean);
}

type WidgetDef = {
  type: string;
  title?: string;
  metric?: string;
  metrics?: string[];
  group_by?: string;
  x_field?: string;
  entity?: string;
  columns?: string[];
  label_field?: string;
  value_field?: string;
  warn_field?: string;
  filter_field?: string;
  filter_op?: string;
  filter_value?: number | string;
  y_unit?: string;
};

function pageDisplayTitle(page: PageDef, bp: Blueprint): string {
  // 尊重蓝图标题；不再把「库存」强行改成「商品」
  return page.title || page.id;
}

function resourceForEntity(bp: Blueprint, entity: string): string {
  const r = (bp?.apis?.resources || []).find((x: any) => x.entity === entity) || bp?.apis?.resources?.[0];
  return String(r?.path || `/${entity}`).replace(/^\//, "");
}

type FieldMeta = {
  name: string;
  label: string;
  type: string;
  enum_values?: string[];
  widget?: string;
};

function fieldsOf(bp: Blueprint, entity: string): FieldMeta[] {
  const ent = (bp?.entities || []).find((e: any) => e.name === entity) || bp?.entities?.[0];
  return (ent?.fields || []).map((f: any) => ({
    name: f.name,
    label: f.label || f.name,
    type: f.type || "string",
    enum_values: f.enum_values || f.enumValues,
    widget: f.ui?.widget,
  }));
}

const SYSTEM_FORM_FIELDS = new Set([
  "id", "tenant_id", "org_unit_id", "created_by", "created_at", "updated_at",
]);

const CATEGORY_FIELD_HINT = /(status|type|category|state|分类|类型|状态)$/i;

function isCategoryField(f: FieldMeta): boolean {
  if (f.type === "enum" || f.widget === "select") return true;
  if (f.enum_values && f.enum_values.length > 0) return true;
  return CATEGORY_FIELD_HINT.test(f.name) || CATEGORY_FIELD_HINT.test(f.label);
}

function isDateField(f: FieldMeta): boolean {
  if (f.type === "datetime" || isDateTimeField(f)) return false;
  if (f.type === "date" || f.widget === "datepicker") return true;
  return /日期/.test(f.label) || /(^|_)date$/i.test(f.name);
}

function isDateTimeField(f: FieldMeta): boolean {
  return f.type === "datetime" || f.widget === "datetime" || /(_at$|时间|datetime)/i.test(f.name);
}

function toDateInputValue(raw: string, mode: "date" | "datetime"): string {
  if (!raw) return "";
  const d = new Date(raw);
  if (Number.isNaN(d.getTime())) {
    // already yyyy-mm-dd or yyyy-mm-ddThh:mm
    if (mode === "date") return raw.slice(0, 10);
    return raw.length >= 16 ? raw.slice(0, 16) : raw;
  }
  const pad = (n: number) => String(n).padStart(2, "0");
  const y = d.getFullYear();
  const m = pad(d.getMonth() + 1);
  const day = pad(d.getDate());
  if (mode === "date") return `${y}-${m}-${day}`;
  return `${y}-${m}-${day}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

function fromDateInputValue(raw: string, mode: "date" | "datetime"): string {
  if (!raw) return "";
  if (mode === "date") return raw;
  // datetime-local → ISO-ish for backend
  const d = new Date(raw);
  return Number.isNaN(d.getTime()) ? raw : d.toISOString();
}

function humanizeDbError(msg: string): string {
  if (/status_check|_status_check/i.test(msg)) {
    return "状态值不合法：请从下拉框选择蓝图允许的枚举值。";
  }
  if (/violates check constraint/i.test(msg) || /检查约束/.test(msg)) {
    return `字段值不符合约束：${msg}`;
  }
  if (/null value|非空|not-null/i.test(msg)) {
    return `必填字段为空：${msg}`;
  }
  return msg;
}

function FieldControl({
  meta,
  value,
  options,
  onChange,
}: {
  meta: FieldMeta;
  value: string;
  options: string[];
  onChange: (v: string) => void;
}) {
  if (isCategoryField(meta) || options.length > 0) {
    const opts = options.length ? options : meta.enum_values || [];
    return (
      <select value={value} onChange={(e) => onChange(e.target.value)}>
        <option value="">请选择</option>
        {opts.map((ev) => (
          <option key={ev} value={ev}>{ev}</option>
        ))}
      </select>
    );
  }
  if (meta.type === "boolean" || meta.widget === "checkbox") {
    return (
      <select value={value} onChange={(e) => onChange(e.target.value)}>
        <option value="">请选择</option>
        <option value="true">是</option>
        <option value="false">否</option>
      </select>
    );
  }
  if (isDateTimeField(meta)) {
    return (
      <input
        type="datetime-local"
        value={toDateInputValue(value, "datetime")}
        onChange={(e) => onChange(fromDateInputValue(e.target.value, "datetime"))}
      />
    );
  }
  if (isDateField(meta)) {
    return (
      <input
        type="date"
        value={toDateInputValue(value, "date")}
        onChange={(e) => onChange(e.target.value)}
      />
    );
  }
  if (meta.type === "int" || meta.type === "bigint" || meta.type === "decimal" || meta.widget === "number") {
    return (
      <input
        type="number"
        step={meta.type === "decimal" ? "0.01" : "1"}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    );
  }
  if (meta.type === "text" || meta.widget === "textarea") {
    return (
      <textarea rows={3} value={value} onChange={(e) => onChange(e.target.value)} />
    );
  }
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

function formatValue(key: string, value: unknown): string {
  if (value == null || value === "") return "—";
  if (String(key).endsWith("_at")) {
    const d = new Date(String(value));
    if (!Number.isNaN(d.getTime())) {
      return d.toLocaleString("zh-CN", {
        month: "2-digit",
        day: "2-digit",
        hour: "2-digit",
        minute: "2-digit",
      });
    }
  }
  return String(value);
}

function statusClass(v: string): string {
  if (/在售|正常|启用|合格/.test(v)) return "ok";
  if (/停售|禁用|缺货|超限|异常|逾期/.test(v)) return "bad";
  return "warn";
}

export function GeneratedApp({
  session,
  blueprint,
  onBack,
}: {
  session: Session | null;
  blueprint: Blueprint;
  onBack: () => void;
}) {
  const slug = blueprint?.meta?.slug as string;
  const pages: PageDef[] = blueprint?.pages || [];
  const defaultPage = pages.find((p) => p.type === "list") || pages[0];
  const [pageId, setPageId] = useState(defaultPage?.id || "");
  const [editId, setEditId] = useState<string | null>(null);
  const [error, setError] = useState("");
  const [info, setInfo] = useState("");
  const [busy, setBusy] = useState(false);

  const page = useMemo(() => {
    if (pageId.startsWith("__edit__")) {
      const entity = pageId.replace("__edit__", "");
      const list = pages.find((p) => p.type === "list" && p.entity === entity);
      const fields = fieldsOf(blueprint, entity);
      return {
        id: pageId,
        title: "编辑",
        route: `/${entity}/edit`,
        type: "form_edit",
        entity,
        layout: {
          form_fields:
            list?.layout?.columns?.filter((c) => c !== "id") ||
            fields.map((f) => f.name).filter((n) => n !== "id"),
          actions: ["edit"],
        },
      } as PageDef;
    }
    return pages.find((p) => p.id === pageId) || defaultPage;
  }, [pageId, pages, defaultPage, blueprint]);
  const resource = page ? resourceForEntity(blueprint, page.entity) : "";
  const fields = useMemo(
    () => (page ? fieldsOf(blueprint, page.entity) : []),
    [page, blueprint]
  );
  const fieldMap = useMemo(
    () => Object.fromEntries(fields.map((f) => [f.name, f])),
    [fields]
  );

  const navPages = useMemo(() => {
    const noise = /^(欢迎您|退出|帮助|登录|注册|返回|系统首页|修改密码)$/;
    const list = pages.filter(
      (p) =>
        p.type !== "form_edit" &&
        p.type !== "detail" &&
        !noise.test(String(p.title || ""))
    );
    const rank = (p: PageDef) => {
      if (p.type === "list") return 0;
      if (p.type === "form_create") return 1;
      if (p.type === "dashboard") return 2;
      return 3;
    };
    return [...list].sort((a, b) => rank(a) - rank(b));
  }, [pages]);

  const chromeNav = useMemo(() => {
    const rawItems = Array.isArray(blueprint?.meta?.ui?.nav_items)
      ? (blueprint.meta.ui.nav_items as unknown[])
      : [];
    if (!rawItems.length) return [] as { label: string; pageId?: string }[];
    const dash = navPages.find((p) => p.type === "dashboard");
    const normRoute = (r: string) => r.replace(/\/+$/, "").toLowerCase();
    return rawItems
      .map((raw) => {
        const label = asUiLabel(raw);
        const route = asUiRoute(raw);
        if (!label && !route) return null;
        let hit =
          (route
            ? navPages.find((p) => normRoute(String(p.route || "")) === normRoute(route))
            : undefined) ||
          navPages.find(
            (p) =>
              p.title === label ||
              String(p.title || "").includes(label) ||
              (label && label.includes(String(p.title || "")))
          );
        // 无命中时：若该文案与看板页标题一致，才绑到 dashboard（不写死业务关键词）
        if (!hit && dash && label && (dash.title === label || String(dash.title || "").includes(label))) {
          hit = dash;
        }
        return { label: label || hit?.title || route, pageId: hit?.id };
      })
      .filter(Boolean) as { label: string; pageId?: string }[];
  }, [blueprint?.meta?.ui?.nav_items, navPages]);

  const shellLinks = useMemo(() => {
    return normalizeUiStrings(blueprint?.meta?.ui?.shell_links, ["欢迎您", "退出", "帮助"]);
  }, [blueprint?.meta?.ui?.shell_links]);

  const platformEn =
    blueprint?.meta?.ui?.platform_subtitle ||
    blueprint?.meta?.platform_subtitle ||
    "";
  const floatActions = useMemo(() => {
    return normalizeUiStrings(blueprint?.meta?.ui?.float_actions, ["客服", "电话", "点击隐藏"]);
  }, [blueprint?.meta?.ui?.float_actions]);

  const uiPreset =
    blueprint?.meta?.ui_preset ||
    pages.find((p) => p.layout?.preset)?.layout?.preset ||
    "default";
  const projectCtx = blueprint?.meta?.project_context || "";
  const looksFaithfulDash = (p?: PageDef | null) => {
    if (!p || p.type !== "dashboard") return false;
    if (p.layout?.preset === "screenshot_faithful" || p.layout?.preset === "ops_monitor") return true;
    const ws = p.layout?.widgets || [];
    const types = new Set(ws.map((w) => w.type));
    return (
      (types.has("line_chart") || types.has("bar_chart")) &&
      (types.has("status_strip") || types.has("table"))
    );
  };
  const isFaithful =
    uiPreset === "screenshot_faithful" ||
    uiPreset === "ops_monitor" ||
    Boolean(blueprint?.meta?.source?.screenshot_faithful) ||
    Boolean(blueprint?.meta?.source?.image_refs?.length) ||
    Boolean(blueprint?.meta?.source?.layout_refs?.length) ||
    looksFaithfulDash(pages.find((p) => p.type === "dashboard"));
  const platformTitle =
    blueprint?.meta?.ui?.platform_title ||
    blueprint?.meta?.platform_title ||
    "";

  // 截图忠实应用：默认打开看板
  useEffect(() => {
    if (!isFaithful) return;
    const dash = pages.find((p) => p.type === "dashboard");
    if (dash && (!pageId || pageId === defaultPage?.id) && defaultPage?.type !== "dashboard") {
      setPageId(dash.id);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isFaithful, blueprint?.meta?.slug]);

  return (
    <div className={`gen-app ${isFaithful ? "gen-app-ops" : ""}`}>
      {isFaithful ? (
        <header className="sf-shell-bar">
          <div className="sf-shell-left">
            {platformTitle || "业务管理平台"}
            {platformEn ? <span className="sf-shell-en">{platformEn}</span> : null}
          </div>
          <div className="sf-shell-center">{blueprint?.meta?.name || slug}</div>
          <div className="sf-shell-right">
            {shellLinks.map((t) => (
              <span key={t} className="sf-shell-link">
                {t}
              </span>
            ))}
            <button type="button" className="sf-shell-back" onClick={onBack}>
              返回控制台
            </button>
          </div>
        </header>
      ) : (
        <header className="gen-header">
          <div>
            <p className="gen-eyebrow">业务模块</p>
            <h1 className="gen-title">{blueprint?.meta?.name || slug}</h1>
            <p className="gen-desc">{blueprint?.meta?.description || "由蓝图自动生成的业务页面"}</p>
          </div>
          <button type="button" className="btn secondary" onClick={onBack}>返回控制台</button>
        </header>
      )}

      <nav className={`gen-nav ${isFaithful ? "gen-nav-ops" : ""}`}>
        {(isFaithful && chromeNav.length > 0 ? chromeNav : navPages.map((p) => ({ label: pageDisplayTitle(p, blueprint), pageId: p.id }))).map(
          (item, idx) => {
            const active = item.pageId ? page?.id === item.pageId && !editId : false;
            return (
              <button
                key={`${item.label}-${idx}`}
                type="button"
                className={`gen-nav-item ${active ? "active" : ""} ${item.pageId ? "" : "is-chrome"}`}
                onClick={() => {
                  if (!item.pageId) return;
                  setEditId(null);
                  setPageId(item.pageId);
                  setError("");
                  setInfo("");
                }}
              >
                {item.label}
              </button>
            );
          }
        )}
        {isFaithful && chromeNav.length > 0
          ? navPages
              .filter((p) => p.type === "list" || p.type === "dashboard")
              .filter((p) => !chromeNav.some((c) => c.pageId === p.id || c.label === p.title))
              .map((p) => (
                <button
                  key={p.id}
                  type="button"
                  className={`gen-nav-item ${page?.id === p.id && !editId ? "active" : ""}`}
                  onClick={() => {
                    setEditId(null);
                    setPageId(p.id);
                    setError("");
                    setInfo("");
                  }}
                >
                  {pageDisplayTitle(p, blueprint)}
                </button>
              ))
          : null}
      </nav>

      {isFaithful && projectCtx && page?.type !== "dashboard" && (
        <div className="sf-mini-context">{projectCtx}</div>
      )}

      {error && <p className="flash err">{error}</p>}
      {info && <p className="flash ok">{info}</p>}

      {page && resource && (
        <PageMachineTag
          slug={slug}
          resource={resource}
          page={page}
          blueprint={blueprint}
        />
      )}

      {page?.type === "list" && !editId && (
        <ListPage
          session={session}
          slug={slug}
          resource={resource}
          page={page}
          fieldMap={fieldMap}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
          onCreate={() => {
            const create = pages.find((p) => p.type === "form_create" && p.entity === page.entity);
            if (create) setPageId(create.id);
          }}
          onEdit={(id) => {
            setEditId(id);
            const edit = pages.find((p) => p.type === "form_edit" && p.entity === page.entity);
            if (edit) {
              setPageId(edit.id);
            } else {
              // 蓝图无 form_edit 时，临时走创建页结构编辑
              setPageId(`__edit__${page.entity}`);
            }
          }}
        />
      )}

      {(page?.type === "form_create" || page?.type === "form_edit") && (
        <FormPage
          session={session}
          slug={slug}
          resource={resource}
          page={page}
          title={pageDisplayTitle(page, blueprint)}
          fields={fields}
          editId={page.type === "form_edit" ? editId : null}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
          onDone={() => {
            setEditId(null);
            const list = pages.find((p) => p.type === "list" && p.entity === page.entity);
            if (list) setPageId(list.id);
          }}
        />
      )}

      {page?.type === "dashboard" && !editId && (
        (isFaithful || looksFaithfulDash(page)) ? (
          <ScreenshotFaithfulDashboard
            session={session}
            slug={slug}
            resource={resource}
            title={pageDisplayTitle(page, blueprint)}
            widgets={(page.layout?.widgets || []).filter((w) => w.type !== "kpi")}
            fieldMap={fieldMap}
            meta={blueprint?.meta || {}}
            setError={setError}
          />
        ) : (
        <DashboardPage
          session={session}
          slug={slug}
          resource={resource}
          page={page}
          fieldMap={fieldMap}
          setError={setError}
        />
        )
      )}

      {isFaithful && floatActions.length > 0 ? (
        <aside className="sf-float-dock" aria-label="快捷操作">
          {floatActions.map((a) => (
            <button key={a} type="button" tabIndex={-1}>
              {a}
            </button>
          ))}
        </aside>
      ) : null}
    </div>
  );
}

function ListPage(props: {
  session: Session | null;
  slug: string;
  resource: string;
  page: PageDef;
  fieldMap: Record<string, FieldMeta>;
  busy: boolean;
  setBusy: (v: boolean) => void;
  setError: (v: string) => void;
  setInfo: (v: string) => void;
  onCreate: () => void;
  onEdit: (id: string) => void;
}) {
  const { session, slug, resource, page, fieldMap, busy, setBusy, setError, setInfo, onCreate, onEdit } = props;
  const columns = page.layout?.columns?.length
    ? page.layout.columns
    : Object.keys(fieldMap).filter((k) => !["id", "tenant_id"].includes(k)).slice(0, 8);
  const filterKeys = useMemo(
    () => page.layout?.filters || [],
    [page.layout?.filters]
  );
  const actions = page.layout?.actions || ["refresh"];
  const actionLabels = page.layout?.action_labels || {};
  const labelOf = (key: string, fallback: string) => actionLabels[key] || fallback;
  const toolbarKeys = actions.filter((a) => !["edit", "delete"].includes(a));
  const [filters, setFilters] = useState<Record<string, string>>({});
  const [filterOptions, setFilterOptions] = useState<Record<string, string[]>>({});
  const [rows, setRows] = useState<Record<string, unknown>[]>([]);
  const [total, setTotal] = useState(0);

  const load = useCallback(async () => {
    setBusy(true);
    setError("");
    try {
      const res = await listRows(session, slug, resource, filters);
      setRows(res.items || []);
      setTotal(res.total || 0);
      const opts: Record<string, string[]> = {};
      for (const fk of filterKeys) {
        const meta = fieldMap[fk];
        if (meta?.enum_values?.length) {
          opts[fk] = meta.enum_values;
          continue;
        }
        const set = new Set<string>();
        for (const row of res.items || []) {
          const v = row[fk];
          if (v != null && String(v).trim() !== "") set.add(String(v));
        }
        opts[fk] = Array.from(set).sort();
      }
      setFilterOptions((prev) => {
        let changed = false;
        const next = { ...prev };
        for (const [k, v] of Object.entries(opts)) {
          const old = prev[k] || [];
          if (old.length !== v.length || old.some((x, i) => x !== v[i])) {
            next[k] = v;
            changed = true;
          }
        }
        return changed ? next : prev;
      });
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setBusy(false);
    }
  }, [session, slug, resource, filters, setBusy, setError, filterKeys, fieldMap]);

  useEffect(() => { void load(); }, [load]);

  async function onDelete(id: string) {
    if (!confirm("确认删除该行？")) return;
    setBusy(true);
    try {
      await deleteRow(session, slug, resource, id);
      setInfo("已删除");
      await load();
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setBusy(false);
    }
  }

  return (
    <section className="panel gen-panel">
      <BlockMachineTag
        slug={slug}
        resource={resource}
        blockId={`list:${page.id}`}
        endpoint={{ method: "GET", path: `/api/v1/apps/${slug}/${resource}` }}
        extra={{ page_type: "list", filters: filterKeys, columns }}
      />
      <div className="gen-toolbar">
          <h2 className="section-title" style={{ margin: 0 }}>{page.title}</h2>
        <div className="actions" style={{ margin: 0 }}>
          {toolbarKeys.map((key) => {
            if (key === "create") {
              return (
                <button key={key} type="button" className="btn" disabled={busy} onClick={onCreate}>
                  {labelOf("create", "新增")}
                </button>
              );
            }
            if (key === "refresh") {
              return (
                <button key={key} type="button" className="btn secondary" disabled={busy} onClick={() => void load()}>
                  {labelOf("refresh", "刷新")}
                </button>
              );
            }
            if (key === "import") {
              return (
                <label
                  key={key}
                  className="file-field btn secondary"
                  style={{ margin: 0, padding: "8px 12px", cursor: "pointer" }}
                >
                  {labelOf("import", "导入 Excel")}
                  <input
                    type="file"
                    accept=".xlsx,.xlsm,.csv"
                    hidden
                    onChange={async (e) => {
                      const f = e.target.files?.[0];
                      if (!f) return;
                      setBusy(true);
                      try {
                        const res = await importRows(session, slug, resource, f);
                        const inserted = res.inserted || 0;
                        const skipped = res.skipped || 0;
                        const errN = (res.errors || []).length;
                        setInfo(
                          `导入完成：成功 ${inserted} 行` +
                            (skipped ? `，跳过 ${skipped}` : "") +
                            (errN ? `，错误 ${errN} 条（仅展示部分）` : "")
                        );
                        if (errN && res.errors?.[0]) setError(String(res.errors[0]));
                        await load();
                      } catch (err: any) {
                        setError(err.message || String(err));
                      } finally {
                        setBusy(false);
                        e.target.value = "";
                      }
                    }}
                  />
                </label>
              );
            }
            if (key === "export") {
              return (
                <button
                  key={key}
                  type="button"
                  className="btn secondary"
                  disabled={busy}
                  onClick={async () => {
                    setBusy(true);
                    try {
                      await exportRows(session, slug, resource, "xlsx");
                      setInfo("已导出 Excel");
                    } catch (err: any) {
                      setError(err.message || String(err));
                    } finally {
                      setBusy(false);
                    }
                  }}
                >
                  {labelOf("export", "导出 Excel")}
                </button>
              );
            }
            if (key === "search") {
              return (
                <button key={key} type="button" className="btn secondary" disabled={busy} onClick={() => void load()}>
                  {labelOf("search", "查询")}
                </button>
              );
            }
            // 用户/截图中的其它按键：展示原文，占位行为提示
            const customLabel = labelOf(key, key.replace(/^custom_/, ""));
            return (
              <button
                key={key}
                type="button"
                className="btn secondary"
                disabled={busy}
                onClick={() => setInfo(`按键「${customLabel}」已按说明展示（业务逻辑可后续绑定）`)}
              >
                {customLabel}
              </button>
            );
          })}
        </div>
      </div>

      {filterKeys.length > 0 &&
      (page.layout?.filter_style === "section_radios" || page.layout?.filter_style === "radios") ? (
        <div className="ops-section-radios">
          {(() => {
            const radioKey =
              filterKeys.find((k) => (fieldMap[k]?.enum_values || []).length > 0) || filterKeys[0];
            const radioMeta = fieldMap[radioKey] || { name: radioKey, label: radioKey, type: "string" };
            const enums = filterOptions[radioKey] || radioMeta.enum_values || [];
            return (
              <>
                <span className="ops-radio-label">{radioMeta.label || "类型"}</span>
                {["", ...enums].map((ev) => (
                  <button
                    key={ev || "all"}
                    type="button"
                    className={`ops-radio ${(filters[radioKey] || "") === ev ? "active" : ""}`}
                    onClick={() => {
                      setFilters((prev) => ({ ...prev, [radioKey]: ev }));
                      setTimeout(() => void load(), 0);
                    }}
                  >
                    {ev || "全部"}
                  </button>
                ))}
                {filterKeys
                  .filter((k) => k !== radioKey)
                  .map((fk) => {
                    const meta = fieldMap[fk] || { name: fk, label: fk, type: "string" };
                    return (
                      <label key={fk} className="ops-extra-filter">
                        {meta.label || fk}
                        <input
                          value={filters[fk] || ""}
                          placeholder="筛选…"
                          onChange={(e) => setFilters((prev) => ({ ...prev, [fk]: e.target.value }))}
                        />
                      </label>
                    );
                  })}
                <button type="button" className="btn secondary" disabled={busy} onClick={() => void load()}>
                  应用筛选
                </button>
              </>
            );
          })()}
        </div>
      ) : filterKeys.length > 0 ? (
        <div className="row compact gen-filters">
          {filterKeys.map((fk) => {
            const meta = fieldMap[fk] || { name: fk, label: fk, type: "string" };
            const opts = filterOptions[fk] || meta.enum_values || [];
            const useSelect = isCategoryField(meta) || opts.length > 0;
            return (
              <label key={fk}>
                {meta.label || fk}
                {useSelect ? (
                  <select
                    value={filters[fk] || ""}
                    onChange={(e) => setFilters((prev) => ({ ...prev, [fk]: e.target.value }))}
                  >
                    <option value="">全部</option>
                    {opts.map((ev) => (
                      <option key={ev} value={ev}>{ev}</option>
                    ))}
                  </select>
                ) : (
                  <input
                    value={filters[fk] || ""}
                    placeholder="筛选…"
                    onChange={(e) => setFilters((prev) => ({ ...prev, [fk]: e.target.value }))}
                  />
                )}
              </label>
            );
          })}
          <div className="actions" style={{ marginTop: 0 }}>
            <button type="button" className="btn secondary" disabled={busy} onClick={() => void load()}>应用筛选</button>
          </div>
        </div>
      ) : null}
      <p className="pages-line">共 {total} 条</p>

      {rows.length === 0 ? (
        <div className="empty">暂无数据，可点「新增」或导入 Excel</div>
      ) : (
        <div className="table-wrap">
          <table>
            <thead>
              <tr>
                {columns.map((c) => <th key={c}>{fieldMap[c]?.label || c}</th>)}
                {(actions.includes("edit") || actions.includes("delete")) && <th>操作</th>}
              </tr>
            </thead>
            <tbody>
              {rows.map((r, i) => (
                <tr key={String(r.id ?? i)}>
                  {columns.map((c) => {
                    const text = formatValue(c, r[c]);
                    if (c === "status") {
                      return (
                        <td key={c} className="cell-status">
                          <span className={`badge ${statusClass(text)}`}>{text}</span>
                        </td>
                      );
                    }
                    return (
                      <td key={c} className={c.endsWith("_at") ? "cell-muted" : undefined}>{text}</td>
                    );
                  })}
                  {(actions.includes("edit") || actions.includes("delete")) && (
                    <td>
                      <div className="actions" style={{ margin: 0, gap: 6 }}>
                        {actions.includes("edit") && (
                          <button type="button" className="btn secondary" style={{ padding: "6px 10px" }} onClick={() => onEdit(String(r.id))}>
                            {labelOf("edit", "编辑")}
                          </button>
                        )}
                        {actions.includes("delete") && (
                          <button type="button" className="btn secondary" style={{ padding: "6px 10px" }} onClick={() => void onDelete(String(r.id))}>
                            {labelOf("delete", "删除")}
                          </button>
                        )}
                      </div>
                    </td>
                  )}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </section>
  );
}

function FormPage(props: {
  session: Session | null;
  slug: string;
  resource: string;
  page: PageDef;
  title: string;
  fields: FieldMeta[];
  editId: string | null;
  busy: boolean;
  setBusy: (v: boolean) => void;
  setError: (v: string) => void;
  setInfo: (v: string) => void;
  onDone: () => void;
}) {
  const { session, slug, resource, page, title, fields, editId, busy, setBusy, setError, setInfo, onDone } = props;
  const formFields = (page.layout?.form_fields || fields.map((f) => f.name))
    .filter((n) => !SYSTEM_FORM_FIELDS.has(n));
  const actionLabels = page.layout?.action_labels || {};
  const labelOf = (key: string, fallback: string) => actionLabels[key] || fallback;
  const [form, setForm] = useState<Record<string, string>>({});
  const [optionMap, setOptionMap] = useState<Record<string, string[]>>({});

  useEffect(() => {
    const init: Record<string, string> = {};
    for (const name of formFields) init[name] = "";
    setForm(init);

    void (async () => {
      const nextOpts: Record<string, string[]> = {};
      for (const name of formFields) {
        const meta = fields.find((f) => f.name === name);
        if (!meta) continue;
        if (meta.enum_values?.length) {
          nextOpts[name] = meta.enum_values;
        }
      }
      try {
        const res = await listRows(session, slug, resource);
        const items = res.items || [];
        for (const name of formFields) {
          const meta = fields.find((f) => f.name === name);
          if (!meta || !isCategoryField(meta)) continue;
          if (nextOpts[name]?.length) continue;
          const set = new Set<string>();
          for (const row of items) {
            const v = row[name];
            if (v != null && String(v).trim() !== "") set.add(String(v));
          }
          if (set.size > 0 && set.size <= 50) nextOpts[name] = Array.from(set).sort();
        }
        if (editId) {
          const row = items.find((x) => String(x.id) === editId);
          if (row) {
            const next: Record<string, string> = {};
            for (const name of formFields) {
              const meta = fields.find((f) => f.name === name);
              const raw = row[name] == null ? "" : String(row[name]);
              if (meta && isDateTimeField(meta)) next[name] = raw;
              else if (meta && isDateField(meta)) next[name] = toDateInputValue(raw, "date");
              else next[name] = raw;
            }
            setForm(next);
          }
        }
      } catch (e: any) {
        if (editId) setError(e.message || String(e));
      }
      setOptionMap(nextOpts);
    })();
  }, [editId, slug, resource]); // eslint-disable-line react-hooks/exhaustive-deps

  async function submit() {
    setBusy(true);
    setError("");
    try {
      const body: Record<string, unknown> = {};
      for (const name of formFields) {
        const meta = fields.find((f) => f.name === name);
        const raw = form[name];
        if (meta?.type === "int" || meta?.type === "bigint") body[name] = raw === "" ? null : Number(raw);
        else if (meta?.type === "decimal") body[name] = raw === "" ? null : Number(raw);
        else if (meta?.type === "boolean") body[name] = raw === "true";
        else if (meta && isDateTimeField(meta)) body[name] = raw === "" ? null : raw;
        else if (meta && isDateField(meta)) body[name] = raw === "" ? null : raw;
        else body[name] = raw;
        if (isCategoryField(meta || { name, label: name, type: "string" }) && !raw) {
          throw new Error(`请选择「${meta?.label || name}」`);
        }
      }
      if (editId) {
        await updateRow(session, slug, resource, editId, body);
        setInfo("已更新");
      } else {
        await createRow(session, slug, resource, body);
        setInfo("已创建");
      }
      onDone();
    } catch (e: any) {
      setError(humanizeDbError(e.message || String(e)));
    } finally {
      setBusy(false);
    }
  }

  return (
    <section className="panel gen-panel" style={{ maxWidth: 560 }}>
      <h2 className="section-title">{title}</h2>
      <div className="row">
        {formFields.map((name) => {
          const meta = fields.find((f) => f.name === name) || { name, label: name, type: "string" };
          const options = optionMap[name] || meta.enum_values || [];
          return (
            <label key={name}>
              {meta.label || name}
              <FieldControl
                meta={meta}
                value={form[name] || ""}
                options={options}
                onChange={(v) => setForm((p) => ({ ...p, [name]: v }))}
              />
            </label>
          );
        })}
      </div>
      <div className="actions">
        <button type="button" className="btn" disabled={busy} onClick={() => void submit()}>
          {editId ? labelOf("save", "保存") : labelOf("create", "创建")}
        </button>
        <button type="button" className="btn secondary" disabled={busy} onClick={onDone}>
          {labelOf("cancel", "取消")}
        </button>
      </div>
    </section>
  );
}

function DashboardPage(props: {
  session: Session | null;
  slug: string;
  resource: string;
  page: PageDef;
  fieldMap?: Record<string, FieldMeta>;
  setError: (v: string) => void;
}) {
  const { session, slug, resource, page, fieldMap = {}, setError } = props;
  const widgets = page.layout?.widgets || [];
  const [stats, setStats] = useState<{ total: number; sum?: number; buckets?: { key: string; count: number; sum?: number }[] } | null>(null);
  const [rows, setRows] = useState<Record<string, unknown>[]>([]);

  const groupBy =
    widgets.find((w) => w.type === "bar_chart" || w.type === "pie_chart")?.group_by ||
    widgets.find((w) => w.group_by)?.group_by ||
    "";
  const sumField =
    widgets.find((w) => w.metric && w.metric !== "count")?.metric ||
    widgets.find((w) => w.metrics?.length)?.metrics?.[0] ||
    "";

  const widgetKey = useMemo(() => JSON.stringify(widgets), [page.layout?.widgets]);

  useEffect(() => {
    void (async () => {
      try {
        if (groupBy) {
          const res = await aggregate(session, slug, resource, groupBy, sumField || undefined);
          setStats(res);
        }
        const needRows = widgets.some((w) =>
          ["line_chart", "table", "status_strip"].includes(w.type)
        );
        if (needRows || !groupBy) {
          const list = await listRows(session, slug, resource);
          setRows(list.items || []);
          if (!groupBy) {
            setStats({ total: list.total || (list.items || []).length });
          }
        }
      } catch (e: any) {
        setError(e.message || String(e));
      }
    })();
  }, [session, slug, resource, groupBy, sumField, setError, widgetKey]); // eslint-disable-line react-hooks/exhaustive-deps

  if (!stats) return <div className="panel gen-panel"><div className="empty">加载看板…</div></div>;

  function labelOf(name: string) {
    return fieldMap[name]?.label || name;
  }

  function lineFromWidget(w: WidgetDef) {
    const xf = w.x_field || w.group_by || "";
    const seriesKeys =
      (w.metrics && w.metrics.length ? w.metrics : w.metric && w.metric !== "count" ? [w.metric] : [])
        .filter(Boolean);
    if (!xf || !seriesKeys.length || !rows.length) return null;
    const sorted = [...rows].sort((a, b) => String(a[xf] ?? "").localeCompare(String(b[xf] ?? ""), "zh"));
    const points = sorted.slice(0, 100).map((row) => {
      const values: Record<string, number> = {};
      for (const k of seriesKeys) {
        const n = Number(row[k]);
        if (Number.isFinite(n)) values[k] = n;
      }
      return { x: String(row[xf] ?? ""), values };
    });
    const series = seriesKeys.map((key) => ({ key, label: labelOf(key) }));
    return (
      <LineChart
        key={`line-${w.title}-${xf}`}
        title={w.title || "趋势图"}
        points={points}
        series={series}
        yUnit={w.y_unit || ""}
      />
    );
  }

  function matchFilter(row: Record<string, unknown>, w: WidgetDef) {
    if (!w.filter_field) return true;
    const raw = row[w.filter_field];
    const n = Number(raw);
    const op = w.filter_op || "gt";
    const v = Number(w.filter_value ?? 0);
    if (op === "gt") return Number.isFinite(n) && n > v;
    if (op === "gte") return Number.isFinite(n) && n >= v;
    if (op === "lt") return Number.isFinite(n) && n < v;
    if (op === "eq") return String(raw) === String(w.filter_value ?? "");
    return true;
  }

  return (
    <section className={`panel gen-panel ${page.layout?.preset === "ops_monitor" ? "ops-dashboard" : ""}`}>
      <h2 className="section-title">{page.title}</h2>
      {page.layout?.preset === "ops_monitor" && (
        <p className="ops-dash-hint">看板由蓝图 widgets 驱动，结构跟随需求与截图</p>
      )}
      <div className="kpi-grid">
        {(widgets.filter((w) => w.type === "kpi").length
          ? widgets.filter((w) => w.type === "kpi")
          : [{ type: "kpi", title: "记录数", metric: "count" } as WidgetDef]
        ).map((w, i) => (
          <KpiCard
            key={`${w.title}-${i}`}
            title={w.title || "KPI"}
            value={w.metric === "count" || !w.metric ? stats.total : Number(stats.sum || 0).toFixed(2)}
          />
        ))}
      </div>

      {widgets.filter((w) => w.type === "line_chart").map((w) => lineFromWidget(w))}

      {widgets.filter((w) => w.type === "status_strip").map((w, i) => {
        const labelF = w.label_field || "";
        const valueF = w.value_field || "";
        const secondaryF = (w as any).secondary_field || "";
        const warnF = w.warn_field || "";
        const cycle = Number((w as any).cycle_days) || 30;
        const stacked = (w as any).variant === "stacked" || (w as any).variant === "stacked_days" || Boolean(secondaryF);
        if (!labelF || !valueF) return null;
        const items = rows.slice(0, 48).map((r) => {
          let top = Number(r[valueF] ?? 0);
          let bottom = secondaryF ? Number(r[secondaryF] ?? 0) : 0;
          if (stacked && top + bottom > 50 && top + bottom < 120) {
            const t = Math.round((top / 100) * cycle);
            bottom = Math.max(0, cycle - t);
            top = t;
          }
          return {
            label: String(r[labelF] ?? ""),
            value: top,
            secondary: stacked ? bottom : undefined,
            tone: (warnF && Number(r[warnF] ?? 0) > 0 ? "warn" : "ok") as "ok" | "warn",
            stop: String(r.status || "").includes("停") || (stacked && top === 0 && bottom === 0),
          };
        });
        return (
          <StatusStrip
            key={`strip-${i}`}
            title={w.title || "状态条"}
            variant={stacked ? "stacked" : "cells"}
            items={items}
          />
        );
      })}

      {widgets.filter((w) => w.type === "table").map((w, i) => {
        const cols = w.columns?.length ? w.columns : Object.keys(rows[0] || {}).slice(0, 6);
        const data = rows.filter((r) => matchFilter(r, w)).slice(0, 50);
        return (
          <div className="chart-card" key={`tbl-${i}`}>
            <h4>{w.title || "明细表"}</h4>
            <div className="table-wrap">
              <table>
                <thead>
                  <tr>{cols.map((c) => <th key={c}>{labelOf(c)}</th>)}</tr>
                </thead>
                <tbody>
                  {data.map((r, ri) => (
                    <tr key={ri}>
                      {cols.map((c) => <td key={c}>{formatValue(c, r[c])}</td>)}
                    </tr>
                  ))}
                  {!data.length && (
                    <tr><td colSpan={cols.length} className="empty">暂无数据</td></tr>
                  )}
                </tbody>
              </table>
            </div>
          </div>
        );
      })}

      <div className="chart-grid">
        {widgets.filter((w) => w.type === "bar_chart").map((w, i) => (
          <BarChart key={`bar-${i}`} title={w.title || "分布"} buckets={stats.buckets || []} />
        ))}
        {widgets.filter((w) => w.type === "pie_chart").map((w, i) => (
          <PieChart key={`pie-${i}`} title={w.title || "占比"} buckets={stats.buckets || []} />
        ))}
        {!widgets.some((w) => w.type === "bar_chart" || w.type === "pie_chart") && (stats.buckets?.length || 0) > 0 && (
          <BarChart title="分布" buckets={stats.buckets || []} />
        )}
      </div>
    </section>
  );
}
