import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { LayoutDashboard, Workflow, Database, FileText, Activity, Bookmark, Boxes, Settings } from "lucide-react";

import { TopBar } from "./shell/TopBar";
import { LeftNav, type ViewKey } from "./shell/LeftNav";
import { ApprovalBar } from "./shell/ApprovalBar";
import { StudioView } from "./studio/StudioView";
import { MemoryView } from "./views/MemoryView";
import { KbView } from "./views/KbView";
import { ReportView } from "./views/ReportView";
import { RunsView } from "./views/RunsView";
import { Home } from "./views/Home";
import { Placeholder } from "./views/Placeholder";
import { CommandPalette, type Command } from "./components/CommandPalette";
import { UpdateBanner } from "./components/UpdateBanner";
import { Login } from "./views/Login";
import { submitTask, streamTokens, streamExec, taskStatus, listRuns, authMe, logout, type Identity, type AuthUser } from "./lib/api";
import type { TaskDsl } from "./lib/dsl";
import { emptyRun, type RunState } from "./lib/run";
import { ToastProvider } from "./ui";

// 会话标识：本地持久化生成一次（多轮历史用，与鉴权身份分离）。
function getSessionId(): string {
  let s = localStorage.getItem("sdx_sess");
  if (!s) {
    s = "sess-" + Math.random().toString(36).slice(2, 10);
    localStorage.setItem("sdx_sess", s);
  }
  return s;
}

const PLACEHOLDERS: Partial<Record<ViewKey, { title: string; desc: string }>> = {
  home: { title: "工作台", desc: "概览：知识库 / 文档 / 近期运行 / 待办报告 / 配额计费 + 快捷入口。" },
  kb: { title: "知识库 (RAG)", desc: "入库流水线监控 · 检索调试台(带来源徽标) · 文档/块浏览 · 知识图谱 · 检索评测。依赖 embedding + 入库 worker + 真实混合检索。" },
  report: { title: "报告生成", desc: "模板库 · 大纲编辑 · 章节并行生成进度 · 实时预览(含引用) · 导出 docx/pdf。依赖 RAG 核心链 + UniOffice。" },
  runs: { title: "运行 · 观测", desc: "实时执行 · 节点轨迹 · 工具调用 · 运行历史复盘。当前运行结果见底部抽屉。" },
  market: { title: "市场 · Packs", desc: "垂直包(法律/医疗/金融) · Agent 模板 · 开通向导(建租户→入库→注册模板→应用配置)。依赖多租户 + Pack 格式。" },
  admin: { title: "管理", desc: "租户/工作区 · 用户计费 · 护栏 · 模型与连接 · 设置。" },
};

export default function App() {
  const [view, setView] = useState<ViewKey>("home");
  const [user, setUser] = useState<AuthUser | null>(null);
  const [authLoading, setAuthLoading] = useState(true);
  const identity = useMemo<Identity>(() => ({ userId: user?.id ?? "", sessionId: getSessionId() }), [user]);
  const [run, setRun] = useState<RunState>(emptyRun);
  const [cmdOpen, setCmdOpen] = useState(false);
  const closeRef = useRef<(() => void) | null>(null);

  const execCloseRef = useRef<(() => void) | null>(null);
  const pollRef = useRef<number | null>(null);
  const stopPoll = () => {
    if (pollRef.current != null) {
      window.clearInterval(pollRef.current);
      pollRef.current = null;
    }
  };

  // 全局 ⌘K / Ctrl+K 唤起命令面板（键盘优先工作站入口）。
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setCmdOpen((o) => !o);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  // 启动校验现有令牌；并监听 401 登出事件（令牌失效 → 回登录页）。
  useEffect(() => {
    authMe()
      .then(setUser)
      .catch(() => setUser(null))
      .finally(() => setAuthLoading(false));
    const onLogout = () => setUser(null);
    window.addEventListener("sdx:logout", onLogout);
    return () => window.removeEventListener("sdx:logout", onLogout);
  }, []);

  const onLogout = useCallback(() => {
    logout();
    setUser(null);
  }, []);

  const commands = useMemo<Command[]>(() => {
    const go = (key: ViewKey) => () => setView(key);
    return [
      { id: "home", label: "前往 · 工作台", icon: LayoutDashboard, group: "页面", keywords: "home dashboard 概览", run: go("home") },
      { id: "studio", label: "前往 · 编排", icon: Workflow, group: "页面", keywords: "studio agent graph 编排 图", run: go("studio") },
      { id: "kb", label: "前往 · 知识库", icon: Database, group: "页面", keywords: "kb rag 检索 图谱 入库", run: go("kb") },
      { id: "report", label: "前往 · 报告生成", icon: FileText, group: "页面", keywords: "report 报告 word docx", run: go("report") },
      { id: "runs", label: "前往 · 运行观测", icon: Activity, group: "页面", keywords: "runs trace 轨迹 观测", run: go("runs") },
      { id: "memory", label: "前往 · 记忆", icon: Bookmark, group: "页面", keywords: "memory 画像 偏好", run: go("memory") },
      { id: "market", label: "前往 · 市场 Packs", icon: Boxes, group: "页面", keywords: "market pack 垂直", run: go("market") },
      { id: "admin", label: "前往 · 管理", icon: Settings, group: "页面", keywords: "admin 租户 模型 设置", run: go("admin") },
      { id: "act-report", label: "生成报告", icon: FileText, group: "动作", keywords: "新建 report 撰写", run: go("report") },
      { id: "act-ingest", label: "入库知识", icon: Database, group: "动作", keywords: "上传 ingest 文件", run: go("kb") },
      { id: "act-studio", label: "新建 Agent 编排", icon: Workflow, group: "动作", keywords: "new flow", run: go("studio") },
    ];
  }, []);

  // attachRun：给一个 task 挂上「状态轮询 + 执行轨迹流 + token 流」。新发起与恢复待审任务共用，
  // 故页面刷新/重开后能重新挂回在途任务（HITL 审批可能等数小时，必须可恢复，否则待审任务点不到批准）。
  const attachRun = useCallback((taskId: string, t0: number) => {
    let first = true;
    // 轮询后端任务状态：可靠捕获 waiting（审批中断）—— exec 实时事件会抢跑，状态落 PG 不会丢。
    const terminal = new Set(["done", "failed", "timeout", "rejected"]);
    pollRef.current = window.setInterval(async () => {
      try {
        const s = await taskStatus(taskId);
        setRun((r) => {
          if (r.taskId !== taskId) return r;
          // 命中终态：同时把 phase 也置终态，否则 token 流没收到 done（如恢复的任务/外部置终态）
          // 时 phase 会永卡 streaming → 运行按钮永远禁用「运行中…」。
          const phase = terminal.has(s.status)
            ? s.status === "failed" || s.status === "timeout"
              ? "error"
              : "done"
            : r.phase;
          return { ...r, lifecycle: s.status, detail: s.detail, phase };
        });
        if (terminal.has(s.status)) stopPoll();
      } catch {
        /* 忽略瞬时失败，下个 tick 再试 */
      }
    }, 1500);
    // 执行轨迹：与 token 流并行订阅；网关从 Redis 回放历史事件（含 await），故恢复时也能补回审批摘要。
    execCloseRef.current = streamExec(
      taskId,
      (ev) => setRun((r) => (r.taskId === taskId ? { ...r, exec: [...r.exec, ev] } : r)),
      () => {},
      () => {},
    );
    closeRef.current = streamTokens(
      taskId,
      (tok) =>
        setRun((r) => {
          if (r.taskId !== taskId) return r;
          const ev = first ? [...r.events, { t: Date.now() - t0, label: "首 token" }] : r.events;
          first = false;
          return { ...r, output: r.output + tok, events: ev };
        }),
      () => setRun((r) => (r.taskId === taskId ? { ...r, phase: "done", events: [...r.events, { t: Date.now() - t0, label: "完成" }] } : r)),
      () => setRun((r) => (r.taskId === taskId ? { ...r, phase: "error", error: "连接中断" } : r)),
    );
  }, []);

  const onRun = useCallback(
    async (dsl: TaskDsl) => {
      closeRef.current?.();
      execCloseRef.current?.();
      stopPoll();
      const t0 = Date.now();
      setRun({ phase: "submitting", output: "", events: [{ t: 0, label: "提交任务" }], exec: [] });
      setView("runs"); // 发起即跳「运行 · 观测」，实时看轨迹/输出（观测统一收敛在此页）
      try {
        const taskId = await submitTask(dsl, identity);
        setRun((r) => ({
          ...r,
          phase: "streaming",
          taskId,
          events: [...r.events, { t: Date.now() - t0, label: `已发布 ${taskId}` }],
        }));
        attachRun(taskId, t0);
      } catch (e) {
        setRun((r) => ({ ...r, phase: "error", error: (e as Error).message }));
      }
    },
    [identity, attachRun],
  );

  // 恢复在途待审任务：登录后若存在 waiting 任务且当前无 live run，挂回它 → 全局审批条重现，
  // 用户刷新页面/重开 app 也能继续批准（HITL 持久化中断后审批可跨重启、可等数小时）。
  const restoredRef = useRef(false);
  useEffect(() => {
    if (!user || restoredRef.current) return;
    restoredRef.current = true; // 只恢复一次。App 是根组件不会真卸载，故不用 cancelled 守卫
    (async () => {                // （StrictMode 双调用 effect 时，cancelled 会把首次 async 的恢复误吞）。
      try {
        const runs = await listRuns(20);
        const waiting = runs.find((r) => r.status === "waiting");
        if (!waiting) return;
        const t0 = Date.now();
        setRun((r) =>
          r.taskId
            ? r // 已有 live run，不覆盖
            : { phase: "streaming", taskId: waiting.task_id, output: "", events: [{ t: 0, label: "恢复待审任务" }], exec: [], lifecycle: "waiting" },
        );
        attachRun(waiting.task_id, t0);
      } catch {
        /* 列表拉取失败则跳过恢复，不影响正常使用 */
      }
    })();
  }, [user, attachRun]);

  // 鉴权门：校验中显示占位；未登录显示登录页；登录后进入主应用。
  if (authLoading) {
    return <div className="flex h-screen w-screen items-center justify-center bg-ink-950 text-sm text-slate-500">加载中…</div>;
  }
  if (!user) {
    return (
      <ToastProvider>
        <Login onAuthed={setUser} />
      </ToastProvider>
    );
  }

  return (
    <ToastProvider>
    <div className="relative flex h-screen w-screen flex-col bg-ink-950 text-slate-200">
      {/* 顶部柔光，增加纵深 */}
      <div
        className="pointer-events-none absolute inset-x-0 top-0 h-64 opacity-60"
        style={{ background: "radial-gradient(60% 100% at 50% 0%, rgba(124,92,246,0.10), transparent 70%)" }}
      />
      <UpdateBanner />
      <TopBar user={user} onLogout={onLogout} onCommand={() => setCmdOpen(true)} />
      <ApprovalBar run={run} />
      <div className="relative flex min-h-0 flex-1">
        <LeftNav active={view} onSelect={setView} />
        <main className="min-w-0 flex-1 overflow-hidden">
          {view === "home" ? (
            <Home onSelect={setView} />
          ) : view === "studio" ? (
            <StudioView onRun={onRun} phase={run.phase} identity={identity} />
          ) : view === "kb" ? (
            <KbView identity={identity} />
          ) : view === "report" ? (
            <ReportView identity={identity} />
          ) : view === "runs" ? (
            <RunsView run={run} />
          ) : view === "memory" ? (
            <MemoryView identity={identity} />
          ) : (
            <Placeholder {...(PLACEHOLDERS[view] ?? { title: "模块", desc: "规划中。" })} />
          )}
        </main>
      </div>
      <CommandPalette open={cmdOpen} onClose={() => setCmdOpen(false)} commands={commands} />
    </div>
    </ToastProvider>
  );
}
