import {
  AlertTriangle,
  Bot,
  ClipboardList,
  Database,
  FileSpreadsheet,
  FolderOpen,
  KeyRound,
  Play,
  RefreshCcw,
  Save,
  Send,
  Settings,
  ShieldCheck,
  Terminal,
  Trash2,
  UserRound,
  Wifi
} from "lucide-react";
import { Component, type ErrorInfo, type ReactNode } from "react";
import { FormEvent, useEffect, useMemo, useRef, useState } from "react";
import type {
  AgentTask,
  AppConfig,
  AssistantResultTable,
  CaptchaResult,
  DashboardSnapshot,
  LogEntry,
  McpRuntimeConfigSnapshot,
  PriceComparisonExportResult,
  PriceComparisonRow,
  SessionStatus,
  TableExportResult,
  TaskRunResult
} from "../shared/types";
import logoUrl from "./assets/logo.png";

type View = "assistant" | "tasks" | "settings" | "logs";

interface ChatMessage {
  id: string;
  role: "user" | "assistant";
  text: string;
  taskIds?: string[];
  taskParams?: Record<string, Record<string, string>>;
  comparisonRows?: PriceComparisonRow[];
  orderStats?: OrderStatsTableData;
  mcpTables?: AssistantResultTable[];
  attachment?: ChatAttachment;
}

interface OrderStatsTableData {
  title: string;
  rows: OrderStatRow[];
}

interface OrderStatRow {
  metric: string;
  value: string;
  note: string;
  evidence: string;
}

interface ChatAttachment {
  kind: "excel";
  fileName: string;
  filePath: string;
  rowCount: number;
  tableCount?: number;
}

interface Feedback {
  type: "success" | "error" | "info";
  text: string;
}

interface ErrorBoundaryState {
  error?: Error;
}

const categoryNames: Record<AgentTask["category"], string> = {
  system: "系统",
  member: "会员",
  points: "积分",
  coupon: "券",
  order: "订单",
  product: "商品",
  ops: "运维"
};

export class AppErrorBoundary extends Component<{ children: ReactNode }, ErrorBoundaryState> {
  state: ErrorBoundaryState = {};

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { error };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    console.error("Renderer crashed", error, info.componentStack);
  }

  render() {
    if (this.state.error) {
      return (
        <div className="error-screen">
          <img src={logoUrl} alt="市民请集合" />
          <h1>界面渲染异常</h1>
          <p>{this.state.error.message || "当前页面遇到异常，已拦截白屏。"}</p>
          <button className="primary-button" type="button" onClick={() => window.location.reload()}>
            <RefreshCcw size={16} />
            <span>重新加载</span>
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export function App() {
  const [activeView, setActiveView] = useState<View>("assistant");
  const [dashboard, setDashboard] = useState<DashboardSnapshot>();
  const [config, setConfig] = useState<AppConfig>();
  const [configDraft, setConfigDraft] = useState<AppConfig>();
  const [session, setSession] = useState<SessionStatus>({ authenticated: false });
  const [tasks, setTasks] = useState<AgentTask[]>([]);
  const [logs, setLogs] = useState<LogEntry[]>([]);
  const [selectedTaskId, setSelectedTaskId] = useState("product.price.compare");
  const [params, setParams] = useState<Record<string, string>>({});
  const [lastResult, setLastResult] = useState<TaskRunResult>();
  const [busy, setBusy] = useState(false);
  const [chatBusy, setChatBusy] = useState(false);
  const [authFeedback, setAuthFeedback] = useState<Feedback>();
  const [loginFocusTick, setLoginFocusTick] = useState(0);
  const [prompt, setPrompt] = useState("");
  const [messages, setMessages] = useState<ChatMessage[]>([
    {
      id: crypto.randomUUID(),
      role: "assistant",
      text: "你好，我是市民请集合智能助手。你可以直接说要查会员、导出数据、处理积分或记录一个管理员操作需求，我会先给出可执行动作。"
    }
  ]);

  const selectedTask = useMemo(
    () => tasks.find((task) => task.id === selectedTaskId) ?? tasks[0],
    [selectedTaskId, tasks]
  );

  async function refresh() {
    const [nextDashboard, nextConfig, nextTasks, nextLogs, nextSession] = await Promise.all([
      window.smqjhAgent.getDashboard(),
      window.smqjhAgent.getConfig(),
      window.smqjhAgent.listTasks(),
      window.smqjhAgent.listLogs(),
      window.smqjhAgent.getSession()
    ]);
    setDashboard(nextDashboard);
    setConfig(nextConfig);
    setConfigDraft((current) => current ?? nextConfig);
    setTasks(nextTasks);
    setLogs(nextLogs);
    setSession(nextSession);
  }

  useEffect(() => {
    void refresh();
    return window.smqjhAgent.onLog((entry) => setLogs((current) => [entry, ...current].slice(0, 300)));
  }, []);

  async function runTask() {
    if (!selectedTask) {
      return;
    }

    setBusy(true);
    try {
      const result = await window.smqjhAgent.runTask({ taskId: selectedTask.id, params });
      setLastResult(result);
      await refresh();
    } finally {
      setBusy(false);
    }
  }

  async function saveConfig(nextConfig: AppConfig) {
    const saved = await window.smqjhAgent.saveConfig(nextConfig);
    setConfig(saved);
    setConfigDraft(saved);
    await refresh();
  }

  async function login(request: { username: string; password: string; tenantCode: string; captchaId: string; captchaCode: string }) {
    setBusy(true);
    setAuthFeedback({ type: "info", text: "正在登录..." });
    try {
      const nextSession = await window.smqjhAgent.login(request);
      setSession(nextSession);
      setAuthFeedback({ type: "success", text: "登录成功" });
      await refresh();
    } catch (error) {
      const message = friendlyError(error);
      setAuthFeedback({ type: "error", text: message || "登录失败" });
    } finally {
      setBusy(false);
    }
  }

  async function resolveProductPriceParams(text: string, currentMessages: ChatMessage[]): Promise<Record<string, string> | undefined> {
    const localParams = buildProductPriceParams(text, currentMessages);
    if (!shouldAskDeepSeekForProductIntent(text, currentMessages)) {
      return localParams;
    }

    try {
      const result = await window.smqjhAgent.extractIntent({
        message: text,
        history: currentMessages.slice(-8).map((message) => ({
          role: message.role,
          content: message.text
        })),
        authenticated: session.authenticated,
        username: session.username,
        environmentName: config?.environmentName ?? dashboard?.config.environmentName ?? "test-gateway",
        baseUrl: config?.baseUrl ?? dashboard?.config.baseUrl ?? "http://192.168.1.242:8080",
        availableTasks: tasks.map(({ id, title, category, description, dangerLevel }) => ({
          id,
          title,
          category,
          description,
          dangerLevel
        }))
      });

      const params = normalizeProductIntentParams(result.params, text, currentMessages);
      if (result.taskId === "product.price.compare" && result.confidence >= 0.5 && params?.productKeyword) {
        return params;
      }

      return undefined;
    } catch {
      return localParams;
    }
  }

  async function resolveDatabaseQueryParams(text: string, currentMessages: ChatMessage[]): Promise<Record<string, string> | undefined> {
    if (!config?.deepSeek.enabled && !dashboard?.config.deepSeek.enabled) {
      return undefined;
    }

    try {
      const result = await window.smqjhAgent.extractIntent({
        message: text,
        history: currentMessages.slice(-8).map((message) => ({
          role: message.role,
          content: message.text
        })),
        authenticated: session.authenticated,
        username: session.username,
        environmentName: config?.environmentName ?? dashboard?.config.environmentName ?? "test-gateway",
        baseUrl: config?.baseUrl ?? dashboard?.config.baseUrl ?? "http://192.168.1.242:8080",
        availableTasks: tasks.map(({ id, title, category, description, dangerLevel }) => ({
          id,
          title,
          category,
          description,
          dangerLevel
        }))
      });

      if (result.taskId !== "database.readonly.query" || result.confidence < 0.55) {
        return undefined;
      }

      const sql = (result.params?.sql ?? "").trim();
      if (!sql) {
        return undefined;
      }

      return {
        question: result.params?.question?.trim() || text,
        sql
      };
    } catch {
      return undefined;
    }
  }

  async function sendPrompt(event: FormEvent) {
    event.preventDefault();
    const text = prompt.trim();
    if (!text || chatBusy) {
      return;
    }

    const userMessage: ChatMessage = { id: crypto.randomUUID(), role: "user", text };
    setPrompt("");
    setChatBusy(true);

    const rowsForExport = isComparisonExportIntent(text) ? getLatestComparisonRows(messages) ?? getComparisonRows(lastResult?.data) : undefined;
    const tablesForExport = isComparisonExportIntent(text) ? getLatestMcpTables(messages) : undefined;
    if (rowsForExport) {
      const actionMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: "assistant",
        text: "收到，我正在把上一轮价格对比结果整理成 Excel 文件。"
      };
      setMessages((current) => [...current, userMessage, actionMessage]);

      try {
        const exported = await window.smqjhAgent.exportPriceComparison({
          title: buildComparisonExportTitle(rowsForExport),
          rows: rowsForExport
        });
        setMessages((current) =>
          current.map((message) =>
            message.id === actionMessage.id
              ? {
                  ...message,
                  text: formatExportResult(exported),
                  attachment: {
                    kind: "excel",
                    fileName: exported.fileName,
                    filePath: exported.filePath,
                    rowCount: exported.rowCount
                  }
                }
              : message
          )
        );
      } catch (error) {
        const message = friendlyError(error);
        setMessages((current) =>
          current.map((item) =>
            item.id === actionMessage.id
              ? {
                  ...item,
                  text: `Excel 导出失败：${message || "写入文件失败"}。上一轮表格数据还在当前会话里，可以稍后重试导出。`
                }
              : item
          )
        );
      } finally {
        setChatBusy(false);
      }
      return;
    }

    if (tablesForExport) {
      const actionMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: "assistant",
        text: "收到，我正在把上一轮表格结果整理成 Excel 文件。"
      };
      setMessages((current) => [...current, userMessage, actionMessage]);

      try {
        const exported = await window.smqjhAgent.exportTables({
          title: buildTableExportTitle(tablesForExport),
          tables: tablesForExport
        });
        setMessages((current) =>
          current.map((message) =>
            message.id === actionMessage.id
              ? {
                  ...message,
                  text: formatTableExportResult(exported),
                  attachment: {
                    kind: "excel",
                    fileName: exported.fileName,
                    filePath: exported.filePath,
                    rowCount: exported.rowCount,
                    tableCount: exported.tableCount
                  }
                }
              : message
          )
        );
      } catch (error) {
        const message = friendlyError(error);
        setMessages((current) =>
          current.map((item) =>
            item.id === actionMessage.id
              ? {
                  ...item,
                  text: `Excel 导出失败：${message || "写入文件失败"}。上一轮表格数据还在当前会话里，可以稍后重试导出。`
                }
              : item
          )
        );
      } finally {
        setChatBusy(false);
      }
      return;
    }

    const latestOrderStats = isOrderStatsTableIntent(text) ? getLatestOrderStats(messages) : undefined;
    if (latestOrderStats) {
      setMessages((current) => [
        ...current,
        userMessage,
        {
          id: crypto.randomUUID(),
          role: "assistant",
          text: "已把上一轮订单统计整理成表格视图。",
          orderStats: latestOrderStats
        }
      ]);
      setChatBusy(false);
      return;
    }

    const thinkingMessage: ChatMessage = {
      id: crypto.randomUUID(),
      role: "assistant",
      text: "正在理解需求，并按需要调用 MCP/本地工具..."
    };
    const requestMessages = [...messages, userMessage].slice(-12).map((message) => ({
      role: message.role,
      content: message.text
    }));

    setMessages((current) => [...current, userMessage, thinkingMessage]);

    try {
      const result = await window.smqjhAgent.runAssistant({
        message: text,
        history: requestMessages,
        authenticated: session.authenticated,
        username: session.username,
        environmentName: config?.environmentName ?? dashboard?.config.environmentName ?? "test-gateway",
        baseUrl: config?.baseUrl ?? dashboard?.config.baseUrl ?? "http://192.168.1.242:8080"
      });
      const taskIds = result.toolCalls
        .map((call) => call.name)
        .filter((name) => tasks.some((task) => task.id === name));
      const taskParams = Object.fromEntries(
        result.toolCalls
          .filter((call) => tasks.some((task) => task.id === call.name))
          .map((call) => [call.name, stringifyParams(call.arguments)])
      );

      await refresh();
      setMessages((current) =>
        current.map((message) =>
          message.id === thinkingMessage.id
            ? {
                ...message,
                text: result.content || "已完成处理，结果如下。",
                taskIds: taskIds.length ? taskIds : undefined,
                taskParams,
                comparisonRows: result.comparisonRows?.length ? result.comparisonRows : undefined,
                mcpTables: result.tables?.length ? result.tables : undefined
              }
            : message
        )
      );
    } catch (error) {
      const message = friendlyError(error);
      setMessages((current) =>
        current.map((item) =>
          item.id === thinkingMessage.id
            ? {
                ...item,
                text: `智能体执行失败：${message || "调用失败"}。\n\n我没有继续走前端规则兜底，避免再次出现意图被硬匹配带偏。请检查 MCP 服务、MCP 托管的 DeepSeek 配置和数据库只读配置后重试。`
              }
            : item
        )
      );
    } finally {
      setChatBusy(false);
    }
    return;

    const orderCountParams = buildOrderCountParams(text, messages);
    if (orderCountParams) {
      const actionMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: "assistant",
        text: "收到，我直接走数据库只读查询订单数量，并排除逻辑删除订单。"
      };

      setMessages((current) => [...current, userMessage, actionMessage]);
      setSelectedTaskId("order.count.query");
      setParams(orderCountParams);
      setLastResult(undefined);

      try {
        const result = await window.smqjhAgent.runTask({
          taskId: "order.count.query",
          params: orderCountParams
        });

        setLastResult(result);
        await refresh();
        setMessages((current) =>
          current.map((message) =>
            message.id === actionMessage.id
              ? {
                  ...message,
                  text: formatOrderCountResult(result, orderCountParams),
                  orderStats: buildOrderStatsTable(result, orderCountParams)
                }
              : message
          )
        );
      } catch (error) {
        const message = friendlyError(error);
        setMessages((current) =>
          current.map((item) =>
            item.id === actionMessage.id
              ? {
                  ...item,
                  text: `订单数量查询启动失败：${message || "调用失败"}。`
                }
              : item
          )
        );
      } finally {
        setChatBusy(false);
      }
      return;
    }

    const productLookupParams = await resolveProductLookupParams(text, messages);
    if (productLookupParams) {
      const actionMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: "assistant",
        text: `我判断这个问题需要查商品库，正在按“${productLookupParams.productKeyword}”执行数据库只读查询。`,
        taskIds: ["product.lookup.summary"],
        taskParams: {
          "product.lookup.summary": productLookupParams
        }
      };

      setMessages((current) => [...current, userMessage, actionMessage]);
      setSelectedTaskId("product.lookup.summary");
      setParams(productLookupParams);
      setLastResult(undefined);

      try {
        const result = await window.smqjhAgent.runTask({
          taskId: "product.lookup.summary",
          params: productLookupParams
        });

        setLastResult(result);
        await refresh();
        setMessages((current) =>
          current.map((message) =>
            message.id === actionMessage.id
              ? {
                  ...message,
                  text: formatProductLookupResult(result, productLookupParams)
                }
              : message
          )
        );
      } catch (error) {
        const message = friendlyError(error);
        setMessages((current) =>
          current.map((item) =>
            item.id === actionMessage.id
              ? {
                  ...item,
                  text: `商品资料查询启动失败：${message || "调用失败"}。`
                }
              : item
          )
        );
      } finally {
        setChatBusy(false);
      }
      return;
    }

    const productPriceParams = await resolveProductPriceParams(text, messages);
    if (productPriceParams) {
      const actionMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: "assistant",
        text: `DeepSeek 已识别到商品和比价口径，我按“${productPriceParams.productKeyword}”直接发起商品价格对比调研：先用数据库只读查询 smqjh 系统价，再后台采集苏宁易购和慢慢买公开价格线索。`,
        taskIds: ["product.price.compare"],
        taskParams: {
          "product.price.compare": productPriceParams
        }
      };

      setMessages((current) => [...current, userMessage, actionMessage]);
      setSelectedTaskId("product.price.compare");
      setParams(productPriceParams);
      setLastResult(undefined);

      try {
        const result = await window.smqjhAgent.runTask({
          taskId: "product.price.compare",
          params: productPriceParams
        });

        setLastResult(result);
        await refresh();
        setMessages((current) =>
          current.map((message) =>
            message.id === actionMessage.id
              ? {
                  ...message,
                  text: formatProductPriceResult(result, productPriceParams),
                  comparisonRows: getComparisonRows(result.data)
                }
              : message
          )
        );
      } catch (error) {
        const message = friendlyError(error);
        setMessages((current) =>
          current.map((item) =>
            item.id === actionMessage.id
              ? {
                  ...item,
                  text: `商品价格对比调研启动失败：${message || "调用失败"}。你也可以点击下方任务手动执行。`
                }
              : item
          )
        );
      } finally {
        setChatBusy(false);
      }
      return;
    }

    const databaseQueryParams = await resolveDatabaseQueryParams(text, messages);
    if (databaseQueryParams) {
      const actionMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: "assistant",
        text: "DeepSeek 判断这个问题需要查 smqjh 数据库，我会先做只读 SQL 安全校验，再直接执行并返回表格。",
        taskIds: ["database.readonly.query"],
        taskParams: {
          "database.readonly.query": databaseQueryParams
        }
      };

      setMessages((current) => [...current, userMessage, actionMessage]);
      setSelectedTaskId("database.readonly.query");
      setParams(databaseQueryParams);
      setLastResult(undefined);

      try {
        const result = await window.smqjhAgent.runTask({
          taskId: "database.readonly.query",
          params: databaseQueryParams
        });

        setLastResult(result);
        await refresh();
        setMessages((current) =>
          current.map((message) =>
            message.id === actionMessage.id
              ? {
                  ...message,
                  text: formatReadOnlyQueryResult(result, databaseQueryParams)
                }
              : message
          )
        );
      } catch (error) {
        const message = friendlyError(error);
        setMessages((current) =>
          current.map((item) =>
            item.id === actionMessage.id
              ? {
                  ...item,
                  text: `数据库智能只读查询启动失败：${message || "调用失败"}。`
                }
              : item
          )
        );
      } finally {
        setChatBusy(false);
      }
      return;
    }

    const localReply = buildAssistantReply(text, tasks, session);
    const legacyThinkingMessage: ChatMessage = {
      id: crypto.randomUUID(),
      role: "assistant",
      text: "正在分析这个需求..."
    };
    const legacyRequestMessages = [...messages, userMessage].slice(-12).map((message) => ({
      role: message.role,
      content: message.text
    }));

    setMessages((current) => [...current, userMessage, legacyThinkingMessage]);

    try {
      const result = await window.smqjhAgent.chat({
        messages: legacyRequestMessages,
        authenticated: session.authenticated,
        username: session.username,
        environmentName: config?.environmentName ?? dashboard?.config.environmentName ?? "test-gateway",
        baseUrl: config?.baseUrl ?? dashboard?.config.baseUrl ?? "http://192.168.1.242:8080",
        availableTasks: tasks.map(({ id, title, category, description, dangerLevel }) => ({
          id,
          title,
          category,
          description,
          dangerLevel
        }))
      });
      const taskIds = matchTaskIds(text, tasks);
      setMessages((current) =>
        current.map((message) =>
          message.id === legacyThinkingMessage.id
            ? { ...message, text: result.content, taskIds: taskIds.length > 0 ? taskIds : localReply.taskIds }
            : message
        )
      );
    } catch (error) {
      const message = friendlyError(error);
      setMessages((current) =>
        current.map((item) =>
          item.id === legacyThinkingMessage.id
            ? {
                ...item,
                text: `DeepSeek 暂时没有返回：${message || "调用失败"}\n\n${localReply.text}`,
                taskIds: localReply.taskIds
              }
            : item
        )
      );
    } finally {
      setChatBusy(false);
    }
  }

  function openTask(taskId: string, presetParams?: Record<string, string>) {
    setSelectedTaskId(taskId);
    setParams(presetParams ?? {});
    setLastResult(undefined);
    setActiveView("tasks");
  }

  function openLogin() {
    setActiveView("settings");
    setLoginFocusTick((value) => value + 1);
  }

  return (
    <div className="shell">
      <aside className="sidebar">
        <div className="brand">
          <img className="brand-logo" src={logoUrl} alt="市民请集合" />
          <div>
            <strong>市民请集合</strong>
            <span>智能助手</span>
          </div>
        </div>
        <nav className="nav">
          <NavButton active={activeView === "assistant"} icon={<Bot size={18} />} label="对话" onClick={() => setActiveView("assistant")} />
          <NavButton active={activeView === "tasks"} icon={<ClipboardList size={18} />} label="任务" onClick={() => setActiveView("tasks")} />
          <NavButton active={activeView === "settings"} icon={<Settings size={18} />} label="配置" onClick={() => setActiveView("settings")} />
          <NavButton active={activeView === "logs"} icon={<Terminal size={18} />} label="日志" onClick={() => setActiveView("logs")} />
        </nav>
        <button className="session-box" type="button" onClick={openLogin} title="打开后台登录">
          <StatusPill ok={session.authenticated} label={session.authenticated ? "已登录" : "未登录"} />
          <span>{session.username ?? "后台账号"}</span>
        </button>
      </aside>

      <main className="content">
        <header className="topbar">
          <div>
            <h1>{activeView === "assistant" ? "市民请集合智能助手" : activeView === "tasks" ? "任务中心" : activeView === "settings" ? "系统配置" : "运行日志"}</h1>
            <p>{config?.environmentName ?? "test-gateway"} · {config?.baseUrl ?? "http://192.168.1.242:8080"}</p>
          </div>
          <button className="icon-button" onClick={() => void refresh()} title="刷新">
            <RefreshCcw size={18} />
          </button>
        </header>

        {activeView === "assistant" && dashboard && (
          <AssistantView
            messages={messages}
            prompt={prompt}
            setPrompt={setPrompt}
            sendPrompt={sendPrompt}
            chatBusy={chatBusy}
            dashboard={dashboard}
            session={session}
            tasks={tasks}
            onPickTask={openTask}
          />
        )}

        {activeView === "tasks" && (
          <TasksView
            tasks={tasks}
            selectedTask={selectedTask}
            selectedTaskId={selectedTaskId}
            setSelectedTaskId={(taskId) => {
              setSelectedTaskId(taskId);
              setParams({});
              setLastResult(undefined);
            }}
            params={params}
            setParams={setParams}
            runTask={runTask}
            busy={busy}
            lastResult={lastResult}
          />
        )}

        {activeView === "settings" && config && (
          <SettingsView
            config={configDraft ?? config}
            setConfig={setConfigDraft}
            saveConfig={saveConfig}
            login={login}
            logout={async () => {
              setAuthFeedback(undefined);
              setSession(await window.smqjhAgent.logout());
            }}
            busy={busy}
            session={session}
            feedback={authFeedback}
            focusTick={loginFocusTick}
          />
        )}

        {activeView === "logs" && (
          <LogsView logs={logs} clear={async () => {
            await window.smqjhAgent.clearLogs();
            setLogs([]);
          }} />
        )}
      </main>
    </div>
  );
}

function matchTaskIds(text: string, tasks: AgentTask[]): string[] {
  const normalized = text.toLowerCase();
  const matchedIds: string[] = [];

  if (isOrderCountIntent(text)) {
    matchedIds.push("order.count.query");
  }
  if (isProductLookupIntent(text)) {
    matchedIds.push("product.lookup.summary");
  }
  if (isProductPriceIntent(text, "")) {
    matchedIds.push("product.price.compare");
  }
  if (text.includes("管理员") || text.includes("当前用户") || text.includes("当前账号") || normalized.includes("me")) {
    matchedIds.push("user.current");
  }
  if (text.includes("网关") || text.includes("连通") || text.includes("验证码") || normalized.includes("health")) {
    matchedIds.push("cloud.health");
  }

  const taskIds = matchedIds.filter((id, index) => matchedIds.indexOf(id) === index && tasks.some((task) => task.id === id));
  if (taskIds.length === 0) {
    taskIds.push(...["product.price.compare", "order.count.query", "cloud.health"].filter((id) => tasks.some((task) => task.id === id)));
  }

  return taskIds;
}

function buildAssistantReply(text: string, tasks: AgentTask[], session: SessionStatus): { text: string; taskIds: string[] } {
  const taskIds = matchTaskIds(text, tasks);

  const authText = session.authenticated ? "当前后台账号已登录。" : "当前还没有后台登录，涉及后台接口的动作会先要求登录。";
  return {
    taskIds,
    text: `${authText} 我理解这个需求可以先走 ${taskIds.length} 个动作：${taskIds.map((id) => tasks.find((task) => task.id === id)?.title ?? id).join("、")}。你可以点右侧动作进入参数确认。`
  };
}

function buildOrderCountParams(text: string, messages: ChatMessage[]): Record<string, string> | undefined {
  if (!isOrderCountIntent(text, messages)) {
    return undefined;
  }

  const mobile = extractMobile(text) || getLatestOrderMobile(messages);
  const dateRange = inferOrderDateRange(text);
  return {
    mobile: mobile ?? "",
    dateFrom: dateRange?.dateFrom ?? "",
    dateTo: dateRange?.dateTo ?? ""
  };
}

function isOrderCountIntent(text: string, messages: ChatMessage[] = []): boolean {
  const normalized = text.trim();
  if (!normalized) {
    return false;
  }
  const hasOrderWord = /订单|下单|订购|购买|买了|消费记录|交易记录|订单数|订单数量|多少单/.test(normalized);
  const hasAmountWord = /销售额|营业额|营收|金额|订单金额|实付|支付金额|收款|收入/.test(normalized);
  const hasCountWord = /多少|几|数量|总数|统计|计数|条|笔|单|次|下了|汇报|汇总/.test(normalized);
  const followsOrderContext = hasAmountWord && Boolean(getLatestOrderStats(messages));
  return (hasOrderWord && (hasCountWord || hasAmountWord)) || hasAmountWord || followsOrderContext;
}

function extractMobile(text: string): string | undefined {
  return text.match(/1\d{10}/)?.[0];
}

function getLatestOrderMobile(messages: ChatMessage[]): string | undefined {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const mobile = extractMobile(messages[index].text);
    if (mobile) {
      return mobile;
    }
  }
  return undefined;
}

function isOrderStatsTableIntent(text: string): boolean {
  return /表格|表格视图|列表|列出来|整理一下|汇总一下/.test(text) && !isComparisonExportIntent(text);
}

function getLatestOrderStats(messages: ChatMessage[]): OrderStatsTableData | undefined {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    if (messages[index].orderStats?.rows.length) {
      return messages[index].orderStats;
    }
  }
  return undefined;
}

function inferOrderDateRange(text: string): { dateFrom: string; dateTo: string } | undefined {
  const today = startOfLocalDay(new Date());
  if (/今天|今日/.test(text)) {
    return { dateFrom: formatIsoDate(today), dateTo: formatIsoDate(addDays(today, 1)) };
  }
  if (/昨天|昨日/.test(text)) {
    const yesterday = addDays(today, -1);
    return { dateFrom: formatIsoDate(yesterday), dateTo: formatIsoDate(today) };
  }
  const recentDays = text.match(/近\s*(\d+)\s*天|最近\s*(\d+)\s*天/);
  if (recentDays) {
    const days = Number(recentDays[1] || recentDays[2]);
    if (Number.isFinite(days) && days > 0) {
      return { dateFrom: formatIsoDate(addDays(today, -days)), dateTo: formatIsoDate(addDays(today, 1)) };
    }
  }
  if (/近一年|最近一年|一年内|过去一年/.test(text)) {
    const from = new Date(today);
    from.setFullYear(from.getFullYear() - 1);
    return { dateFrom: formatIsoDate(from), dateTo: formatIsoDate(addDays(today, 1)) };
  }
  if (/本月|这个月|当月/.test(text)) {
    return { dateFrom: formatIsoDate(new Date(today.getFullYear(), today.getMonth(), 1)), dateTo: formatIsoDate(addDays(today, 1)) };
  }
  if (/今年|本年/.test(text)) {
    return { dateFrom: formatIsoDate(new Date(today.getFullYear(), 0, 1)), dateTo: formatIsoDate(addDays(today, 1)) };
  }
  if (/去年|上一年/.test(text)) {
    return { dateFrom: formatIsoDate(new Date(today.getFullYear() - 1, 0, 1)), dateTo: formatIsoDate(new Date(today.getFullYear(), 0, 1)) };
  }
  const explicitRange = text.match(/(\d{4}-\d{1,2}-\d{1,2}).{0,8}(?:到|至|-|~).{0,8}(\d{4}-\d{1,2}-\d{1,2})/);
  if (explicitRange) {
    return {
      dateFrom: normalizeDateText(explicitRange[1]),
      dateTo: formatIsoDate(addDays(parseLocalDate(normalizeDateText(explicitRange[2])), 1))
    };
  }
  return undefined;
}

function formatOrderCountResult(result: TaskRunResult, params: Record<string, string>): string {
  if (!result.success) {
    return `订单数量查询未完成：${result.message}`;
  }

  const data = result.data && typeof result.data === "object" ? (result.data as Record<string, unknown>) : {};
  const scope = buildOrderScopeLabel(params);
  return `已通过 MySQL 只读查询${scope}，订单总数：${numberText(data.totalCount)} 单，已支付销售额：${moneyText(data.paidAmount)}。下方已整理成表格视图。`;
}

function buildOrderStatsTable(result: TaskRunResult, params: Record<string, string>): OrderStatsTableData | undefined {
  if (!result.success || !result.data || typeof result.data !== "object") {
    return undefined;
  }

  const data = result.data as Record<string, unknown>;
  const scope = buildOrderScopeLabel(params);
  const evidence = String(data.evidence ?? "").trim();
  const rows: OrderStatRow[] = [
    {
      metric: "订单总数",
      value: `${numberText(data.totalCount)} 单`,
      note: "满足当前筛选条件且未逻辑删除的订单数量",
      evidence
    },
    {
      metric: "订单金额合计",
      value: moneyText(data.totalAmount),
      note: `金额口径：${String(data.amountField ?? "COALESCE(actual_total, total, order_money)")}`,
      evidence
    },
    {
      metric: "已支付销售额",
      value: moneyText(data.paidAmount),
      note: "is_payed = 1 的订单金额合计",
      evidence: "SUM(CASE WHEN is_payed = 1 THEN orderAmount ELSE 0 END)"
    },
    {
      metric: "未支付金额",
      value: moneyText(data.unpaidAmount),
      note: "is_payed = 0 或为空的订单金额合计",
      evidence: "SUM(CASE WHEN COALESCE(is_payed, 0) = 0 THEN orderAmount ELSE 0 END)"
    },
    {
      metric: "已完成金额",
      value: moneyText(data.completedAmount),
      note: "完成口径订单金额合计",
      evidence: "hb_order_status IN (80, 90, 100)"
    },
    {
      metric: "已取消金额",
      value: moneyText(data.canceledAmount),
      note: "取消口径订单金额合计",
      evidence: "hb_order_status IN (-1, 50, 60)"
    }
  ];

  if (params.mobile) {
    rows.push(
      {
        metric: "收货手机号匹配",
        value: `${numberText(data.consigneeMobileCount)} 单`,
        note: `consignee_mobile 匹配 ${params.mobile}`,
        evidence: `oms_order.consignee_mobile LIKE "%${params.mobile}%"`
      },
      {
        metric: "会员手机号匹配",
        value: `${numberText(data.memberMobileCount)} 单`,
        note: `会员手机号或权益账号手机号匹配 ${params.mobile}`,
        evidence: `sm_member.mobile / ums_member_account.mobile LIKE "%${params.mobile}%"`
      },
      {
        metric: "买家手机号匹配",
        value: `${numberText(data.buyerMobileCount)} 单`,
        note: `买家会员手机号匹配 ${params.mobile}`,
        evidence: `sm_member.mobile LIKE "%${params.mobile}%"`
      }
    );
  }

  rows.push(
    {
      metric: "已支付",
      value: `${numberText(data.paidCount)} 单`,
      note: "is_payed = 1",
      evidence: "COUNT DISTINCT order_id"
    },
    {
      metric: "未支付",
      value: `${numberText(data.unpaidCount)} 单`,
      note: "is_payed = 0 或为空",
      evidence: "COUNT DISTINCT order_id"
    },
    {
      metric: "已完成",
      value: `${numberText(data.completedCount)} 单`,
      note: "hb_order_status 属于完成口径",
      evidence: "hb_order_status IN (80, 90, 100)"
    },
    {
      metric: "已取消",
      value: `${numberText(data.canceledCount)} 单`,
      note: "hb_order_status 属于取消口径",
      evidence: "hb_order_status IN (-1, 50, 60)"
    }
  );

  const latestOrderTime = String(data.latestOrderTime ?? "").trim();
  if (latestOrderTime) {
    rows.push({
      metric: "最近下单时间",
      value: latestOrderTime,
      note: "当前筛选范围内 create_time 最大值",
      evidence: "MAX(oms_order.create_time)"
    });
  }

  return {
    title: `订单统计${scope}`,
    rows
  };
}

function buildOrderScopeLabel(params: Record<string, string>): string {
  const parts: string[] = [];
  if (params.mobile) {
    parts.push(`手机号 ${params.mobile}`);
  }
  if (params.dateFrom && params.dateTo) {
    parts.push(`${params.dateFrom} 至 ${formatIsoDate(addDays(parseLocalDate(params.dateTo), -1))}`);
  } else if (params.dateFrom) {
    parts.push(`${params.dateFrom} 起`);
  } else if (params.dateTo) {
    parts.push(`${formatIsoDate(addDays(parseLocalDate(params.dateTo), -1))} 前`);
  }
  return parts.length ? `（${parts.join("，")}）` : "全部非删除订单";
}

function numberText(value: unknown): string {
  const number = Number(value);
  return Number.isFinite(number) ? String(number) : "0";
}

function moneyText(value: unknown): string {
  const number = Number(value);
  return Number.isFinite(number) ? `¥${number.toFixed(2)}` : "¥0.00";
}

function startOfLocalDay(date: Date): Date {
  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}

function addDays(date: Date, days: number): Date {
  const next = new Date(date);
  next.setDate(next.getDate() + days);
  return next;
}

function formatIsoDate(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

function normalizeDateText(value: string): string {
  return value
    .split("-")
    .map((part, index) => (index === 0 ? part.padStart(4, "0") : part.padStart(2, "0")))
    .join("-");
}

function parseLocalDate(value: string): Date {
  const [year, month, day] = normalizeDateText(value).split("-").map(Number);
  return new Date(year, month - 1, day);
}

async function resolveProductLookupParams(text: string, currentMessages: ChatMessage[]): Promise<Record<string, string> | undefined> {
  return buildProductLookupParams(text, currentMessages);
}

function buildProductLookupParams(text: string, messages: ChatMessage[]): Record<string, string> | undefined {
  if (!isProductLookupIntent(text)) {
    return undefined;
  }
  const keyword = extractProductKeyword(text) || getLatestComparisonProduct(messages);
  return keyword ? { productKeyword: keyword } : undefined;
}

function isProductLookupIntent(text: string): boolean {
  if (isObviousNonProductTopic(text)) {
    return false;
  }
  const hasProductContext = /商品|产品|sku|spu|商城|业务系统|系统|后台|smqjh/i.test(text);
  const hasLookupWord = /描述|说明|详情|资料|信息|简介|规格|品牌|状态|叫什么|是什么|查一下|查询|查看/.test(text);
  const keyword = extractProductKeyword(text);
  return Boolean(keyword) && hasLookupWord && (hasProductContext || /ml|mL|ML|毫升|瓶|罐|盒|箱|可乐|饮料|牛奶|茶|水/.test(text));
}

function formatProductLookupResult(result: TaskRunResult, params: Record<string, string>): string {
  if (!result.success) {
    return `商品资料查询未完成：${result.message}。`;
  }
  const data = result.data && typeof result.data === "object" ? (result.data as Record<string, unknown>) : {};
  return [
    `已通过 MySQL 只读查询“${params.productKeyword}”的商品资料。`,
    "",
    "| 项目 | 内容 |",
    "| --- | --- |",
    `| 商品名称 | ${escapeMarkdownCell(String(data.productName ?? ""))} |`,
    `| 品牌 | ${escapeMarkdownCell(String(data.brandName ?? "未设置"))} |`,
    `| 价格 | ${escapeMarkdownCell(String(data.price ?? "未获取"))} |`,
    `| 状态 | ${escapeMarkdownCell(String(data.status ?? "未设置"))} |`,
    `| 商品描述 | ${escapeMarkdownCell(String(data.description ?? "未读取到描述"))} |`,
    `| 备注 | ${escapeMarkdownCell(String(data.note ?? ""))} |`,
    `| 依据 | ${escapeMarkdownCell(String(data.evidence ?? ""))} |`
  ].join("\n");
}

function escapeMarkdownCell(value: string): string {
  return value
    .replace(/\|/g, "\\|")
    .replace(/\r?\n/g, " ")
    .trim();
}

function formatReadOnlyQueryResult(result: TaskRunResult, params: Record<string, string>): string {
  if (!result.success) {
    const data = result.data && typeof result.data === "object" ? (result.data as Record<string, unknown>) : {};
    const reason = String(data.reason ?? result.message ?? "只读查询失败");
    const sql = String(data.sql ?? params.sql ?? "").trim();
    return [
      `数据库智能只读查询未完成：${reason}`,
      sql ? `依据 SQL：${sql}` : ""
    ]
      .filter(Boolean)
      .join("\n\n");
  }

  const data = result.data && typeof result.data === "object" ? (result.data as Record<string, unknown>) : {};
  const columns = Array.isArray(data.columns) ? data.columns.map((column) => String(column)) : [];
  const rows = Array.isArray(data.rows) ? data.rows.filter((row): row is Record<string, unknown> => Boolean(row) && typeof row === "object") : [];
  const executedSql = String(data.executedSql ?? params.sql ?? "").trim();
  const rowCount = Number(data.rowCount ?? rows.length);

  return [
    `已根据你的问题执行数据库只读查询，共返回 ${Number.isFinite(rowCount) ? rowCount : rows.length} 行。`,
    toMarkdownResultTable(columns, rows),
    executedSql ? `依据 SQL：${executedSql}` : ""
  ]
    .filter(Boolean)
    .join("\n\n");
}

function toMarkdownResultTable(columns: string[], rows: Record<string, unknown>[]): string {
  if (columns.length === 0) {
    return "| 结果 | 说明 |\n| --- | --- |\n| 无返回行 | 查询执行成功，但结果集为空 |";
  }

  const displayColumns = columns.slice(0, 12);
  const overflowCount = Math.max(columns.length - displayColumns.length, 0);
  const header = `| ${displayColumns.map(escapeMarkdownCell).join(" | ")}${overflowCount > 0 ? " | 其他列 |" : " |"}`;
  const divider = `| ${displayColumns.map(() => "---").join(" | ")}${overflowCount > 0 ? " | --- |" : " |"}`;
  const body = rows.slice(0, 100).map((row) => {
    const cells = displayColumns.map((column) => escapeMarkdownCell(formatQueryCell(row[column])));
    if (overflowCount > 0) {
      cells.push(`还有 ${overflowCount} 列未展开`);
    }
    return `| ${cells.join(" | ")} |`;
  });

  return [header, divider, ...body].join("\n");
}

function formatQueryCell(value: unknown): string {
  if (value === null || value === undefined) {
    return "";
  }
  if (value instanceof Date) {
    return value.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", hour12: false });
  }
  if (typeof value === "object") {
    return JSON.stringify(value);
  }
  return String(value);
}

function buildProductPriceParams(text: string, messages: ChatMessage[]): Record<string, string> | undefined {
  const standaloneIntent = isStandaloneProductPriceIntent(text);
  const followUpIntent = !standaloneIntent && isProductPriceFollowUp(text, messages);
  if (!standaloneIntent && !followUpIntent) {
    return undefined;
  }

  const productFromCurrent = extractProductKeyword(text);
  const productFromContext = followUpIntent ? getLatestComparisonProduct(messages) : "";
  let productKeyword = productFromCurrent || productFromContext;
  if (!productKeyword) {
    return undefined;
  }

  const spec = inferProductSpec(text);
  if (spec && !productKeyword.includes(spec)) {
    productKeyword = `${productKeyword} ${spec}`;
  }

  return {
    productKeyword,
    productType: "auto",
    openBrowser: /打开浏览器|弹出页面|人工复核|手动查看/.test(text) ? "true" : "false",
    notes: spec ? `按${spec}口径查询，对比同规格到手价。` : "按同规格、同口径到手价进行对比。"
  };
}

function shouldAskDeepSeekForProductIntent(text: string, messages: ChatMessage[]): boolean {
  return isStandaloneProductPriceIntent(text) || isProductPriceFollowUp(text, messages);
}

function normalizeProductIntentParams(
  params: Record<string, string> | undefined,
  text: string,
  messages: ChatMessage[]
): Record<string, string> | undefined {
  if (!params) {
    return undefined;
  }
  const fallback = buildProductPriceParams(text, messages);
  if (!fallback) {
    return undefined;
  }

  const rawKeyword = (params.productKeyword || params.keyword || "").trim();
  const keyword = extractProductKeyword(rawKeyword) || rawKeyword || fallback.productKeyword || "";
  if (!keyword) {
    return undefined;
  }

  const spec = inferProductSpec(`${text} ${keyword}`);
  const productKeyword = spec && !keyword.includes(spec) ? `${keyword} ${spec}` : keyword;
  const notes = (params.notes || "").trim() || (spec ? `按${spec}口径查询，对比同规格到手价。` : "按同规格、同口径到手价进行对比。");

  return {
    productKeyword,
    productType: params.productType || "auto",
    openBrowser: String(params.openBrowser).toLowerCase() === "true" || /打开浏览器|弹出页面|人工复核|手动查看/.test(text) ? "true" : "false",
    notes
  };
}

function isProductPriceIntent(text: string, historyText: string): boolean {
  return isStandaloneProductPriceIntent(text) || isStandaloneProductPriceIntent(historyText);
}

function isStandaloneProductPriceIntent(text: string): boolean {
  if (isObviousNonProductTopic(text)) {
    return false;
  }

  const keyword = extractProductKeyword(text);
  if (!keyword || isGenericProductKeyword(keyword)) {
    return false;
  }

  const hasPriceWord = /价格|定价|售价|比价|到手价|多少钱|对比|比较|电商价|系统价/.test(text);
  const hasProductContext = /商品|产品|系统价|业务系统|sku|spu|淘宝|京东|拼多多|电商|市面|平台|商城|同规格|smqjh/i.test(text);
  const hasConcreteSpec = Boolean(inferProductSpec(text)) || /单瓶|一瓶|1瓶|单件|一件|箱装|整箱|瓶装|盒装|罐装|ml|mL|ML|毫升|升|L\b/.test(text);
  return hasPriceWord && (hasProductContext || hasConcreteSpec);
}

function isProductPriceFollowUp(text: string, messages: ChatMessage[]): boolean {
  if (isObviousNonProductTopic(text) || !getLatestComparisonProduct(messages)) {
    return false;
  }

  const looksLikeFollowUp = /^(再|继续|换成|改成|按|只|也|那|这个|这款|它|同规格|单瓶|单件|一瓶|一件)/.test(text.trim())
    || /单瓶|一瓶|1瓶|单件|一件|同规格|口径|规格|\d+(?:\.\d+)?\s*(?:ml|mL|ML|毫升|l|L|升)/.test(text);
  const hasPriceAction = /价格|定价|售价|比价|到手价|多少钱|对比|比较|查|看|跑/.test(text);
  return looksLikeFollowUp && hasPriceAction;
}

function isObviousNonProductTopic(text: string): boolean {
  if (/天气|气温|下雨/.test(text)) {
    return true;
  }
  const hasPriceOrProduct = /商品|产品|价格|定价|售价|比价|到手价|多少钱|系统价|电商|淘宝|京东|拼多多|市面/.test(text);
  return !hasPriceOrProduct && /订单|订购|支付|退款|售后|物流|快递|会员|手机号|登录|验证码|日志|网关|接口|token|权限|菜单|配置|数据库|报错|启动|退出/.test(text);
}

function isGenericProductKeyword(keyword: string): boolean {
  return /^(单瓶|一瓶|1瓶|单件|一件|单个|规格|同规格|口径|价格|比价|对比|好|好的|行|可以|只|这个|这款|它)$/i.test(keyword.trim());
}

function getLatestComparisonProduct(messages: ChatMessage[]): string {
  const rows = getLatestComparisonRows(messages);
  if (!rows) {
    return "";
  }
  const systemRow = rows.find((row) => row.platform === "smqjh系统" && row.productName && row.price !== "未获取");
  return (systemRow ?? rows.find((row) => row.productName))?.productName ?? "";
}

function inferProductSpec(text: string): string {
  if (/单瓶|一瓶|1瓶/.test(text)) {
    return "单瓶";
  }
  const volume = text.match(/\d+(?:\.\d+)?\s*(?:ml|mL|ML|毫升|l|L|升)(?:\s*[xX*×]\s*\d+\s*(?:瓶|桶|罐|支|盒)?)?/);
  return volume?.[0].replace(/\s+/g, "") ?? "";
}

function extractProductKeyword(text: string): string {
  const beforeCompare = text
    .replace(/(?:和|与|跟|同|及|以及)?\s*(?:其它|其他|其余)?\s*(?:电商平台|电商|平台|淘宝|京东|拼多多|市面).*$/s, " ")
    .replace(/市面上.*$/s, " ")
    .replace(/其它?电商平台.*$/s, " ");

  const cleaned = beforeCompare
    .replace(/https?:\/\/\S+/g, " ")
    .replace(/[？?。！，,；;：:\n\r]/g, " ")
    .replace(/(?:帮我|麻烦|我要|我想|可以)?\s*(?:在|从)?\s*(?:业务系统|系统|后台|smqjh)(?:里面|里|中|的)?/gi, " ")
    .replace(/我们|咱们|我方|我司|本方|己方|我要|我想|帮我|麻烦|可以|一下|这个|某个|当前|只/g, " ")
    .replace(/(?:只)?(?:查询一下|查一下|查询|查看|查|看看)/g, " ")
    .replace(/商品描述|商品说明|商品资料|商品信息|描述|说明|资料|信息|简介|详情|是什么|还是什么|商品|产品|价格是多少|定价是多少|售价是多少|多少钱|价格|定价|售价|到手价|比价|对比一下|对比下|对比|比较一下|比较下|比较|做个|做一下/g, " ")
    .replace(/淘宝|京东|拼多多|电商平台|电商|市面|平台/g, " ")
    .replace(/的/g, " ")
    .replace(/\s+/g, " ")
    .replace(/^(?:在|从|里|里面|中)\s+/g, "")
    .replace(/^(?:和|与|跟|同|及|以及)\s*/g, "")
    .replace(/\s*(?:和|与|跟|同|及|以及)$/g, "")
    .trim();

  const genericOnly = /^(单瓶|一瓶|1瓶|单个|规格|同规格|口径|好|好的|行|可以|只)$/;
  return genericOnly.test(cleaned) ? "" : cleaned;
}

function formatProductPriceResult(result: TaskRunResult, params: Record<string, string>): string {
  if (!result.success) {
    return `商品价格对比调研未完成：${result.message}。我已把“${params.productKeyword}”填入任务参数，可以在任务页调整后重新运行。`;
  }

  const data = result.data && typeof result.data === "object" ? (result.data as Record<string, unknown>) : {};
  const rows = getComparisonRows(result.data) ?? [];
  const externalRows = rows.filter((row) => row.platform !== "smqjh系统");
  const blockedCount = externalRows.filter((row) => row.status === "未采集到").length;
  const systemMissing = rows.some((row) => row.platform === "smqjh系统" && row.price === "未获取");
  const browserResults = Array.isArray(data.browserResults) ? data.browserResults : [];
  const openedCount = browserResults.filter((item) => Boolean((item as { opened?: boolean }).opened)).length;
  const browserText = openedCount > 0 ? `另外按要求打开了 ${openedCount} 个外部平台页面。` : "未弹出外部平台页面。";
  const systemText = systemMissing ? "smqjh 系统价暂未匹配到，通常需要更精确的商品ID、SKU ID、商品编码，或确认后台商品名称。" : "已拿到 smqjh 系统价。";
  const marketText =
    blockedCount === externalRows.length && externalRows.length > 0
      ? "外部平台公开页面触发登录/风控或没有可解析价格，这不是你还需要手工查，而是需要接入平台官方接口、采购接口或授权采集账号。"
      : "外部平台已尽量后台采集，表格里会标出采集状态。";

  return [
    `已按“${params.productKeyword}”完成商品价格对比调研，${browserText}`,
    `${systemText}${marketText}`,
    "下方是表格视图，“依据”列会标出数据库只读查询范围或外部浏览器请求地址。"
  ]
    .filter(Boolean)
    .join("\n\n");
}

function isComparisonExportIntent(text: string): boolean {
  const normalized = text.toLowerCase();
  const hasExportAction = /导出|下载|保存|生成|做成|做个|做一个|整理成|转成|给我一个|给我份/.test(text);
  const hasSheetTarget = /excel|xlsx|xls|exlc|表格|电子表格|文件/.test(normalized);
  return hasExportAction && hasSheetTarget;
}

function getLatestComparisonRows(messages: ChatMessage[]): PriceComparisonRow[] | undefined {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const rows = messages[index].comparisonRows;
    if (rows?.length) {
      return rows;
    }
  }
  return undefined;
}

function getLatestMcpTables(messages: ChatMessage[]): AssistantResultTable[] | undefined {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const tables = messages[index].mcpTables?.filter((table) => table.columns.length > 0);
    if (tables?.length) {
      return tables;
    }
  }
  return undefined;
}

function buildComparisonExportTitle(rows: PriceComparisonRow[]): string {
  const systemRow = rows.find((row) => row.platform === "smqjh系统") ?? rows[0];
  return `商品价格对比_${systemRow?.productName || "导出"}`;
}

function buildTableExportTitle(tables: AssistantResultTable[]): string {
  const title = tables[0]?.title?.trim();
  return title ? `智能体查询结果_${title}` : "智能体查询结果";
}

function formatExportResult(result: PriceComparisonExportResult): string {
  return `已经按上一轮价格对比结果生成 Excel：${result.fileName}\n\n共导出 ${result.rowCount} 行数据，文件已保存到本机下载目录。`;
}

function formatTableExportResult(result: TableExportResult): string {
  return `已经把上一轮表格结果生成 Excel：${result.fileName}\n\n共导出 ${result.tableCount} 个表、${result.rowCount} 行数据，文件已保存到本机下载目录。`;
}

function NavButton(props: { active: boolean; icon: JSX.Element; label: string; onClick: () => void }) {
  return (
    <button className={props.active ? "nav-button active" : "nav-button"} type="button" aria-pressed={props.active} onClick={props.onClick}>
      {props.icon}
      <span>{props.label}</span>
    </button>
  );
}

function StatusPill({ ok, label }: { ok: boolean; label: string }) {
  return <span className={ok ? "pill ok" : "pill muted"}>{label}</span>;
}

function friendlyError(error: unknown): string {
  const raw = error instanceof Error ? error.message : String(error);
  const cleaned = raw
    .replace(/^Error invoking remote method '[^']+': Error: /, "")
    .replace(/^Error: /, "")
    .trim();
  if (cleaned === "Bad credentials") {
    return "登录失败：账号、密码或验证码错误";
  }
  return cleaned;
}

function stringifyParams(params: Record<string, unknown>): Record<string, string> {
  return Object.fromEntries(
    Object.entries(params).map(([key, value]) => [key, value === undefined || value === null ? "" : String(value)])
  );
}

function AssistantView(props: {
  messages: ChatMessage[];
  prompt: string;
  setPrompt: (value: string) => void;
  sendPrompt: (event: FormEvent) => void;
  chatBusy: boolean;
  dashboard: DashboardSnapshot;
  session: SessionStatus;
  tasks: AgentTask[];
  onPickTask: (taskId: string, presetParams?: Record<string, string>) => void;
}) {
  const reviewTasks = props.tasks.filter((task) => task.dangerLevel === "review").length;
  const conversationEndRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    conversationEndRef.current?.scrollIntoView({ block: "end", behavior: "smooth" });
  }, [props.messages, props.chatBusy]);

  return (
    <section className="assistant-grid">
      <section className="panel conversation-panel">
        <div className="conversation">
          {props.messages.map((message) => (
            <div key={message.id} className={`message ${message.role}`}>
              <div className="avatar">
                {message.role === "assistant" ? <img src={logoUrl} alt="" /> : <UserRound size={18} />}
              </div>
              <div className="bubble">
                <MessageContent text={message.text} />
                {message.comparisonRows && <PriceComparisonTable rows={message.comparisonRows} />}
                {message.orderStats && <OrderStatsTable stats={message.orderStats} />}
                {message.mcpTables && <AssistantResultTables tables={message.mcpTables} />}
                {message.attachment && <AttachmentCard attachment={message.attachment} />}
                {message.taskIds && (
                  <div className="message-actions">
                    {message.taskIds.map((taskId) => {
                      const task = props.tasks.find((item) => item.id === taskId);
                      return task ? (
                        <button key={taskId} className="secondary-button" onClick={() => props.onPickTask(taskId, message.taskParams?.[taskId])}>
                          <Play size={15} />
                          <span>{task.title}</span>
                        </button>
                      ) : null;
                    })}
                  </div>
                )}
              </div>
            </div>
          ))}
          {props.chatBusy && (
            <div className="message assistant pending">
              <div className="avatar">
                <img src={logoUrl} alt="" />
              </div>
              <div className="bubble thinking-bubble">
                <div className="thinking-row">
                  <span>正在处理</span>
                  <span className="thinking-dots" aria-hidden="true">
                    <i />
                    <i />
                    <i />
                  </span>
                </div>
              </div>
            </div>
          )}
          <div ref={conversationEndRef} />
        </div>
        <form className="composer" onSubmit={props.sendPrompt}>
          <div className="composer-input">
            <textarea
              value={props.prompt}
              placeholder="输入管理员想完成的操作，例如：帮我查这个手机号的企业会员，或者记录一个批量发券需求"
              disabled={props.chatBusy}
              onChange={(event) => props.setPrompt(event.target.value)}
              onKeyDown={(event) => {
                if (event.ctrlKey && event.key === "Enter") {
                  event.preventDefault();
                  event.currentTarget.form?.requestSubmit();
                }
              }}
            />
            <div className="composer-footer">
              <span>{props.chatBusy ? "处理中" : "就绪"}</span>
              <span>{props.prompt.length} 字</span>
            </div>
          </div>
          <button className="primary-button send-button" type="submit" title="发送" disabled={props.chatBusy || !props.prompt.trim()}>
            <Send size={16} />
            <span>{props.chatBusy ? "思考中" : "发送"}</span>
          </button>
        </form>
      </section>

      <aside className="context-panel">
        <section className="panel">
          <div className="panel-title">
            <h2>执行上下文</h2>
          </div>
          <div className="metric-stack">
            <ContextMetric icon={<Wifi size={18} />} label="网关" value={props.dashboard.config.baseUrl} />
            <ContextMetric icon={<ShieldCheck size={18} />} label="会话" value={props.session.authenticated ? "后台已登录" : "等待登录"} />
            <ContextMetric icon={<Database size={18} />} label="任务" value={`${props.tasks.length} 个`} />
            <ContextMetric icon={<AlertTriangle size={18} />} label="需复核" value={`${reviewTasks} 个`} />
          </div>
        </section>
        <section className="panel">
          <div className="panel-title">
            <h2>快捷动作</h2>
          </div>
          <div className="quick-actions">
            {props.tasks.slice(0, 6).map((task) => (
              <button key={task.id} className="quick-action" type="button" onClick={() => props.onPickTask(task.id)}>
                <span className={`danger ${task.dangerLevel}`}>{categoryNames[task.category]}</span>
                <strong>{task.title}</strong>
                <small>{task.endpoint ?? "local"}</small>
              </button>
            ))}
          </div>
        </section>
      </aside>
    </section>
  );
}

function ContextMetric({ icon, label, value }: { icon: JSX.Element; label: string; value: string }) {
  return (
    <div className="context-metric">
      {icon}
      <span>{label}</span>
      <strong>{value}</strong>
    </div>
  );
}

function DashboardView(props: { dashboard: DashboardSnapshot; session: SessionStatus; tasks: AgentTask[]; onOpenTasks: () => void }) {
  const reviewTasks = props.tasks.filter((task) => task.dangerLevel === "review").length;
  return (
    <section className="dashboard-grid">
      <div className="metric">
        <Wifi size={20} />
        <span>网关</span>
        <strong>{props.dashboard.config.baseUrl}</strong>
      </div>
      <div className="metric">
        <ShieldCheck size={20} />
        <span>会话</span>
        <strong>{props.session.authenticated ? "后台已登录" : "等待登录"}</strong>
      </div>
      <div className="metric">
        <Database size={20} />
        <span>任务</span>
        <strong>{props.tasks.length} 个</strong>
      </div>
      <div className="metric">
        <AlertTriangle size={20} />
        <span>需复核</span>
        <strong>{reviewTasks} 个</strong>
      </div>

      <section className="panel wide">
        <div className="panel-title">
          <h2>当前框架</h2>
          <button className="primary-button" onClick={props.onOpenTasks}>
            <Play size={16} />
            <span>执行任务</span>
          </button>
        </div>
        <div className="blueprint">
          <BlueprintItem title="连接层" text="OAuth 登录、Bearer Token、统一 Result 解包、超时控制" />
          <BlueprintItem title="任务层" text="任务注册表、参数表单、预演模式、执行日志" />
          <BlueprintItem title="配置层" text="网关地址、环境名、客户端配置、本地 JSON 持久化" />
          <BlueprintItem title="需求池" text="管理员诉求先记录为草稿，再补接口与审批策略" />
        </div>
      </section>

      <section className="panel">
        <div className="panel-title">
          <h2>接口线索</h2>
        </div>
        <ul className="plain-list">
          <li>/oauth2/token</li>
          <li>/api/v1/users/me</li>
          <li>/api/v1/member/export/task/start</li>
          <li>/api/v1/pointsRecharge/exportTemplate</li>
          <li>/api/v1/members/enterprise/getByMobile</li>
        </ul>
      </section>

      <section className="panel">
        <div className="panel-title">
          <h2>最近日志</h2>
        </div>
        <div className="compact-logs">
          {props.dashboard.lastLogs.length === 0 ? <span className="empty">暂无日志</span> : props.dashboard.lastLogs.map((log) => <LogLine key={log.id} log={log} />)}
        </div>
      </section>
    </section>
  );
}

function BlueprintItem({ title, text }: { title: string; text: string }) {
  return (
    <div className="blueprint-item">
      <strong>{title}</strong>
      <span>{text}</span>
    </div>
  );
}

function TasksView(props: {
  tasks: AgentTask[];
  selectedTask?: AgentTask;
  selectedTaskId: string;
  setSelectedTaskId: (taskId: string) => void;
  params: Record<string, string>;
  setParams: (params: Record<string, string>) => void;
  runTask: () => void;
  busy: boolean;
  lastResult?: TaskRunResult;
}) {
  const comparisonRows = getComparisonRows(props.lastResult?.data);

  return (
    <section className="task-layout">
      <div className="task-list">
        {props.tasks.map((task) => (
          <button
            key={task.id}
            className={task.id === props.selectedTaskId ? "task-card selected" : "task-card"}
            type="button"
            onClick={() => props.setSelectedTaskId(task.id)}
          >
            <span className={`danger ${task.dangerLevel}`}>{categoryNames[task.category]}</span>
            <strong>{task.title}</strong>
            <small>{task.endpoint ?? "local"}</small>
          </button>
        ))}
      </div>

      <div className="panel task-detail">
        {props.selectedTask && (
          <>
            <div className="panel-title">
              <div>
                <h2>{props.selectedTask.title}</h2>
                <p>{props.selectedTask.description}</p>
              </div>
              <span className={`danger ${props.selectedTask.dangerLevel}`}>{props.selectedTask.dangerLevel}</span>
            </div>
            <div className="endpoint-row">
              <code>{props.selectedTask.method ?? "LOCAL"}</code>
              <span>{props.selectedTask.endpoint ?? props.selectedTask.id}</span>
            </div>
            <div className="param-grid">
              {props.selectedTask.params.length === 0 && <span className="empty">无需参数</span>}
              {props.selectedTask.params.map((param) => (
                <label key={param.key} className="field">
                  <span>{param.label}{param.required ? " *" : ""}</span>
                  {param.type === "textarea" ? (
                    <textarea
                      value={props.params[param.key] ?? ""}
                      placeholder={param.placeholder}
                      onChange={(event) => props.setParams({ ...props.params, [param.key]: event.target.value })}
                    />
                  ) : (
                    <input
                      type={param.type === "password" ? "password" : param.type === "number" ? "number" : "text"}
                      value={props.params[param.key] ?? ""}
                      placeholder={param.placeholder}
                      onChange={(event) => props.setParams({ ...props.params, [param.key]: event.target.value })}
                    />
                  )}
                </label>
              ))}
            </div>
            <div className="action-row">
              <button className="primary-button" type="button" disabled={props.busy} onClick={props.runTask}>
                <Play size={16} />
                <span>{props.busy ? "执行中" : "运行"}</span>
              </button>
            </div>
            {props.lastResult && (
              <>
                {comparisonRows && <PriceComparisonTable rows={comparisonRows} />}
                {comparisonRows ? (
                  <details className="result-details">
                    <summary>查看原始结果</summary>
                    <pre className={props.lastResult.success ? "result success" : "result error"}>
                      {JSON.stringify(props.lastResult, null, 2)}
                    </pre>
                  </details>
                ) : (
                  <pre className={props.lastResult.success ? "result success" : "result error"}>
                    {JSON.stringify(props.lastResult, null, 2)}
                  </pre>
                )}
              </>
            )}
          </>
        )}
      </div>
    </section>
  );
}

function MessageContent({ text }: { text: string }) {
  const blocks = parseMarkdownBlocks(text);
  return (
    <>
      {blocks.map((block, index) =>
        block.type === "table" ? (
          <MarkdownTable key={`table-${index}`} headers={block.headers} rows={block.rows} />
        ) : (
          <p key={`text-${index}`}>{block.text}</p>
        )
      )}
    </>
  );
}

type MarkdownBlock =
  | { type: "text"; text: string }
  | { type: "table"; headers: string[]; rows: string[][] };

function parseMarkdownBlocks(text: string): MarkdownBlock[] {
  const lines = text.split(/\r?\n/);
  const blocks: MarkdownBlock[] = [];
  let textBuffer: string[] = [];

  function flushText() {
    const value = textBuffer.join("\n").trim();
    if (value) {
      blocks.push({ type: "text", text: value });
    }
    textBuffer = [];
  }

  for (let index = 0; index < lines.length; index += 1) {
    if (isMarkdownTableRow(lines[index]) && isMarkdownDelimiter(lines[index + 1] ?? "")) {
      flushText();
      const headers = parseMarkdownTableRow(lines[index]);
      index += 2;
      const rows: string[][] = [];
      while (index < lines.length && isMarkdownTableRow(lines[index])) {
        rows.push(parseMarkdownTableRow(lines[index]));
        index += 1;
      }
      index -= 1;
      if (headers.length && rows.length) {
        blocks.push({ type: "table", headers, rows });
      }
    } else {
      textBuffer.push(lines[index]);
    }
  }

  flushText();
  return blocks.length ? blocks : [{ type: "text", text }];
}

function isMarkdownTableRow(line: string): boolean {
  return /^\s*\|.+\|\s*$/.test(line);
}

function isMarkdownDelimiter(line: string): boolean {
  return /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(line);
}

function parseMarkdownTableRow(line: string): string[] {
  return line
    .trim()
    .replace(/^\|/, "")
    .replace(/\|$/, "")
    .split("|")
    .map((cell) => cell.trim());
}

function MarkdownTable({ headers, rows }: { headers: string[]; rows: string[][] }) {
  return (
    <div className="markdown-table-wrap">
      <table className="markdown-table">
        <thead>
          <tr>
            {headers.map((header, index) => (
              <th key={`${header}-${index}`}>{header}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((row, rowIndex) => (
            <tr key={`row-${rowIndex}`}>
              {headers.map((_, cellIndex) => (
                <td key={`cell-${rowIndex}-${cellIndex}`}>{row[cellIndex] ?? ""}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function PriceComparisonTable({ rows }: { rows: PriceComparisonRow[] }) {
  return (
    <div className="comparison-wrap">
      <table className="comparison-table">
        <thead>
          <tr>
            <th>平台</th>
            <th>商品/规格</th>
            <th>价格</th>
            <th>折算单价</th>
            <th>状态</th>
            <th>来源</th>
            <th>备注</th>
            <th>依据</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <tr key={`${row.platform}-${row.productName}-${row.price}-${row.status}`}>
              <td>{row.platform}</td>
              <td>{row.productName}</td>
              <td className="price-cell">{row.price}</td>
              <td className="unit-price-cell">{row.unitPrice || "-"}</td>
              <td>
                <span className={`table-status ${statusTone(row.status)}`}>{row.status}</span>
              </td>
              <td>{row.source}</td>
              <td>{row.note}</td>
              <td>
                <EvidenceCell evidence={row.evidence} sourceUrl={row.sourceUrl} />
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function AssistantResultTables({ tables }: { tables: AssistantResultTable[] }) {
  return (
    <>
      {tables.map((table) => (
        <div key={`${table.title}-${table.columns.join(",")}`} className="markdown-table-wrap">
          <div className="table-caption">{table.title}</div>
          <table className="markdown-table">
            <thead>
              <tr>
                {table.columns.map((column) => (
                  <th key={column}>{column}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {table.rows.length ? (
                table.rows.map((row, rowIndex) => (
                  <tr key={`mcp-row-${rowIndex}`}>
                    {table.columns.map((column) => (
                      <td key={`${rowIndex}-${column}`}>{formatQueryCell(row[column])}</td>
                    ))}
                  </tr>
                ))
              ) : (
                <tr>
                  <td colSpan={Math.max(table.columns.length, 1)}>没有查询到数据</td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      ))}
    </>
  );
}

function OrderStatsTable({ stats }: { stats: OrderStatsTableData }) {
  return (
    <div className="order-stats-wrap">
      <div className="table-caption">{stats.title}</div>
      <table className="order-stats-table">
        <thead>
          <tr>
            <th>统计项</th>
            <th>数值</th>
            <th>口径说明</th>
            <th>依据</th>
          </tr>
        </thead>
        <tbody>
          {stats.rows.map((row) => (
            <tr key={`${row.metric}-${row.value}`}>
              <td>{row.metric}</td>
              <td className="stat-value">{row.value}</td>
              <td>{row.note}</td>
              <td>{row.evidence}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function statusTone(status: string): string {
  if (/已匹配|价格线索|成功|已登录/.test(status)) {
    return "ok";
  }
  if (/未|待|失败|风控|错误/.test(status)) {
    return "warn";
  }
  return "muted";
}

function AttachmentCard({ attachment }: { attachment: ChatAttachment }) {
  return (
    <div className="attachment-card">
      <div className="attachment-main">
        <FileSpreadsheet size={20} />
        <div>
          <strong>{attachment.fileName}</strong>
          <span>{attachment.tableCount ? `${attachment.tableCount} 个表，` : ""}{attachment.rowCount} 行数据</span>
        </div>
      </div>
      <div className="attachment-actions">
        <button className="secondary-button" type="button" onClick={() => void window.smqjhAgent.openExportedFile(attachment.filePath)}>
          <FileSpreadsheet size={15} />
          <span>打开</span>
        </button>
        <button className="secondary-button" type="button" onClick={() => void window.smqjhAgent.showExportedFile(attachment.filePath)}>
          <FolderOpen size={15} />
          <span>定位</span>
        </button>
      </div>
    </div>
  );
}

function EvidenceCell({ evidence, sourceUrl }: { evidence?: string; sourceUrl?: string }) {
  const lines = (evidence || sourceUrl || "无").split(/\r?\n/).filter(Boolean);
  return (
    <div className="evidence-cell">
      {lines.map((line) => {
        const url = line.match(/https?:\/\/\S+/)?.[0];
        const label = url ? evidenceLinkLabel(line, url) : "";
        return (
          <div key={line} className="evidence-item">
            {url ? (
              <>
                <a className="evidence-link" href={url} target="_blank" rel="noreferrer" title={url}>
                  {label}
                </a>
                <details className="evidence-detail">
                  <summary>查看地址</summary>
                  <code>{safeDecodeUrl(url)}</code>
                </details>
              </>
            ) : (
              <span className="evidence-text">{line}</span>
            )}
          </div>
        );
      })}
    </div>
  );
}

function evidenceLinkLabel(line: string, url: string): string {
  const prefix = line.replace(url, "").replace(/[：:\s]+$/g, "").trim();
  const host = (() => {
    try {
      return new URL(url).hostname.replace(/^www\./, "");
    } catch {
      return "请求地址";
    }
  })();
  const action = prefix.includes("直连") ? "直连请求" : prefix.includes("搜索") ? "搜索请求" : prefix || "打开依据";
  return `${action} · ${host}`;
}

function safeDecodeUrl(url: string): string {
  try {
    return decodeURI(url);
  } catch {
    return url;
  }
}

function getComparisonRows(data: unknown): PriceComparisonRow[] | undefined {
  if (!data || typeof data !== "object" || !("comparisonRows" in data)) {
    return undefined;
  }

  const rows = (data as { comparisonRows?: unknown }).comparisonRows;
  if (!Array.isArray(rows) || rows.length === 0) {
    return undefined;
  }

  return rows
    .filter((row): row is Record<string, unknown> => Boolean(row) && typeof row === "object")
    .map((row) => ({
      platform: String(row.platform ?? ""),
      productName: String(row.productName ?? ""),
      price: String(row.price ?? ""),
      unitPrice: row.unitPrice ? String(row.unitPrice) : undefined,
      status: String(row.status ?? ""),
      source: String(row.source ?? ""),
      note: String(row.note ?? ""),
      sourceUrl: row.sourceUrl ? String(row.sourceUrl) : undefined,
      evidence: row.evidence ? String(row.evidence) : undefined
    }));
}

function SettingsView(props: {
  config: AppConfig;
  setConfig: (config: AppConfig) => void;
  saveConfig: (config: AppConfig) => Promise<void>;
  login: (request: { username: string; password: string; tenantCode: string; captchaId: string; captchaCode: string }) => Promise<void>;
  logout: () => Promise<void>;
  busy: boolean;
  session: SessionStatus;
  feedback?: Feedback;
  focusTick: number;
}) {
  const draft = props.config;
  const setDraft = props.setConfig;
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [tenantCode, setTenantCode] = useState(props.config.tenantCode || "zswl");
  const [captchaCode, setCaptchaCode] = useState("");
  const [captcha, setCaptcha] = useState<CaptchaResult>();
  const [captchaError, setCaptchaError] = useState("");
  const [mcpSnapshot, setMcpSnapshot] = useState<McpRuntimeConfigSnapshot>();
  const [mcpError, setMcpError] = useState("");
  const loginPanelRef = useRef<HTMLFormElement>(null);
  const usernameInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => setTenantCode(props.config.tenantCode || "zswl"), [props.config.tenantCode]);
  useEffect(() => {
    void refreshCaptcha();
    void refreshMcpSnapshot();
  }, []);
  useEffect(() => {
    if (props.focusTick === 0) {
      return;
    }
    loginPanelRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
    window.setTimeout(() => usernameInputRef.current?.focus(), 120);
  }, [props.focusTick]);

  function submitLogin(event: FormEvent) {
    event.preventDefault();
    if (!captcha?.captchaId) {
      setCaptchaError("请先刷新验证码");
      return;
    }
    if (!captchaCode.trim()) {
      setCaptchaError("请输入验证码");
      return;
    }
    setCaptchaError("");
    void props.login({
      username,
      password,
      tenantCode,
      captchaId: captcha.captchaId,
      captchaCode: captchaCode.trim()
    }).finally(() => {
      setCaptchaCode("");
      void refreshCaptcha();
    });
  }

  async function refreshCaptcha() {
    setCaptchaError("");
    try {
      const nextCaptcha = await window.smqjhAgent.getCaptcha();
      setCaptcha(nextCaptcha);
    } catch (error) {
      const message = friendlyError(error);
      setCaptchaError(message || "验证码获取失败");
    }
  }

  async function refreshMcpSnapshot() {
    setMcpError("");
    try {
      setMcpSnapshot(await window.smqjhAgent.getMcpConfig());
    } catch (error) {
      const message = friendlyError(error);
      setMcpError(message || "MCP 配置状态读取失败");
      setMcpSnapshot(undefined);
    }
  }

  return (
    <section className="settings-grid">
      <form className="panel form-panel" onSubmit={(event) => {
        event.preventDefault();
        void props.saveConfig(draft);
      }}>
        <div className="panel-title">
          <h2>连接配置</h2>
          <button className="primary-button" type="submit">
            <Save size={16} />
            <span>保存</span>
          </button>
        </div>
        <label className="field">
          <span>环境名</span>
          <input value={draft.environmentName} onChange={(event) => setDraft({ ...draft, environmentName: event.target.value })} />
        </label>
        <label className="field">
          <span>网关地址</span>
          <input value={draft.baseUrl} onChange={(event) => setDraft({ ...draft, baseUrl: event.target.value })} />
        </label>
        <label className="field">
          <span>Token 路径</span>
          <input value={draft.auth.tokenPath} onChange={(event) => setDraft({ ...draft, auth: { ...draft.auth, tokenPath: event.target.value } })} />
        </label>
        <div className="two-col">
          <label className="field">
            <span>Client ID</span>
            <input value={draft.auth.clientId} onChange={(event) => setDraft({ ...draft, auth: { ...draft.auth, clientId: event.target.value } })} />
          </label>
          <label className="field">
            <span>Client Secret</span>
            <input value={draft.auth.clientSecret} onChange={(event) => setDraft({ ...draft, auth: { ...draft.auth, clientSecret: event.target.value } })} />
          </label>
        </div>
        <div className="two-col">
          <label className="field">
            <span>默认租户编码</span>
            <input value={draft.tenantCode} onChange={(event) => setDraft({ ...draft, tenantCode: event.target.value })} />
          </label>
          <label className="field">
            <span>租户域</span>
            <input value={draft.tenantDomain} onChange={(event) => setDraft({ ...draft, tenantDomain: event.target.value })} />
          </label>
        </div>
        <div className="two-col">
          <label className="field">
            <span>超时 ms</span>
            <input type="number" value={draft.requestTimeoutMs} onChange={(event) => setDraft({ ...draft, requestTimeoutMs: Number(event.target.value) })} />
          </label>
          <label className="toggle">
            <input type="checkbox" checked={draft.dryRun} onChange={(event) => setDraft({ ...draft, dryRun: event.target.checked })} />
            <span>预演模式</span>
          </label>
        </div>
        <div className="subsection-title">MCP 服务</div>
        <p className="scope-note">数据库连接、只读查询和业务工具由 MCP 托管，桌面端只保存 MCP 连接地址；敏感信息不在前端配置页展示。</p>
        <label className="toggle compact-toggle">
          <input
            type="checkbox"
            checked={draft.mcp.enabled}
            onChange={(event) => setDraft({ ...draft, mcp: { ...draft.mcp, enabled: event.target.checked } })}
          />
          <span>启用 MCP 工具调用</span>
        </label>
        <div className="two-col">
          <label className="field">
            <span>MCP 地址</span>
            <input value={draft.mcp.host} onChange={(event) => setDraft({ ...draft, mcp: { ...draft.mcp, host: event.target.value } })} />
          </label>
          <label className="field">
            <span>端口</span>
            <input type="number" value={draft.mcp.port} onChange={(event) => setDraft({ ...draft, mcp: { ...draft.mcp, port: Number(event.target.value) } })} />
          </label>
        </div>
        <label className="field">
          <span>MCP 路径</span>
          <input value={draft.mcp.path} onChange={(event) => setDraft({ ...draft, mcp: { ...draft.mcp, path: event.target.value } })} />
        </label>
        <label className="field">
          <span>MCP Token</span>
          <input
            type="password"
            value={draft.mcp.authToken}
            autoComplete="off"
            placeholder="未配置时本机直连"
            onChange={(event) => setDraft({ ...draft, mcp: { ...draft.mcp, authToken: event.target.value } })}
            onBlur={() => void props.saveConfig(draft)}
          />
        </label>
        <div className="mcp-status-card">
          <div className="mcp-status-header">
            <strong>MCP 托管配置状态</strong>
            <button className="secondary-button" type="button" onClick={() => void refreshMcpSnapshot()}>
              <RefreshCcw size={15} />
              <span>刷新</span>
            </button>
          </div>
          {mcpError ? <div className="feedback error">{mcpError}</div> : <McpConfigSummary snapshot={mcpSnapshot} />}
        </div>
        <div className="subsection-title">DeepSeek 智能体</div>
        <p className="scope-note">DeepSeek 已迁移到 MCP 侧托管。桌面端不再保存或展示 API Key，模型和推理强度请在 MCP 本地配置或环境变量中维护。</p>
      </form>

      <form className="panel form-panel" ref={loginPanelRef} onSubmit={submitLogin}>
        <div className="panel-title">
          <h2>后台登录</h2>
          <StatusPill ok={props.session.authenticated} label={props.session.authenticated ? "已登录" : "未登录"} />
        </div>
        {props.session.authenticated ? (
          <div className="logged-in-actions">
            <button className="secondary-button" type="button" onClick={() => void props.logout()}>
              <UserRound size={16} />
              <span>退出登录</span>
            </button>
          </div>
        ) : (
          <>
            {props.feedback && <div className={`feedback ${props.feedback.type}`}>{props.feedback.text}</div>}
            {captchaError && <div className="feedback error">{captchaError}</div>}
            <label className="field">
              <span>租户编码</span>
              <input value={tenantCode} onChange={(event) => setTenantCode(event.target.value)} />
            </label>
            <label className="field">
              <span>账号</span>
              <input ref={usernameInputRef} value={username} onChange={(event) => setUsername(event.target.value)} autoComplete="username" />
            </label>
            <label className="field">
              <span>密码</span>
              <input type="password" value={password} onChange={(event) => setPassword(event.target.value)} autoComplete="current-password" />
            </label>
            <label className="field">
              <span>验证码</span>
              <div className="captcha-row">
                <input value={captchaCode} onChange={(event) => setCaptchaCode(event.target.value)} />
                <button className="captcha-button" type="button" onClick={() => void refreshCaptcha()} title="刷新验证码">
                  {captcha?.captchaBase64 ? <img src={captcha.captchaBase64} alt="验证码" /> : "刷新"}
                </button>
              </div>
            </label>
            <div className="action-row">
              <button className="primary-button" disabled={props.busy} type="submit">
                <KeyRound size={16} />
                <span>{props.busy ? "登录中" : "登录"}</span>
              </button>
            </div>
          </>
        )}
      </form>
    </section>
  );
}

function McpConfigSummary({ snapshot }: { snapshot?: McpRuntimeConfigSnapshot }) {
  if (!snapshot) {
    return <div className="mcp-empty">尚未读取 MCP 配置状态</div>;
  }

  const db = snapshot.database ?? {};
  const ai = snapshot.deepSeek ?? {};
  const mcp = snapshot.mcp ?? {};

  return (
    <div className="mcp-summary-grid">
      <ConfigSummaryItem label="运行环境" value={stringValue(snapshot.environmentName) || "-"} />
      <ConfigSummaryItem label="网关" value={stringValue(snapshot.baseUrl) || "-"} />
      <ConfigSummaryItem label="MCP 地址" value={`${stringValue(mcp.host) || "127.0.0.1"}:${stringValue(mcp.port) || "8765"}${stringValue(mcp.path) || "/mcp"}`} />
      <ConfigSummaryItem label="MCP Token" value={truthyValue(mcp.tokenConfigured) ? "已配置" : "未配置"} />
      <ConfigSummaryItem label="数据库" value={truthyValue(db.enabled) ? "已启用" : "未启用"} tone={truthyValue(db.enabled) ? "ok" : "warn"} />
      <ConfigSummaryItem label="数据库名" value={stringValue(db.database) || "-"} />
      <ConfigSummaryItem label="数据库地址" value={`${stringValue(db.host) || "-"}:${stringValue(db.port) || "3306"}`} />
      <ConfigSummaryItem label="数据库账号" value={stringValue(db.username) || "-"} />
      <ConfigSummaryItem label="DeepSeek" value={truthyValue(ai.enabled) ? "已启用" : "未启用"} tone={truthyValue(ai.enabled) ? "ok" : "warn"} />
      <ConfigSummaryItem label="模型" value={stringValue(ai.model) || "-"} />
      <ConfigSummaryItem label="API Key" value={truthyValue(ai.apiKeyConfigured) ? "已配置" : "未配置"} tone={truthyValue(ai.apiKeyConfigured) ? "ok" : "warn"} />
      <ConfigSummaryItem label="配置来源" value={stringValue(snapshot.desktopConfigPath) || "MCP 本地配置"} wide />
    </div>
  );
}

function ConfigSummaryItem({ label, value, tone = "muted", wide = false }: { label: string; value: string; tone?: "ok" | "warn" | "muted"; wide?: boolean }) {
  return (
    <div className={`mcp-summary-item ${wide ? "wide" : ""}`}>
      <span>{label}</span>
      <strong className={tone}>{value}</strong>
    </div>
  );
}

function stringValue(value: unknown): string {
  return value === undefined || value === null ? "" : String(value);
}

function truthyValue(value: unknown): boolean {
  return value === true || value === "true" || value === "1" || value === 1;
}

function LogsView({ logs, clear }: { logs: LogEntry[]; clear: () => Promise<void> }) {
  return (
    <section className="panel logs-panel">
      <div className="panel-title">
        <h2>日志</h2>
        <button className="secondary-button" onClick={() => void clear()}>
          <Trash2 size={16} />
          <span>清空</span>
        </button>
      </div>
      <div className="logs-list">
        {logs.length === 0 ? <span className="empty">暂无日志</span> : logs.map((log) => <LogLine key={log.id} log={log} />)}
      </div>
    </section>
  );
}

function LogLine({ log }: { log: LogEntry }) {
  return (
    <div className={`log-line ${log.level}`}>
      <time>{new Date(log.timestamp).toLocaleString()}</time>
      <span>{log.level}</span>
      <strong>{log.message}</strong>
    </div>
  );
}
