import { useEffect, useRef, useState } from "react";
import {
  Session,
  acceptInvite,
  clearSession,
  createTenant,
  generateDraft,
  getBlueprint,
  getCapsule,
  getCompanyEntitlements,
  isPendingMembership,
  listApps,
  listAudit,
  listLlmProviders,
  loadSession,
  loginWith,
  publish,
  register,
  saveDraft,
  saveSession,
  uploadFile,
} from "./api";
import { AgentUsersPage } from "./AgentUsersPage";
import { InvitesPage } from "./InvitesPage";
import { ModulesPage } from "./ModulesPage";
import { OrgUnitsPage } from "./OrgUnitsPage";
import { RolesPage } from "./RolesPage";
import { SyncPage } from "./SyncPage";
import { PlatformTenantsPage } from "./PlatformTenantsPage";
import { MembersPage } from "./MembersPage";
import { isCompanyAdmin, isPlatformAdmin } from "./agentPerms";
import { GeneratedApp } from "./GeneratedApp";
import { loadDemoFixtures } from "./demoFixtures";
import { clearActivePreview, fetchPreview, previewRouteId } from "./preview";
import { ConsoleLayout, type ConsoleTab } from "./ConsoleLayout";
import { LoadingShell, LoginShell, PendingShell } from "./AuthShell";
import { Alert, Button, Card, Col, Form, Input, Row, Select, Space, Table, Typography } from "antd";

type Tab = ConsoleTab;
type View = "console" | "app";

function appRouteSlug(): string | null {
  const h = (window.location.hash || "").replace(/^#/, "");
  const m = h.match(/^\/?app\/([a-zA-Z][a-zA-Z0-9_]{0,63})\/?$/);
  return m ? m[1] : null;
}

function navigateToApp(appSlug: string) {
  const next = `#/app/${encodeURIComponent(appSlug)}`;
  if (window.location.hash === next) {
    window.dispatchEvent(new Event("hashchange"));
  } else {
    window.location.hash = next;
  }
}

function navigateToConsole() {
  if (!window.location.hash || window.location.hash === "#/" || window.location.hash === "#") {
    return;
  }
  window.location.hash = "#/";
}

/** 把 File 写回原生 file input，使「选择文件」旁显示文件名（非仅下方提示）。 */
function assignInputFiles(input: HTMLInputElement | null, files: File[]) {
  if (!input) return;
  try {
    const dt = new DataTransfer();
    for (const f of files) dt.items.add(f);
    input.files = dt.files;
  } catch {
    /* 个别环境不支持赋值，仍靠 React state 提交 */
  }
}

export default function App() {
  const [tab, setTab] = useState<Tab>("generate");
  const [view, setView] = useState<View>("console");
  const [session, setSession] = useState<Session | null>(null);
  const [error, setError] = useState("");
  const [info, setInfo] = useState("");

  const [username, setUsername] = useState("demo");
  const [password, setPassword] = useState("demo123");
  const [displayName, setDisplayName] = useState("演示用户");
  const [inviteCode, setInviteCode] = useState("");
  const [companyName, setCompanyName] = useState("");

  const [prompt, setPrompt] = useState("");
  const [excel, setExcel] = useState<File | null>(null);
  const [images, setImages] = useState<File[]>([]);
  const [layoutFiles, setLayoutFiles] = useState<File[]>([]);
  const excelInputRef = useRef<HTMLInputElement>(null);
  const imagesInputRef = useRef<HTMLInputElement>(null);
  const layoutsInputRef = useRef<HTMLInputElement>(null);
  const routeRequestRef = useRef(0);
  const [storageMode, setStorageMode] = useState<"schema_per_app" | "database_per_app">("schema_per_app");
  const [llmProvider, setLlmProvider] = useState("deepseek");
  const [llmModel, setLlmModel] = useState("deepseek-chat");
  const [llmProviders, setLlmProviders] = useState<
    { id: string; label: string; configured: boolean; default_model: string; models: { id: string; label: string }[] }[]
  >([]);
  const [draft, setDraft] = useState<any>(null);
  const [warnings, setWarnings] = useState<string[]>([]);
  const [fidelity, setFidelity] = useState<any>(null);
  const [generateLog, setGenerateLog] = useState<any>(null);
  const [slug, setSlug] = useState("myapp");
  /** 发布目标：__new__ = 新建模块；否则为已有模块 slug（发布新生成的页面） */
  const [publishTarget, setPublishTarget] = useState<string>("__new__");
  const [appList, setAppList] = useState<
    { slug: string; name: string; status: string; status_label?: string; building?: boolean; page_count: number }[]
  >([]);
  const [blueprint, setBlueprint] = useState<any>(null);
  const [resource, setResource] = useState("records");
  const [capsule, setCapsule] = useState("");
  const [agentKeyHidden, setAgentKeyHidden] = useState(true);
  const [busy, setBusy] = useState(false);
  const [authBusy, setAuthBusy] = useState(false);
  const [audits, setAudits] = useState<any[]>([]);
  const [entitlements, setEntitlements] = useState<string[] | null>(null);
  const [demoLoaded, setDemoLoaded] = useState(false);
  const [demoSummary, setDemoSummary] = useState("");

  async function applyDemoFixtures(silent = false) {
    // 未登录时不要抢 busy / info，否则会锁死登录按钮并出现误导绿条
    if (!loadSession() && !session) {
      if (!silent) setError("请先登录后再填入测试默认");
      return;
    }
    setBusy(true);
    if (!silent) setError("");
    try {
      const pack = await loadDemoFixtures();
      setPrompt(pack.prompt);
      setExcel(pack.excel);
      setImages(pack.images);
      setLayoutFiles(pack.layouts);
      setSlug(pack.slug);
      setDemoLoaded(true);
      assignInputFiles(excelInputRef.current, pack.excel ? [pack.excel] : []);
      assignInputFiles(imagesInputRef.current, pack.images);
      assignInputFiles(layoutsInputRef.current, pack.layouts);
      const parts = [
        pack.excel ? pack.excel.name : null,
        pack.images.length ? `${pack.images.length} 张截图` : null,
        pack.layouts.length ? `${pack.layouts.length} 个 HTML` : null,
      ].filter(Boolean);
      setDemoSummary(parts.join(" · ") || "仅需求文本");
      const miss = pack.missing.length ? `（缺：${pack.missing.join(", ")}）` : "";
      setInfo(`已填入测试默认：${pack.label}${miss}`);
    } catch (e: any) {
      if (!silent) setError(e.message || String(e));
    } finally {
      setBusy(false);
    }
  }

  useEffect(() => {
    const saved = loadSession();
    if (saved) setSession(saved);
    const onExpired = () => {
      setSession(null);
      // 草稿预览 / 公开应用 URL 不踢回控制台（否则保真截图永远截到登录页）
      if (previewRouteId() || appRouteSlug()) {
        return;
      }
      setError("登录已失效，请重新登录");
      navigateToConsole();
      setView("console");
      setBlueprint(null);
    };
    window.addEventListener("ajz:session-expired", onExpired);
    listLlmProviders()
      .then((res) => {
        setLlmProviders(res.providers || []);
        const def = res.default || "deepseek";
        const p = (res.providers || []).find((x) => x.id === def) || res.providers?.[0];
        if (p) {
          setLlmProvider(p.id);
          setLlmModel(p.default_model || p.models?.[0]?.id || "");
        }
      })
      .catch(() => {
        /* ignore */
      });
    return () => window.removeEventListener("ajz:session-expired", onExpired);
  }, []);

  // 已登录后才自动填入 test/ 演示包（避免干扰登录页）；超管未进公司时不填
  useEffect(() => {
    if (!session?.accessToken || isPendingMembership(session)) return;
    if (isPlatformAdmin(session.role) && !(session.tenantId > 0)) return;
    const skip = localStorage.getItem("ajz_skip_demo_autoload") === "1";
    if (skip || demoLoaded) return;
    applyDemoFixtures(true).catch(() => {
      /* ignore */
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [session?.accessToken, session?.tenantId]);

  useEffect(() => {
    if (!session?.accessToken || isPendingMembership(session)) {
      setAppList([]);
      setEntitlements(null);
      return;
    }
    if (isPlatformAdmin(session.role) && !(session.tenantId > 0)) {
      setTab("platform");
      setAppList([]);
      setEntitlements(null);
      return;
    }
    listApps(session)
      .then((res) => setAppList(res.items || []))
      .catch(() => setAppList([]));
    if (session.tenantId > 0) {
      // 超管进入公司后 entitlements 接口会返回全量；普通账号按公司额度
      getCompanyEntitlements(session)
        .then((r) => setEntitlements(r.permissions || []))
        .catch(() => setEntitlements(isPlatformAdmin(session.role) ? null : []));
    } else {
      setEntitlements(null);
    }
  }, [session]);

  // 按 URL 打开：/#/preview/{id}（草稿预览，免登录）或 /#/app/{slug}
  useEffect(() => {
    let cancelled = false;
    async function syncRoute() {
      const requestID = ++routeRequestRef.current;
      const isStale = () => cancelled || requestID !== routeRequestRef.current;
      const pid = previewRouteId();
      if (pid) {
        if (!isStale()) {
          setBusy(true);
          setError("");
          setBlueprint(null);
        }
        try {
          const pack = await fetchPreview(pid);
          if (isStale()) return;
          setBlueprint(pack.blueprint);
          setDraft(pack.blueprint);
          setSlug(pack.blueprint?.meta?.slug || "preview");
          setResource(pack.resource || "records");
          setView("app");
        } catch (e: any) {
          if (!isStale()) {
            clearActivePreview();
            setError(e.message || String(e));
            setView("console");
            setBlueprint(null);
            navigateToConsole();
          }
        } finally {
          if (!isStale()) setBusy(false);
        }
        return;
      }

      clearActivePreview();
      const routeSlug = appRouteSlug();
      if (!routeSlug) {
        if (!isStale()) {
          setView("console");
          setBlueprint(null);
        }
        return;
      }
      if (!isStale()) {
        setBusy(true);
        setError("");
        setSlug(routeSlug);
        setBlueprint(null);
      }
      try {
        const sess = session || loadSession();
        const bp = await getBlueprint(sess, routeSlug);
        if (isStale()) return;
        if (String(bp?.meta?.slug || "") !== routeSlug) {
          throw new Error(`模块路由不匹配：请求 ${routeSlug}，返回 ${String(bp?.meta?.slug || "空")}`);
        }
        setBlueprint(bp);
        setDraft(bp);
        const r0 = bp?.apis?.resources?.[0]?.path?.replace(/^\//, "") || resource;
        setResource(r0);
        setView("app");
      } catch (e: any) {
        if (!isStale()) {
          setError(e.message || String(e));
          setView("console");
          setBlueprint(null);
          navigateToConsole();
        }
      } finally {
        if (!isStale()) setBusy(false);
      }
    }
    void syncRoute();
    window.addEventListener("hashchange", syncRoute);
    return () => {
      cancelled = true;
      routeRequestRef.current++;
      window.removeEventListener("hashchange", syncRoute);
    };
  }, [session]);

  // 登录后 / 切到生成页时，把已加载的 File 写回原生选择框（避免一直显示「未选择任何文件」）
  useEffect(() => {
    if (!demoLoaded && !excel && !images.length && !layoutFiles.length) return;
    assignInputFiles(excelInputRef.current, excel ? [excel] : []);
    assignInputFiles(imagesInputRef.current, images);
    assignInputFiles(layoutsInputRef.current, layoutFiles);
  }, [demoLoaded, excel, images, layoutFiles, tab, session, view]);

  function persist(s: Session) {
    setSession(s);
    saveSession(s);
  }

  async function onLogin(payload: {
    mode: import("./AuthShell").LoginMode;
    username?: string;
    phone?: string;
    password?: string;
    sms_code?: string;
  }) {
    setAuthBusy(true);
    setError("");
    setInfo("");
    try {
      const body =
        payload.mode === "phone_sms"
          ? { phone: String(payload.phone || "").trim(), sms_code: String(payload.sms_code || "").trim() }
          : payload.mode === "phone_password"
            ? { phone: String(payload.phone || "").trim(), password: String(payload.password || "") }
            : { username: String(payload.username || "").trim(), password: String(payload.password || "") };
      const s = await loginWith(body);
      if (isPlatformAdmin(s.role) || s.username === "ljk_admin") {
        setTab("platform");
      }
      persist(s);
      setInfo(`已登录 ${s.displayName || s.phone || s.username}（${s.role || "用户"}）`);
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setAuthBusy(false);
    }
  }

  async function onRegister(user = username, pass = password, name = displayName) {
    setAuthBusy(true);
    setError("");
    setInfo("");
    try {
      const s = await register(String(user || "").trim(), String(pass || ""), String(name || "").trim());
      persist(s);
      setInfo(`注册成功：待加入公司（pending）。请输入邀请码，或创建自己的公司。`);
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setAuthBusy(false);
    }
  }

  async function onAcceptInvite() {
    if (!session) return;
    setAuthBusy(true);
    setError("");
    try {
      const s = await acceptInvite(session, inviteCode.trim());
      persist(s);
      setInfo(s.displayName ? `已加入租户 #${s.tenantId}` : `已加入租户 #${s.tenantId}`);
      setInviteCode("");
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setAuthBusy(false);
    }
  }

  async function onCreateCompany() {
    if (!session) return;
    setAuthBusy(true);
    setError("");
    try {
      const s = await createTenant(session, companyName.trim() || displayName || username);
      persist(s);
      setInfo(`已创建公司（租户 #${s.tenantId}），你是管理员`);
      setCompanyName("");
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setAuthBusy(false);
    }
  }

  async function onGenerate() {
    setBusy(true);
    setError("");
    setFidelity(null);
    setGenerateLog(null);
    try {
      const res = await generateDraft(prompt, excel, images, storageMode, llmProvider, llmModel, layoutFiles);
      setDraft(res.draft);
      setWarnings(res.warnings || []);
      setFidelity(res.fidelity || null);
      setGenerateLog(res.generate_log || null);
      if (res.draft?.meta?.slug) setSlug(res.draft.meta.slug);
      const r0 = res.draft?.apis?.resources?.[0]?.path?.replace(/^\//, "");
      if (r0) setResource(r0);
      const fid = res.fidelity;
      let fidMsg = "";
      if (fid && !fid.skipped) {
        fidMsg = fid.passed
          ? `还原度达标 ${fid.final_score}%（目标 ${fid.target}%）。`
          : `还原度 ${fid.final_score ?? "—"}%（目标 ${fid.target}%），请查看差异后继续微调或再生成。`;
      }
      const runId = res.generate_log?.run_id ? ` run=${res.generate_log.run_id}` : "";
      let draftNote = "";
      if (session && res.draft?.meta?.slug) {
        try {
          const s = String(res.draft.meta.slug)
            .trim()
            .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
            .toLowerCase()
            .replace(/[^a-z0-9_]+/g, "_")
            .replace(/_+/g, "_")
            .replace(/^_|_$/g, "")
            .slice(0, 48);
          if (s) {
            await saveDraft(session, s, res.draft);
            const listed = await listApps(session);
            setAppList(listed.items || []);
            setPublishTarget("__new__");
            draftNote = `已登记为在建模块「${s}」。`;
          }
        } catch (de: any) {
          draftNote = `（在建登记未成功：${de.message || de}）`;
        }
      }
      setInfo(
        `草稿置信度 ${res.confidence}。${fidMsg}${draftNote}确认发布后将打开模块页。${runId}`
      );
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setBusy(false);
    }
  }

  function openPublishedApp(appSlug: string) {
    setSlug(appSlug);
    navigateToApp(appSlug);
  }

  async function onPublish() {
    if (!session || !draft) return;
    setBusy(true);
    setError("");
    try {
      const normalized = JSON.parse(JSON.stringify(draft));
      const intoExisting = publishTarget !== "__new__";
      // slug：新建用草稿；已有模块用所选目标
      let s = intoExisting
        ? publishTarget
        : String(normalized?.meta?.slug || "app")
            .trim()
            .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
            .toLowerCase()
            .replace(/[^a-z0-9_]+/g, "_")
            .replace(/_+/g, "_")
            .replace(/^_|_$/g, "");
      if (!intoExisting) {
        if (!s || !/^[a-z]/.test(s)) s = `app_${s || "draft"}`.replace(/_+/g, "_");
        s = s.slice(0, 48).replace(/_+$/g, "") || "app";
      }
      const existingMeta = intoExisting ? appList.find((a) => a.slug === s) : null;
      normalized.meta = {
        ...(normalized.meta || {}),
        slug: s,
        name: existingMeta?.name || normalized.meta?.name || s,
      };
      if (!normalized.meta.name) normalized.meta.name = s;
      if (!normalized.version) normalized.version = "1.0";
      if (!normalized.storage) normalized.storage = { mode: "schema_per_app", engine: "postgres" };
      if (!normalized.security) normalized.security = { visibility: "private", roles: [], row_policies: [] };
      if (normalized.apis) {
        normalized.apis = { ...normalized.apis, base_path: `/api/v1/apps/${s}` };
      }
      // 标识符 / type 粗规范，并同步 apis、pages 引用（避免 unknown entity）
      const snake = (v: string) =>
        String(v || "")
          .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
          .toLowerCase()
          .replace(/[^a-z0-9_]+/g, "_")
          .replace(/_+/g, "_")
          .replace(/^_|_$/g, "");
      const entityMap: Record<string, string> = {};
      for (const ent of normalized.entities || []) {
        const oldName = String(ent.name || "");
        if (ent.name) ent.name = snake(ent.name) || "record";
        if (ent.table) ent.table = snake(ent.table) || ent.name;
        if (oldName) {
          entityMap[oldName] = ent.name;
          entityMap[snake(oldName)] = ent.name;
        }
        for (const f of ent.fields || []) {
          if (f.name) f.name = snake(String(f.name).replace(/\./g, "_")) || "field";
          const t = String(f.type || "string").toLowerCase();
          if (["float", "float64", "double", "number", "numeric"].includes(t)) f.type = "decimal";
          else if (["integer", "int32"].includes(t)) f.type = "int";
          else if (["int64", "long"].includes(t)) f.type = "bigint";
          else if (t === "bool") f.type = "boolean";
          else if (["varchar", "str"].includes(t)) f.type = "string";
        }
      }
      for (const r of normalized.apis?.resources || []) {
        const key = String(r.entity || "");
        r.entity = entityMap[key] || entityMap[snake(key)] || snake(key) || r.entity;
        if (r.path) r.path = "/" + (snake(String(r.path).replace(/^\//, "")) || "items");
      }
      for (const p of normalized.pages || []) {
        const key = String(p.entity || "");
        p.entity = entityMap[key] || entityMap[snake(key)] || snake(key) || p.entity;
      }
      const r0 = normalized?.apis?.resources?.[0]?.path?.replace(/^\//, "") || resource;
      setResource(r0);
      const mode = intoExisting ? "add_pages" : "create";
      const resp = await publish(session, s, normalized, mode);
      setDraft(normalized);
      setSlug(s);
      // 刷新模块列表
      try {
        const listed = await listApps(session);
        setAppList(listed.items || []);
      } catch {
        /* ignore */
      }
      const modeLabel =
        resp.publish_mode === "pages_added" || resp.publish_mode === "merged"
          ? `已发布新页面${resp.added_pages?.length ? `（${(resp.added_pages as string[]).join(", ")}）` : ""}`
          : "已新建模块";
      setInfo(`${modeLabel} ${s} → ${resp.schema_name}。正在跳转模块页…`);
      openPublishedApp(s);
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setBusy(false);
    }
  }

  function onOpenApp() {
    if (!session) return;
    setError("");
    if (!slug.trim()) {
      setError("请填写已发布模块 slug");
      return;
    }
    const hit = appList.find((a) => a.slug === slug.trim());
    if (hit && hit.status !== "published") {
      setError(`模块「${slug}」状态为${hit.status_label || hit.status}，请先发布后再打开；可在「模块管理」继续编辑`);
      return;
    }
    setInfo(`正在跳转模块：/#/app/${slug}`);
    openPublishedApp(slug.trim());
  }
  async function onCapsule() {
    if (!session) return;
    setBusy(true);
    setError("");
    try {
      const res = await getCapsule(session, slug);
      setCapsule(res.capsule);
      setInfo(res.hint);
      setTab("agent");
    } catch (e: any) {
      setError(e.message || String(e));
    } finally {
      setBusy(false);
    }
  }

  // 公开展示 / 草稿预览优先：路由加载中不闪登录页
  if (view === "app" && blueprint) {
    return (
      <GeneratedApp
        key={String(blueprint?.meta?.slug || slug)}
        session={session}
        blueprint={blueprint}
        onBack={() => {
          clearActivePreview();
          navigateToConsole();
        }}
      />
    );
  }

  const onConsoleTabChange = async (next: Tab) => {
    setTab(next);
    setError("");
    if (next === "audit" && session) {
      try {
        const res = await listAudit(session);
        setAudits(res.items || []);
      } catch (e: any) {
        setError(e.message || String(e));
      }
    }
  };

  const pendingRoute = previewRouteId() || appRouteSlug();
  if (pendingRoute && !blueprint) {
    return (
      <LoadingShell
        message={`正在打开${previewRouteId() ? "草稿预览" : "模块"}…`}
        error={error || undefined}
      />
    );
  }

  if (!session) {
    return (
      <LoginShell
        username={username}
        password={password}
        displayName={displayName}
        busy={authBusy}
        error={error}
        info={info}
        onUsername={setUsername}
        onPassword={setPassword}
        onDisplayName={setDisplayName}
        onLogin={onLogin}
        onRegister={onRegister}
      />
    );
  }

  if (isPendingMembership(session)) {
    return (
      <PendingShell
        displayLabel={session.displayName || session.username || ""}
        inviteCode={inviteCode}
        companyName={companyName}
        busy={authBusy}
        error={error}
        info={info}
        onInviteCode={setInviteCode}
        onCompanyName={setCompanyName}
        onAcceptInvite={onAcceptInvite}
        onCreateCompany={onCreateCompany}
        onLogout={() => {
          clearSession();
          setSession(null);
        }}
      />
    );
  }

  const providerOptions = (llmProviders.length
    ? llmProviders
    : [
        { id: "deepseek", label: "DeepSeek", configured: true, default_model: "", models: [] as { id: string; label: string }[] },
        { id: "minimax", label: "MiniMax", configured: true, default_model: "", models: [] },
        { id: "heuristic", label: "本地启发式", configured: true, default_model: "", models: [] },
      ]
  ).map((p) => ({
    value: p.id,
    label: `${p.label}${p.configured === false ? "（未配置 Key）" : ""}`,
  }));

  const modelOptions = (llmProviders.find((p) => p.id === llmProvider)?.models || []).map((m) => ({
    value: m.id,
    label: m.label,
  }));
  if (!modelOptions.length && llmModel) {
    modelOptions.push({ value: llmModel, label: llmModel || "默认" });
  }

  return (
    <ConsoleLayout
      session={session}
      entitlements={entitlements}
      tab={tab}
      busy={busy}
      onTabChange={onConsoleTabChange}
      onOpenApp={onOpenApp}
      onSession={persist}
      onLogout={() => {
        clearSession();
        setSession(null);
      }}
      banner={
        <>
          {error ? <Alert type="error" showIcon closable message={error} onClose={() => setError("")} style={{ marginBottom: 12 }} /> : null}
          {info ? <Alert type="success" showIcon closable message={info} onClose={() => setInfo("")} style={{ marginBottom: 12 }} /> : null}
        </>
      }
    >
      {tab === "generate" && (
        <Row gutter={[16, 16]}>
          <Col xs={24} lg={14}>
            <Card title="需求与素材" extra={
              <Space>
                <Button disabled={busy} onClick={() => applyDemoFixtures(false)}>填入测试默认</Button>
                {demoLoaded && <Typography.Text type="secondary">已加载：{demoSummary}</Typography.Text>}
              </Space>
            }>
              <Form layout="vertical" requiredMark={false}>
                <Form.Item
                  label="需求描述"
                  extra="可写业务要求、按键，以及截图关系。上传截图后，未点名修改的部分按原图还原。"
                >
                  <Input.TextArea
                    rows={6}
                    placeholder={"示例：\n两张截图为同一界面：一张展开监督条，一张为收起态；以展开态为准还原。\n平台抬头：……\n操作：新增、刷新、导入 Excel、导出 Excel"}
                    value={prompt}
                    onChange={(e) => setPrompt(e.target.value)}
                  />
                </Form.Item>
                <Form.Item label="数据文件（xlsx / csv / json）" extra={excel ? `当前：${excel.name}` : undefined}>
                  <input
                    ref={excelInputRef}
                    type="file"
                    accept=".xlsx,.xls,.csv,.json"
                    onChange={(e) => setExcel(e.target.files?.[0] || null)}
                  />
                </Form.Item>
                <Form.Item
                  label="界面截图"
                  extra={images.length ? `当前：${images.map((f) => f.name).join("、")}` : "上传后自动走还原闭环"}
                >
                  <input
                    ref={imagesInputRef}
                    type="file"
                    accept="image/*"
                    multiple
                    onChange={(e) => setImages(Array.from(e.target.files || []))}
                  />
                </Form.Item>
                <Form.Item
                  label="页面源码（Ctrl+S 另存为）"
                  extra={layoutFiles.length ? `当前：${layoutFiles.map((f) => f.name).join("、")}` : "支持 .html / .htm / .mhtml"}
                >
                  <input
                    ref={layoutsInputRef}
                    type="file"
                    accept=".html,.htm,.mhtml,.mht,.xhtml,text/html,multipart/related"
                    multiple
                    onChange={(e) => setLayoutFiles(Array.from(e.target.files || []))}
                  />
                </Form.Item>
                <Row gutter={12}>
                  <Col xs={24} md={8}>
                    <Form.Item label="大模型">
                      <Select
                        value={llmProvider}
                        options={providerOptions}
                        onChange={(id) => {
                          setLlmProvider(id);
                          const p = llmProviders.find((x) => x.id === id);
                          setLlmModel(p?.default_model || p?.models?.[0]?.id || "");
                        }}
                      />
                    </Form.Item>
                  </Col>
                  <Col xs={24} md={8}>
                    <Form.Item label="模型">
                      <Select value={llmModel} options={modelOptions} onChange={setLlmModel} />
                    </Form.Item>
                  </Col>
                  <Col xs={24} md={8}>
                    <Form.Item label="存储模式">
                      <Select
                        value={storageMode}
                        onChange={(v) => setStorageMode(v as typeof storageMode)}
                        options={[
                          { value: "schema_per_app", label: "共享库 · schema" },
                          { value: "database_per_app", label: "独立库" },
                        ]}
                      />
                    </Form.Item>
                  </Col>
                </Row>
                <Form.Item label="发布目标">
                  <Select
                    value={publishTarget}
                    onChange={(v) => {
                      setPublishTarget(v);
                      if (v !== "__new__") setSlug(v);
                    }}
                    options={[
                      { value: "__new__", label: "新建模块（草稿 slug）" },
                      ...appList.map((a) => ({
                        value: a.slug,
                        label: `${a.name || a.slug}（${a.slug} · ${a.status_label || a.status} · ${a.page_count} 页）`,
                      })),
                    ]}
                  />
                </Form.Item>
                <Typography.Paragraph type="secondary" style={{ marginTop: -8 }}>
                  {publishTarget === "__new__"
                    ? "将创建新模块；slug 取自蓝图 meta.slug。不同用户可访问的模块由授权决定。"
                    : "将把草稿里新生成的页面发布到该模块（page id / route 不可与现有重复）。"}
                </Typography.Paragraph>
                <Form.Item label="已发布 slug（打开模块用）">
                  <Input value={slug} onChange={(e) => setSlug(e.target.value)} />
                </Form.Item>
                <Form.Item label="持久化上传（对象存储）">
                  <input
                    type="file"
                    onChange={async (e) => {
                      const f = e.target.files?.[0];
                      if (!f || !session) return;
                      try {
                        const up = await uploadFile(session, f);
                        setInfo(`已上传 ${up.filename} → ${up.url}`);
                      } catch (err: any) {
                        setError(err.message || String(err));
                      }
                    }}
                  />
                </Form.Item>
                <Space wrap>
                  <Button type="primary" loading={busy} onClick={onGenerate}>生成蓝图草稿</Button>
                  <Button disabled={busy || !draft} onClick={onPublish}>
                    {publishTarget === "__new__" ? "确认发布（新建模块）" : "确认发布（新页面）"}
                  </Button>
                  <Button disabled={busy} onClick={onOpenApp}>打开已有模块</Button>
                </Space>
                {warnings.length > 0 && (
                  <Alert
                    style={{ marginTop: 16 }}
                    type="warning"
                    showIcon
                    message="生成警告"
                    description={<ul style={{ margin: 0, paddingLeft: 18 }}>{warnings.map((w) => <li key={w}>{w}</li>)}</ul>}
                  />
                )}
                {fidelity && !fidelity.skipped && (
                  <Card size="small" style={{ marginTop: 16 }} title={`还原度迭代 ${fidelity.passed ? "已达标" : "未达标"} · ${fidelity.final_score ?? "—"}% / 目标 ${fidelity.target}%`}>
                    <ul style={{ margin: 0, paddingLeft: 18 }}>
                      {(fidelity.rounds || []).map((r: any) => (
                        <li key={r.round}>
                          第{r.round}轮：{r.score ?? "—"}%{r.pass ? " ✓" : ""}
                          {(r.fails || []).length
                            ? ` · 差异：${(r.fails || []).slice(0, 5).join("；")}${(r.fails || []).length > 5 ? "…" : ""}`
                            : ""}
                        </li>
                      ))}
                    </ul>
                  </Card>
                )}
                {generateLog?.lines?.length > 0 && (
                  <Card
                    size="small"
                    style={{ marginTop: 16 }}
                    title={`生成日志 · ${generateLog.run_id || "—"}`}
                  >
                    <Typography.Paragraph type="secondary" style={{ marginBottom: 8 }}>
                      服务端同步写入 .runtime/logs/generate/
                      {generateLog.log_file
                        ? `（本次：${String(generateLog.log_file).replace(/^.*[\\/]/, "")}）`
                        : ""}
                    </Typography.Paragraph>
                    <pre className="generate-log-body">
                      {(generateLog.lines as any[])
                        .map((l) => `${(l.level || "info").toUpperCase().padEnd(7)} ${l.message || ""}`)
                        .join("\n")}
                    </pre>
                  </Card>
                )}
              </Form>
            </Card>
          </Col>
          <Col xs={24} lg={10}>
            <Card title="蓝图 JSON" extra={<Typography.Text type="secondary">可微调后发布</Typography.Text>}>
              <Typography.Paragraph type="secondary">
                含 entities / apis / pages。发布后 pages 会渲染成真正的业务前端。
              </Typography.Paragraph>
              <Input.TextArea
                className="json-editor"
                rows={22}
                value={draft ? JSON.stringify(draft, null, 2) : ""}
                onChange={(e) => { try { setDraft(JSON.parse(e.target.value)); } catch { /* */ } }}
                placeholder="生成后在此显示蓝图…"
              />
            </Card>
          </Col>
        </Row>
      )}

      {tab === "agent" && (
        <Card title="智能体胶囊">
          <Typography.Paragraph type="secondary">
            前端不展示明文 API 契约，只展示加密胶囊；智能体用 agent_key 解密后请求。
          </Typography.Paragraph>
          <Form layout="vertical" requiredMark={false} style={{ maxWidth: 480 }}>
            <Form.Item label="模块 slug">
              <Input value={slug} onChange={(e) => setSlug(e.target.value)} />
            </Form.Item>
            <Space wrap>
              <Button type="primary" loading={busy} onClick={onCapsule}>生成加密胶囊</Button>
              <Button onClick={() => setAgentKeyHidden((v) => !v)}>
                {agentKeyHidden ? "临时查看 agent_key" : "隐藏 agent_key"}
              </Button>
            </Space>
          </Form>
          {!agentKeyHidden && <pre className="mono-block">{session.agentKey}</pre>}
          {capsule && (
            <div style={{ marginTop: 16 }}>
              <pre className="mono-block">{capsule}</pre>
              <Button style={{ marginTop: 8 }} onClick={() => navigator.clipboard.writeText(capsule)}>复制密文</Button>
            </div>
          )}
        </Card>
      )}

      {tab === "modules" && session && (
        <ModulesPage
          session={session}
          onOpenPublished={(s) => {
            setSlug(s);
            openPublishedApp(s);
          }}
          onContinueDraft={async (s) => {
            setBusy(true);
            setError("");
            try {
              const bp = await getBlueprint(session, s);
              setDraft(bp);
              setSlug(s);
              setPublishTarget(s);
              setTab("generate");
              setInfo(`已加载在建模块「${s}」，可继续编辑后发布`);
            } catch (e: any) {
              setError(e.message || String(e));
            } finally {
              setBusy(false);
            }
          }}
        />
      )}

      {tab === "sync" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && (
        <SyncPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
        />
      )}

      {tab === "roles" && session && (
        <RolesPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
        />
      )}

      {tab === "agents" && session && (
        <AgentUsersPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
        />
      )}

      {tab === "platform" && session && isPlatformAdmin(session.role) && (
        <PlatformTenantsPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
          onSession={persist}
          onEnteredCompany={() => setTab("members")}
        />
      )}
      {tab === "orgs" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && (
        <OrgUnitsPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
        />
      )}

      {tab === "invites" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && (
        <InvitesPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
        />
      )}
      {tab === "members" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && (
        <MembersPage
          session={session}
          busy={busy}
          setBusy={setBusy}
          setError={setError}
          setInfo={setInfo}
        />
      )}

      {tab === "audit" && (
        <Card title="审计">
          <Table
            rowKey="id"
            size="middle"
            pagination={{ pageSize: 20 }}
            dataSource={audits}
            locale={{ emptyText: "暂无审计记录" }}
            columns={[
              { title: "时间", dataIndex: "created_at", width: 180, render: (v) => String(v) },
              { title: "用户", dataIndex: "user_id", width: 120 },
              { title: "动作", dataIndex: "action", width: 140 },
              { title: "详情", dataIndex: "detail", ellipsis: true },
            ]}
          />
        </Card>
      )}
    </ConsoleLayout>
  );
}
