/* ============================================================
   AI PAGE — Luxsin X9 AI Assistant
   - 首次进入: 中央 Logo + 建议气泡，引导用户如何提问
   - 对话中: 消息流式展示（用户气泡 / 助手气泡）
   - 底部: 输入框 + 发送按钮
   - 顶部: 标题 + 历史/新建/清除按钮
   Backend: /sse/question (SSE), /sse/messages, /sse/chats, /sse/clear
   ============================================================ */
import BottomNav from "@/components/BottomNav";
import ConnectScreen from "@/components/ConnectScreen";
import { useDevice } from "@/contexts/DeviceContext";
import {
  ChatRead,
  MessageRead,
  OptimizePeqPayload,
  ToolUseBlock,
  clearChat,
  executeFrontendTool,
  listChats,
  listMessages,
  sendToolResult,
  streamQuestion,
  updateMessageApplied,
} from "@/lib/aiApi";

import { buildPeqSvgCurveData, getFilterType, PeqBandForResponse } from "@/lib/peqAudio";
import { cn } from "@/lib/utils";
import {
  ArrowUp,
  History,
  MessageSquarePlus,
  Sparkles,
  Sliders,
  Volume2,
  Settings2,
  Wand2,
  X,
  Bot,
  Wrench,
  AlertCircle,
  GitCompare,
  Check,
  RotateCcw,
  Loader2,
  Trash2,
  Maximize2,
  ChevronsRight,
} from "lucide-react";
import { useLocation } from "wouter";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Streamdown } from "streamdown";
import { toast } from "sonner";
import localeZh from "@/locales/data-zh.json";
import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";

type AILocale = NonNullable<(typeof localeZh)["ai"]>;

function resolveAILocale(language: number | undefined): AILocale {
  const loc =
    language === 0 ? localeEn : language === 1 ? localeZhHK : localeZh;
  return loc.ai as AILocale;
}

function formatText(template: string, vars: Record<string, string | number>): string {
  return template.replace(/\{(\w+)\}/g, (_, k) => {
    const v = vars[k];
    return v === undefined || v === null ? "" : String(v);
  });
}

type ChatUIMessage = {
  kind: "chat";
  id: string;
  role: "user" | "assistant";
  text: string;
  pending?: boolean;
  error?: boolean;
  tools?: Array<{ id: string; name: string; ok?: boolean }>;
};

type OptimizeUIMessage = {
  kind: "optimize";
  id: string;
  name: string;
  beforePeq: OptimizePeqPayload;
  afterPeq: OptimizePeqPayload;
  applied: boolean;
  appliedAt?: string | null;
};

type UIMessage = ChatUIMessage | OptimizeUIMessage;

interface Suggestion {
  icon: React.ReactNode;
  title: string;
  prompt: string;
}

// 建议气泡图标固定顺序（文案由 i18n 提供）
const SUGGESTION_ICONS: React.ReactNode[] = [
  <Wand2 key="wand" size={14} />,
  <Sliders key="sliders" size={14} />,
  <Volume2 key="vol" size={14} />,
  <Settings2 key="set" size={14} />,
];

// 将后端 DB 存的 content（可能是 JSON 文本）转为纯文本展示
function extractText(raw: string): { text: string; tools: Array<{ id: string; name: string }> } {
  const trimmed = raw?.trim?.() ?? "";
  if (!trimmed) return { text: "", tools: [] };
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
    try {
      const arr = JSON.parse(trimmed);
      if (Array.isArray(arr)) {
        const textParts: string[] = [];
        const tools: Array<{ id: string; name: string }> = [];
        for (const block of arr) {
          if (block?.type === "text" && typeof block.text === "string") {
            textParts.push(block.text);
          } else if (block?.type === "tool_use") {
            tools.push({ id: block.id, name: block.name });
          } else if (block?.type === "tool_result") {
            // 用户侧的工具结果不直接展示
          }
        }
        return { text: textParts.join("\n").trim(), tools };
      }
    } catch {
      // fall through
    }
  }
  return { text: raw, tools: [] };
}

function makeId() {
  return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}

export type AIPageVariant = "page" | "split";

export interface AIPageProps {
  variant?: AIPageVariant;
  /** Hide side panel (desktop split) */
  onClose?: () => void;
}

export default function AIPage({ variant = "page", onClose }: AIPageProps = {}) {
  const isEmbedded = variant === "split";
  const [, setLocation] = useLocation();
  const { isConnected, deviceState, api } = useDevice();
  const mac = deviceState?.mac ?? "";
  const language = deviceState?.language ?? 2;
  const deviceName = deviceState?.device ?? "Luxsin X9";

  const aiText = useMemo(() => resolveAILocale(language), [language]);

  const [chatId, setChatId] = useState<string | null>(null);
  const [messages, setMessages] = useState<UIMessage[]>([]);
  const [input, setInput] = useState("");
  const [sending, setSending] = useState(false);
  const [historyOpen, setHistoryOpen] = useState(false);
  const [chats, setChats] = useState<ChatRead[]>([]);
  // 初次加载历史会话期间，避免先闪一下欢迎页再切到消息
  const [initializing, setInitializing] = useState(true);

  const scrollRef = useRef<HTMLDivElement | null>(null);
  const abortRef = useRef<AbortController | null>(null);
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
  const initialLoadRef = useRef(false);

  const isEmpty = messages.length === 0;

  // ── 自动滚动到底部 ──
  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    el.scrollTop = el.scrollHeight;
  }, [messages, sending]);

  // ── 组件卸载时取消请求 ──
  useEffect(() => {
    return () => abortRef.current?.abort();
  }, []);

  // ── 打开历史会话 ──
  const openHistory = useCallback(async () => {
    if (!mac) {
      toast.error(aiText.deviceRequired);
      return;
    }
    setHistoryOpen(true);
    try {
      const list = await listChats(mac);
      setChats(list);
    } catch (e: any) {
      toast.error(formatText(aiText.loadHistoryFailed, { message: e?.message ?? e }));
    }
  }, [aiText, mac]);

  // ── 把后端消息行映射为 UI 消息 ──
  // 只要助手消息带有 before_peq + after_peq，就展示为 AB 对比卡片；
  // 其余按 type=0 聊天消息处理；用户侧的 type=2 仍然只是内部 prompt，跳过。
  const mapRowsToUI = useCallback((rows: MessageRead[]): UIMessage[] => {
    const result: UIMessage[] = [];
    for (const r of rows) {
      if (r.role === "user" && r.before_peq && r.after_peq) {
        result.push({
          kind: "optimize",
          id: r.id,
          name: r.after_peq.name ?? r.before_peq.name ?? "Optimized EQ",
          beforePeq: r.before_peq,
          afterPeq: r.after_peq,
          applied: !!r.applied,
          appliedAt: r.applied_at ?? null,
        });
      } else if (r.type === 0) {
        const { text, tools } = extractText(r.content);
        if (!text && tools.length === 0) continue;
        result.push({
          kind: "chat",
          id: r.id,
          role: r.role,
          text,
          tools: tools.map((t) => ({ ...t, ok: true })),
        });
      }
    }
    return result;
  }, []);

  // ── 加载指定会话的消息 ──
  const loadChat = useCallback(
    async (id: string, opts?: { closePanel?: boolean }) => {
      try {
        const rows = await listMessages(id);
        setMessages(mapRowsToUI(rows));
        setChatId(id);
        if (opts?.closePanel !== false) setHistoryOpen(false);
      } catch (e: any) {
        toast.error(formatText(aiText.loadChatFailed, { message: e?.message ?? e }));
      }
    },
    [aiText, mapRowsToUI],
  );

  // ── 进入页面时若有历史会话，自动打开最近一次 ──
  useEffect(() => {
    if (initialLoadRef.current) return;
    if (!mac) return;
    initialLoadRef.current = true;
    (async () => {
      try {
        const list = await listChats(mac);
        setChats(list);
        if (list.length === 0) return;
        const newest = list.reduce((a, b) =>
          new Date(a.updated_at) > new Date(b.updated_at) ? a : b,
        );
        await loadChat(newest.id, { closePanel: false });
      } catch {
        // 静默失败：保留欢迎页
      } finally {
        setInitializing(false);
      }
    })();
  }, [mac, loadChat]);

  // 没有 mac 时（设备未识别完毕）不应该停在 initializing；ConnectScreen 会拦截
  useEffect(() => {
    if (!mac && initializing) {
      // 给 mac 一段缓冲期，否则即便最终没有 mac 也要释放欢迎页
      const t = setTimeout(() => setInitializing(false), 800);
      return () => clearTimeout(t);
    }
  }, [mac, initializing]);

  // ── 静默刷新当前会话（流式结束后用于拉取新增的 type=2 优化记录） ──
  const refreshCurrentChat = useCallback(
    async (id: string) => {
      try {
        const rows = await listMessages(id);
        setMessages(mapRowsToUI(rows));
      } catch {
        // 静默失败，不打扰用户
      }
    },
    [mapRowsToUI],
  );

  // ── 新建会话 ──
  const newChat = useCallback(() => {
    abortRef.current?.abort();
    setMessages([]);
    setChatId(null);
    setInput("");
    setHistoryOpen(false);
  }, []);

  // ── 清空当前会话 ──
  const clearCurrent = useCallback(async () => {
    if (!chatId) {
      newChat();
      return;
    }
    try {
      await clearChat(chatId);
      setChats((prev) => prev.filter((c) => c.id !== chatId));
      newChat();
      toast.success(aiText.cleared);
    } catch (e: any) {
      toast.error(formatText(aiText.clearFailed, { message: e?.message ?? e }));
    }
  }, [aiText, chatId, newChat]);

  // ── 历史面板里删除某条会话 ──
  const deleteChatById = useCallback(
    async (id: string) => {
      const ok = window.confirm(aiText.history.deleteConfirm);
      if (!ok) return;
      try {
        await clearChat(id);
        setChats((prev) => prev.filter((c) => c.id !== id));
        if (chatId === id) {
          setChatId(null);
          setMessages([]);
        }
        toast.success(aiText.history.deleted);
      } catch (e: any) {
        toast.error(
          formatText(aiText.history.deleteFailed, { message: e?.message ?? e }),
        );
      }
    },
    [aiText, chatId],
  );

  // ── 发送提问 ──
  const send = useCallback(
    (question: string) => {
      const trimmed = question.trim();
      if (!trimmed || sending) return;
      if (!mac || !deviceState || !api) {
        toast.error(aiText.deviceRequired);
        return;
      }

      const userMsg: ChatUIMessage = {
        kind: "chat",
        id: makeId(),
        role: "user",
        text: trimmed,
      };
      const assistantMsg: ChatUIMessage = {
        kind: "chat",
        id: makeId(),
        role: "assistant",
        text: "",
        pending: true,
        tools: [],
      };

      setMessages((prev) => [...prev, userMsg, assistantMsg]);
      setInput("");
      setSending(true);

      const assistantId = assistantMsg.id;

      const updateAssistant = (updater: (m: ChatUIMessage) => ChatUIMessage) => {
        setMessages((prev) =>
          prev.map((m) => (m.id === assistantId && m.kind === "chat" ? updater(m) : m)),
        );
      };

      // 工具执行后续调用时需要保留当前 chat_id，先捕获一下
      let currentChatId = chatId;

      // 组装新版 /sse/question 请求体
      const buildAndStream = async () => {
        let devicePeq: any;
        try {
          const peqState = await api.getPeqState();
          devicePeq = {
            peqSelect: peqState.peqSelect ?? deviceState.peqSelect ?? 0,
            peqEnable: deviceState.peqEnable ?? 1,
            peq: (peqState.peq ?? []).map((p) => ({
              name: p.name,
              brand: p.brand,
              model: p.model,
              filters: p.filters ?? [],
              preamp: p.preamp,
              canDel: p.canDel,
              autoPre: p.autoPre,
            })),
            msgCount: deviceState.msgCount ?? 0,
          };
        } catch (e: any) {
          updateAssistant((m) => ({
            ...m,
            pending: false,
            error: true,
            text: aiText.requestFailed,
          }));
          setSending(false);
          toast.error(formatText(aiText.requestFailed, { message: e?.message ?? e }));
          return;
        }

        const controller = streamQuestion(
        {
          question: trimmed,
          device_setting: { ...deviceState },
          device_peq: devicePeq,
          chat_id: currentChatId,
        },
        {
          onText: (t) => {
            updateAssistant((m) => ({
              ...m,
              text: (m.text ?? "") + t,
              pending: false,
            }));
          },
          onToolUse: async (block: ToolUseBlock) => {
            updateAssistant((m) => ({
              ...m,
              pending: false,
              tools: [...(m.tools ?? []), { id: block.id, name: block.name }],
            }));
            const result = await executeFrontendTool(block, api);
            updateAssistant((m) => ({
              ...m,
              tools: (m.tools ?? []).map((t) =>
                t.id === block.id ? { ...t, ok: result.ok } : t,
              ),
            }));
            try {
              await sendToolResult(block.id, result);
            } catch (e: any) {
              toast.error(formatText(aiText.toolResultFailed, { message: e?.message ?? e }));
            }
          },
          onError: (msg) => {
            updateAssistant((m) => ({
              ...m,
              pending: false,
              error: true,
              text: m.text || msg || aiText.requestFailed,
            }));
            setSending(false);
          },
          onDone: () => {
            setSending(false);
            // 如果是新建会话，后端首次返回时会创建 chat_id；这里拉一次会话列表补上 id
            const resolveAndRefresh = async () => {
              let id = currentChatId;
              if (!id && mac) {
                try {
                  const list = await listChats(mac);
                  if (list.length > 0) {
                    const newest = list.reduce((a, b) =>
                      new Date(a.updated_at) > new Date(b.updated_at) ? a : b,
                    );
                    id = newest.id;
                    currentChatId = id;
                    setChatId(id);
                  }
                } catch {
                  return;
                }
              }
              if (id) await refreshCurrentChat(id);
            };
            void resolveAndRefresh();
          },
        },
      );
        abortRef.current = controller;
      };
      void buildAndStream();
    },
    [aiText, api, chatId, deviceState, mac, refreshCurrentChat, sending],
  );

  const stop = useCallback(() => {
    abortRef.current?.abort();
    setSending(false);
    setMessages((prev) =>
      prev.map((m) =>
        m.kind === "chat" && m.pending
          ? { ...m, pending: false, text: m.text || aiText.stopped }
          : m,
      ),
    );
  }, [aiText]);

  const onSubmit = useCallback(
    (e: React.FormEvent) => {
      e.preventDefault();
      send(input);
    },
    [input, send],
  );

  const onKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
      if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
        e.preventDefault();
        send(input);
      }
    },
    [input, send],
  );

  const onInputChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setInput(e.target.value);
    const el = e.target;
    el.style.height = "auto";
    el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
  }, []);

  // ── 优化记录：对比对话框 & 应用/回滚 ──
  const [compareMsg, setCompareMsg] = useState<OptimizeUIMessage | null>(null);
  const [applyingId, setApplyingId] = useState<string | null>(null);
  const [currentDevicePeq, setCurrentDevicePeq] = useState<OptimizePeqPayload | null>(null);

  // 拉取当前设备 PEQ，作为"当前"参考曲线与"应用/回滚"后的对比基准
  const refreshCurrentDevicePeq = useCallback(async () => {
    if (!api) return;
    try {
      const peqState = await api.getPeqState();
      const selectedIdx = peqState.peqSelect ?? 0;
      const selected = peqState.peq?.[selectedIdx];
      const selectedFilters = (() => {
        const f = selected?.filters;
        if (!f) return undefined;
        if (typeof f === "string") {
          try {
            return JSON.parse(f);
          } catch {
            return undefined;
          }
        }
        return f;
      })();
      setCurrentDevicePeq({
        name: selected?.name,
        preamp: selected?.preamp,
        canDel: selected?.canDel,
        autoPre: selected?.autoPre,
        brand: selected?.brand,
        model: selected?.model,
        filters: (selectedFilters ?? peqState.filters ?? []) as OptimizePeqPayload["filters"],
      });
    } catch {
      // 静默失败，保留上一次的值
    }
  }, [api]);

  useEffect(() => {
    if (isConnected) void refreshCurrentDevicePeq();
  }, [isConnected, refreshCurrentDevicePeq]);

  const handleApplyToggle = useCallback(
    async (msg: OptimizeUIMessage) => {
      if (!api) {
        toast.error(aiText.deviceDisconnected);
        return;
      }
      const nextApplied = !msg.applied;
      const targetPeq = nextApplied ? msg.afterPeq : msg.beforePeq;
      setApplyingId(msg.id);
      try {
        await api.upgradePeqChange({
          peqChange: {
            name: targetPeq.name ?? msg.name,
            filters: (targetPeq.filters ?? []).map((f) => ({
              type: getFilterType(f.type),
              fc: (f.fc ?? f.frequency ?? 1000) as number,
              gain: f.gain,
              q: f.q,
            })),
            autoPre: targetPeq.autoPre,
            preamp: targetPeq.preamp,
            canDel: targetPeq.canDel,
          },
        });
        await updateMessageApplied(msg.id, nextApplied);
        setMessages((prev) =>
          prev.map((m) =>
            m.kind === "optimize" && m.id === msg.id
              ? { ...m, applied: nextApplied, appliedAt: new Date().toISOString() }
              : m,
          ),
        );
        setCompareMsg((cur) =>
          cur && cur.id === msg.id ? { ...cur, applied: nextApplied } : cur,
        );
        void refreshCurrentDevicePeq();
        toast.success(
          nextApplied ? aiText.optimize.applySuccess : aiText.optimize.rollbackSuccess,
        );
      } catch (e: any) {
        toast.error(formatText(aiText.optimize.applyFailed, { message: e?.message ?? e }));
      } finally {
        setApplyingId(null);
      }
    },
    [aiText, api, refreshCurrentDevicePeq],
  );

  const openCompare = useCallback(
    (m: OptimizeUIMessage) => {
      setCompareMsg(m);
      void refreshCurrentDevicePeq();
    },
    [refreshCurrentDevicePeq],
  );

  const headerTitle = aiText.title;

  if (!isConnected) return <ConnectScreen />;

  return (
    <div
      className={cn(
        "flex flex-col overflow-hidden bg-black",
        /* Same clearance as Layout/main above fixed BottomNav */
        "pb-[calc(4rem+env(safe-area-inset-bottom,0px))]",
        isEmbedded ? "h-full min-h-0" : "h-[100dvh]",
      )}
    >
      {/* ── Header ── */}
      <div className="page-header flex-shrink-0">
        <div className="flex items-center gap-2 mr-auto">
          {isEmbedded && onClose && (
            <>
              <button
                type="button"
                onClick={onClose}
                className="mr-0.5 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-white/70 transition-colors active:text-white"
                style={{ background: "rgba(44,44,46,0.6)" }}
                aria-label={aiText.ariaHidePanel}
              >
                <ChevronsRight size={18} />
              </button>
              <button
                type="button"
                onClick={() => {
                  setLocation("/ai");
                  onClose();
                }}
                className="mr-1 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-white/70 transition-colors active:text-white"
                style={{ background: "rgba(44,44,46,0.6)" }}
                aria-label={aiText.ariaFullPage}
              >
                <Maximize2 size={16} />
              </button>
            </>
          )}
          <div
            className="w-7 h-7 rounded-full flex items-center justify-center"
            style={{
              background: "rgba(0,255,246,0.12)",
              border: "1px solid rgba(0,255,246,0.3)",
              boxShadow: "0 0 10px rgba(0,255,246,0.25)",
            }}
          >
            {/* <Sparkles size={14} className="text-[#00FFF6]" /> */}
            <Bot size={14} className="text-[#00FFF6]" />
          </div>
          <span className="text-[17px] font-semibold text-white">{headerTitle}</span>
        </div>
        <button
          onClick={openHistory}
          className="w-9 h-9 rounded-full flex items-center justify-center text-white/60 active:text-white transition-colors mr-2"
          style={{ background: "rgba(44,44,46,0.6)" }}
          aria-label={aiText.ariaHistory}
        >
          <History size={16} />
        </button>
        <button
          onClick={newChat}
          className="w-9 h-9 rounded-full flex items-center justify-center text-white/60 active:text-white transition-colors"
          style={{ background: "rgba(44,44,46,0.6)" }}
          aria-label={aiText.ariaNewChat}
        >
          <MessageSquarePlus size={16} />
        </button>
      </div>

      {/* ── Messages / Welcome ── */}
      <div ref={scrollRef} className="flex-1 min-h-0 overflow-y-auto px-4">
        {initializing ? (
          <div className="h-full flex items-center justify-center">
            <Loader2 size={20} className="animate-spin text-white/30" />
          </div>
        ) : isEmpty ? (
          <WelcomeView
            aiText={aiText}
            deviceName={deviceName}
            onPick={(p) => {
              setInput(p);
              setTimeout(() => textareaRef.current?.focus(), 0);
            }}
          />
        ) : (
          <div className="pt-3 space-y-3 max-w-full">
            {messages.map((m) =>
              m.kind === "chat" ? (
                <MessageBubble key={m.id} aiText={aiText} msg={m} />
              ) : (
                <OptimizeCard
                  key={m.id}
                  aiText={aiText}
                  msg={m}
                  currentPeq={currentDevicePeq}
                  applying={applyingId === m.id}
                  onCompare={() => openCompare(m)}
                  onApplyToggle={() => handleApplyToggle(m)}
                />
              ),
            )}
            {sending &&
              (() => {
                const last = messages[messages.length - 1];
                return last && last.kind === "chat" && last.pending ? (
                  <TypingIndicator />
                ) : null;
              })()}
          </div>
        )}
      </div>

      {/* ── Input bar ── */}
      <div
        className="flex-shrink-0 px-3 pt-3 pb-3"
        style={{
          background: "rgba(10,10,12,0.96)",
          borderTop: "1px solid rgba(255,255,255,0.06)",
          backdropFilter: "blur(20px) saturate(180%)",
          WebkitBackdropFilter: "blur(20px) saturate(180%)",
        }}
      >
        <form
          onSubmit={onSubmit}
          className="flex items-end gap-2 rounded-[22px] px-3 py-2 w-full"
          style={{
            background: "rgba(28,28,30,0.92)",
            border: "1px solid rgba(255,255,255,0.08)",
          }}
        >
          <textarea
            ref={textareaRef}
            value={input}
            onChange={onInputChange}
            onKeyDown={onKeyDown}
            rows={1}
            placeholder={aiText.inputPlaceholder}
            className="flex-1 bg-transparent outline-none resize-none text-[15px] text-white placeholder:text-white/35 py-1.5 px-1 max-h-40 leading-[1.4]"
          />
          {sending ? (
            <button
              type="button"
              onClick={stop}
              className="flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-all active:scale-95"
              style={{
                background: "rgba(239,68,68,0.18)",
                border: "1px solid rgba(239,68,68,0.4)",
              }}
              aria-label={aiText.ariaStop}
            >
              <span
                className="block w-3 h-3 rounded-[2px]"
                style={{ background: "#ef4444" }}
              />
            </button>
          ) : (
            <button
              type="submit"
              disabled={!input.trim()}
              className={cn(
                "flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-all active:scale-95 disabled:opacity-40",
              )}
              style={{
                background: input.trim() ? "#00FFF6" : "rgba(120,120,128,0.3)",
                boxShadow: input.trim() ? "0 0 12px rgba(0,255,246,0.45)" : "none",
              }}
              aria-label={aiText.ariaSend}
            >
              <ArrowUp
                size={18}
                className={input.trim() ? "text-black" : "text-white/50"}
                strokeWidth={2.5}
              />
            </button>
          )}
        </form>
        {chatId && messages.length > 0 && (
          <div className="flex justify-center mt-2">
            <button
              onClick={clearCurrent}
              className="text-[11px] text-white/35 active:text-white/70 transition-colors px-2"
            >
              {aiText.clearCurrent}
            </button>
          </div>
        )}
      </div>

      {/* ── History panel ── */}
      {historyOpen && (
        <HistoryPanel
          aiText={aiText}
          chats={chats}
          currentId={chatId}
          onClose={() => setHistoryOpen(false)}
          onSelect={(id) => loadChat(id)}
          onNew={newChat}
          onDelete={deleteChatById}
        />
      )}

      {/* ── 对比对话框 ── */}
      {compareMsg && (
        <CompareDialog
          aiText={aiText}
          msg={compareMsg}
          currentPeq={currentDevicePeq}
          applying={applyingId === compareMsg.id}
          onClose={() => setCompareMsg(null)}
          onApplyToggle={() => handleApplyToggle(compareMsg)}
        />
      )}

      {!isEmbedded && <BottomNav />}
    </div>
  );
}

// ============================================================
// 子组件
// ============================================================

function WelcomeView({
  aiText,
  deviceName,
  onPick,
}: {
  aiText: AILocale;
  deviceName: string;
  onPick: (prompt: string) => void;
}) {
  const suggestions = (aiText.suggestions ?? []).map((s, i) => ({
    icon: SUGGESTION_ICONS[i] ?? SUGGESTION_ICONS[0],
    title: s.title,
    prompt: s.prompt,
  }));
  return (
    <div className="flex flex-col items-center justify-center min-h-[60vh] pt-6">
      {/* Logo */}
      <div
        className="w-16 h-16 rounded-[22px] flex items-center justify-center mb-4"
        style={{
          background:
            "radial-gradient(circle at 30% 30%, rgba(0,255,246,0.3), rgba(0,255,246,0.08) 60%, transparent 80%)",
          border: "1px solid rgba(0,255,246,0.4)",
          boxShadow:
            "0 0 30px rgba(0,255,246,0.25), inset 0 0 20px rgba(0,255,246,0.08)",
        }}
      >
        {/* <Sparkles size={28} className="text-[#00FFF6]" /> */}
        <Bot size={28} className="text-[#00FFF6]" />
      </div>
      <h2 className="text-[22px] font-semibold text-white mb-1.5">{aiText.welcomeTitle}</h2>
      <p className="text-[13px] text-white/45 text-center leading-relaxed max-w-[280px] mb-7">
        {formatText(aiText.welcomeDescription, { device: deviceName })}
      </p>

      <div className="w-full max-w-[420px]">
        <div className="text-[11px] text-white/35 font-medium tracking-wider uppercase px-1 mb-2">
          {aiText.suggestionsLabel}
        </div>
        <div className="grid grid-cols-2 gap-2">
          {suggestions.map((s) => (
            <button
              key={s.title}
              onClick={() => onPick(s.prompt)}
              className="text-left rounded-[14px] p-3 transition-all active:scale-[0.98]"
              style={{
                background: "rgba(28,28,30,0.85)",
                border: "1px solid rgba(255,255,255,0.06)",
              }}
            >
              <div
                className="w-6 h-6 rounded-[8px] flex items-center justify-center mb-2"
                style={{
                  background: "rgba(0,255,246,0.1)",
                  border: "1px solid rgba(0,255,246,0.2)",
                  color: "#00FFF6",
                }}
              >
                {s.icon}
              </div>
              <div className="text-[13px] font-medium text-white mb-0.5">{s.title}</div>
              <div className="text-[11px] text-white/40 leading-snug line-clamp-2">
                {s.prompt}
              </div>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

function MessageBubble({ aiText, msg }: { aiText: AILocale; msg: ChatUIMessage }) {
  const isUser = msg.role === "user";
  return (
    <div className={cn("flex w-full", isUser ? "justify-end" : "justify-start")}>
      {!isUser && (
        <div
          className="w-7 h-7 rounded-full flex items-center justify-center mr-2 mt-0.5 flex-shrink-0"
          style={{
            background: "rgba(0,255,246,0.12)",
            border: "1px solid rgba(0,255,246,0.25)",
          }}
        >
          <Bot size={14} className="text-[#00FFF6]" />
        </div>
      )}
      <div
        className={cn(
          "rounded-[18px] px-3.5 py-2.5 max-w-[82%] text-[14.5px] leading-relaxed break-words",
          isUser ? "text-black" : "text-white",
        )}
        style={
          isUser
            ? {
                background: "#00FFF6",
                boxShadow: "0 0 14px rgba(0,255,246,0.25)",
                borderBottomRightRadius: 6,
              }
            : {
                background: "rgba(28,28,30,0.92)",
                border: "1px solid rgba(255,255,255,0.06)",
                borderBottomLeftRadius: 6,
              }
        }
      >
        {/* 工具调用 chips */}
        {msg.tools && msg.tools.length > 0 && (
          <div className="flex flex-wrap gap-1.5 mb-2">
            {msg.tools.map((t) => (
              <span
                key={t.id}
                className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full"
                style={{
                  background: "rgba(0,255,246,0.08)",
                  border: "1px solid rgba(0,255,246,0.22)",
                  color: "#00FFF6",
                }}
              >
                <Wrench size={10} />
                {t.name}
                {t.ok === false && <AlertCircle size={10} className="text-red-400" />}
              </span>
            ))}
          </div>
        )}
        {isUser ? (
          <div className="whitespace-pre-wrap">{msg.text}</div>
        ) : (
          <div className={cn("markdown-body", msg.error && "text-red-400")}>
            {msg.text ? (
              <Streamdown parseIncompleteMarkdown>{msg.text}</Streamdown>
            ) : (
              <span className="text-white/40 italic">{aiText.thinking}</span>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

function TypingIndicator() {
  return (
    <div className="flex items-center gap-1.5 ml-9 mt-1">
      <span className="w-1.5 h-1.5 rounded-full bg-white/40 animate-pulse" style={{ animationDelay: "0ms" }} />
      <span className="w-1.5 h-1.5 rounded-full bg-white/40 animate-pulse" style={{ animationDelay: "150ms" }} />
      <span className="w-1.5 h-1.5 rounded-full bg-white/40 animate-pulse" style={{ animationDelay: "300ms" }} />
    </div>
  );
}

function HistoryPanel({
  aiText,
  chats,
  currentId,
  onClose,
  onSelect,
  onNew,
  onDelete,
}: {
  aiText: AILocale;
  chats: ChatRead[];
  currentId: string | null;
  onClose: () => void;
  onSelect: (id: string) => void;
  onNew: () => void;
  onDelete: (id: string) => void;
}) {
  return (
    <div className="fixed inset-0 z-[60] flex flex-col bg-black/70" onClick={onClose}>
      <div
        className="mt-auto rounded-t-[22px] max-h-[70vh] flex flex-col"
        style={{
          background: "rgba(18,18,20,0.98)",
          border: "1px solid rgba(255,255,255,0.08)",
          paddingBottom: "env(safe-area-inset-bottom, 0px)",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center px-4 pt-3 pb-2">
          <span className="text-[15px] font-semibold text-white flex-1">
            {aiText.history.title}
          </span>
          <button
            onClick={onClose}
            className="w-7 h-7 rounded-full flex items-center justify-center text-white/60"
            style={{ background: "rgba(44,44,46,0.8)" }}
            aria-label={aiText.ariaClose}
          >
            <X size={14} />
          </button>
        </div>
        <div className="overflow-y-auto px-3 pb-3">
          <button
            onClick={onNew}
            className="w-full flex items-center gap-2 px-3 py-3 rounded-[12px] mb-2 text-left"
            style={{
              background: "rgba(0,255,246,0.08)",
              border: "1px dashed rgba(0,255,246,0.3)",
              color: "#00FFF6",
            }}
          >
            <MessageSquarePlus size={16} />
            <span className="text-[14px] font-medium">{aiText.history.newChat}</span>
          </button>
          {chats.length === 0 ? (
            <div className="text-center text-white/40 text-[13px] py-6">
              {aiText.history.empty}
            </div>
          ) : (
            <div className="ios-list-group">
              {chats.map((c) => (
                <div
                  key={c.id}
                  className={cn(
                    "ios-list-row w-full",
                    currentId === c.id && "bg-white/[0.04]",
                  )}
                >
                  <button
                    onClick={() => onSelect(c.id)}
                    className="flex-1 min-w-0 text-left active:opacity-60"
                  >
                    <div className="text-[15px] text-white truncate">{c.title}</div>
                    <div className="text-[11px] text-white/35 mt-0.5">
                      {new Date(c.updated_at).toLocaleString()}
                    </div>
                  </button>
                  {currentId === c.id && (
                    <span className="text-[11px] text-[#00FFF6] ml-2">
                      {aiText.history.current}
                    </span>
                  )}
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      onDelete(c.id);
                    }}
                    className="ml-2 w-8 h-8 rounded-full flex items-center justify-center text-white/40 hover:text-red-400 active:text-red-500 transition-colors"
                    style={{ background: "rgba(44,44,46,0.6)" }}
                    aria-label={aiText.history.delete}
                  >
                    <Trash2 size={14} />
                  </button>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ============================================================
// OptimizeCard: type=2 的优化记录气泡
// ============================================================
function OptimizeCard({
  aiText,
  msg,
  currentPeq,
  applying,
  onCompare,
  onApplyToggle,
}: {
  aiText: AILocale;
  msg: OptimizeUIMessage;
  currentPeq: OptimizePeqPayload | null;
  applying: boolean;
  onCompare: () => void;
  onApplyToggle: () => void;
}) {
  // 当前未应用 → 目标是 after_peq（点击应用即切到 after）
  // 当前已应用 → 目标是 before_peq（点击回滚即切回 before）
  const targetPeq = msg.applied ? msg.beforePeq : msg.afterPeq;
  const filterCount = targetPeq.filters?.length ?? 0;
  const preamp = targetPeq.preamp ?? 0;
  const summary = formatText(aiText.optimize.summary, {
    count: filterCount,
    preamp: `${preamp > 0 ? "+" : ""}${preamp.toFixed(1)}`,
  });
  return (
    <div className="flex w-full justify-start">
      <div
        className="w-7 h-7 rounded-full flex items-center justify-center mr-2 mt-0.5 flex-shrink-0"
        style={{
          background: "rgba(0,255,246,0.12)",
          border: "1px solid rgba(0,255,246,0.25)",
        }}
      >
        <Wand2 size={14} className="text-[#00FFF6]" />
      </div>
      <div
        className="rounded-[18px] max-w-[82%] w-full p-3.5"
        style={{
          background: "rgba(28,28,30,0.92)",
          border: "1px solid rgba(0,255,246,0.25)",
          borderBottomLeftRadius: 6,
          boxShadow: "0 0 14px rgba(0,255,246,0.08)",
        }}
      >
        <div className="flex items-center gap-2 mb-2">
          <span
            className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 rounded-full font-medium"
            style={{
              background: "rgba(0,255,246,0.1)",
              border: "1px solid rgba(0,255,246,0.3)",
              color: "#00FFF6",
            }}
          >
            <Sparkles size={10} /> {aiText.optimize.badge}
          </span>
          {msg.applied && (
            <span className="text-[10px] text-green-400/90">
              {aiText.optimize.applied}
            </span>
          )}
        </div>

        <div className="text-[14px] font-semibold text-white truncate">
          {msg.name}
        </div>
        <div className="text-[12px] text-white/45 mt-0.5">{summary}</div>

        {/* 缩略对比曲线：当前设备 vs 应用/回滚后 */}
        <ComparePreview
          currentPeq={currentPeq}
          targetPeq={targetPeq}
          width={260}
          height={70}
        />

        <div className="flex gap-2 mt-3">
          <button
            onClick={onCompare}
            className="flex-1 h-9 rounded-[10px] text-[13px] font-medium text-white flex items-center justify-center gap-1.5 active:scale-[0.98] transition-all"
            style={{
              background: "rgba(44,44,46,0.8)",
              border: "1px solid rgba(255,255,255,0.08)",
            }}
          >
            <GitCompare size={14} />
            {aiText.optimize.compare}
          </button>
          <button
            onClick={onApplyToggle}
            disabled={applying}
            className={cn(
              "flex-1 h-9 rounded-[10px] text-[13px] font-semibold flex items-center justify-center gap-1.5 active:scale-[0.98] transition-all disabled:opacity-50",
            )}
            style={
              msg.applied
                ? {
                    background: "rgba(239,68,68,0.12)",
                    border: "1px solid rgba(239,68,68,0.3)",
                    color: "#f87171",
                  }
                : {
                    background: "#00FFF6",
                    color: "#000",
                    boxShadow: "0 0 12px rgba(0,255,246,0.35)",
                  }
            }
          >
            {applying ? (
              <Loader2 size={14} className="animate-spin" />
            ) : msg.applied ? (
              <>
                <RotateCcw size={14} /> {aiText.optimize.rollback}
              </>
            ) : (
              <>
                <Check size={14} /> {aiText.optimize.apply}
              </>
            )}
          </button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// 曲线可视化：把 PEQ 的 filters 转为 buildPeqSvgCurveData 可用的格式
// ============================================================
function peqToBands(peq: OptimizePeqPayload): PeqBandForResponse[] {
  const filters = peq.filters ?? [];
  return filters.map((f) => ({
    enabled: true,
    gain: Number(f.gain) || 0,
    freq: Number((f.fc ?? f.frequency) ?? 1000),
    q: Number(f.q) || 1,
    type: f.type,
  }));
}

function CurveSvg({
  bands,
  width,
  height,
  stroke,
  fill,
  className,
}: {
  bands: PeqBandForResponse[];
  width: number;
  height: number;
  stroke: string;
  fill?: string;
  className?: string;
}) {
  const data = useMemo(
    () => buildPeqSvgCurveData({ bands, width, height, yDbMax: 20, paddingY: 6 }),
    [bands, width, height],
  );
  return (
    <svg
      viewBox={`0 0 ${width} ${height}`}
      width="100%"
      height={height}
      className={className}
      preserveAspectRatio="none"
    >
      {/* 中线 */}
      <line
        x1={0}
        x2={width}
        y1={height / 2}
        y2={height / 2}
        stroke="rgba(255,255,255,0.08)"
        strokeDasharray="2 3"
      />
      {fill && <path d={data.fillD} fill={fill} />}
      <path d={data.pathD} fill="none" stroke={stroke} strokeWidth={1.5} />
    </svg>
  );
}

function ComparePreview({
  currentPeq,
  targetPeq,
  width,
  height,
}: {
  currentPeq: OptimizePeqPayload | null;
  targetPeq: OptimizePeqPayload;
  width: number;
  height: number;
}) {
  const currentBands = useMemo(
    () => (currentPeq ? peqToBands(currentPeq) : []),
    [currentPeq],
  );
  const targetBands = useMemo(() => peqToBands(targetPeq), [targetPeq]);
  return (
    <div
      className="relative mt-3 rounded-[10px] overflow-hidden"
      style={{
        background: "rgba(10,10,12,0.6)",
        border: "1px solid rgba(255,255,255,0.05)",
      }}
    >
      {currentBands.length > 0 && (
        <div className="absolute inset-0">
          <CurveSvg bands={currentBands} width={width} height={height} stroke="rgba(255,255,255,0.35)" />
        </div>
      )}
      <div className="relative">
        <CurveSvg bands={targetBands} width={width} height={height} stroke="#00FFF6" fill="rgba(0,255,246,0.08)" />
      </div>
    </div>
  );
}

// ============================================================
// CompareDialog: 放大版的对比弹框
// ============================================================
function CompareDialog({
  aiText,
  msg,
  currentPeq,
  applying,
  onClose,
  onApplyToggle,
}: {
  aiText: AILocale;
  msg: OptimizeUIMessage;
  currentPeq: OptimizePeqPayload | null;
  applying: boolean;
  onClose: () => void;
  onApplyToggle: () => void;
}) {
  // 未应用 → 目标 = after_peq，图例右侧显示“应用后”
  // 已应用 → 目标 = before_peq，图例右侧显示“回滚后”
  const targetPeq = msg.applied ? msg.beforePeq : msg.afterPeq;
  const currentBands = useMemo(
    () => (currentPeq ? peqToBands(currentPeq) : []),
    [currentPeq],
  );
  const targetBands = useMemo(() => peqToBands(targetPeq), [targetPeq]);
  const currentLabel = aiText.compareDialog.legendCurrent;
  const targetLabel = msg.applied
    ? aiText.compareDialog.legendAfterRollback
    : aiText.compareDialog.legendAfterApply;
  const chartWidth = 340;
  const chartHeight = 160;

  return (
    <div
      className="fixed inset-0 z-[60] flex items-center justify-center bg-black/75 px-4"
      onClick={onClose}
    >
      <div
        className="w-full max-w-[420px] rounded-[22px] overflow-hidden flex flex-col"
        style={{
          background: "rgba(18,18,20,0.98)",
          border: "1px solid rgba(255,255,255,0.08)",
          maxHeight: "85vh",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        {/* Header */}
        <div className="flex items-center px-4 pt-4 pb-2">
          <div className="flex-1 min-w-0">
            <div className="text-[11px] text-[#00FFF6] font-medium tracking-wide">
              {aiText.compareDialog.title}
            </div>
            <div className="text-[15px] font-semibold text-white truncate mt-0.5">
              {msg.name}
            </div>
          </div>
          <button
            onClick={onClose}
            className="w-7 h-7 rounded-full flex items-center justify-center text-white/60"
            style={{ background: "rgba(44,44,46,0.8)" }}
            aria-label={aiText.ariaClose}
          >
            <X size={14} />
          </button>
        </div>

        <div className="overflow-y-auto px-4 pb-4 flex-1">
          {/* 叠加曲线图 */}
          <div
            className="rounded-[14px] p-3 mt-1"
            style={{
              background: "rgba(10,10,12,0.7)",
              border: "1px solid rgba(255,255,255,0.06)",
            }}
          >
            <div className="flex items-center gap-3 mb-2 text-[11px]">
              <span className="inline-flex items-center gap-1.5 text-white/55">
                <span
                  className="block w-3 h-[2px] rounded"
                  style={{ background: "rgba(255,255,255,0.45)" }}
                />
                {currentLabel}
              </span>
              <span className="inline-flex items-center gap-1.5" style={{ color: "#00FFF6" }}>
                <span className="block w-3 h-[2px] rounded" style={{ background: "#00FFF6" }} />
                {targetLabel}
              </span>
            </div>
            <div className="relative" style={{ height: chartHeight }}>
              {currentBands.length > 0 && (
                <div className="absolute inset-0">
                  <CurveSvg
                    bands={currentBands}
                    width={chartWidth}
                    height={chartHeight}
                    stroke="rgba(255,255,255,0.45)"
                  />
                </div>
              )}
              <div className="absolute inset-0">
                <CurveSvg
                  bands={targetBands}
                  width={chartWidth}
                  height={chartHeight}
                  stroke="#00FFF6"
                  fill="rgba(0,255,246,0.08)"
                />
              </div>
            </div>
            {/* 频率轴标签 */}
            <div className="flex justify-between text-[10px] text-white/35 mt-1 px-0.5">
              <span>20</span>
              <span>100</span>
              <span>1K</span>
              <span>10K</span>
              <span>20K</span>
            </div>
          </div>

          {/* 参数对比表：当前设备 vs 应用/回滚后 */}
          <div className="mt-3 grid grid-cols-2 gap-2">
            <ParamCard
              aiText={aiText}
              label={currentLabel}
              peq={currentPeq}
              muted
            />
            <ParamCard
              aiText={aiText}
              label={targetLabel}
              peq={targetPeq}
              highlight
            />
          </div>
        </div>

        {/* Footer */}
        <div
          className="px-4 py-3 flex items-center gap-2"
          style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }}
        >
          <button
            onClick={onClose}
            className="flex-1 h-10 rounded-[10px] text-[14px] text-white/70"
            style={{ background: "rgba(44,44,46,0.8)" }}
          >
            {aiText.compareDialog.close}
          </button>
          <button
            onClick={onApplyToggle}
            disabled={applying}
            className="flex-1 h-10 rounded-[10px] text-[14px] font-semibold flex items-center justify-center gap-1.5 active:scale-[0.98] disabled:opacity-50"
            style={
              msg.applied
                ? {
                    background: "rgba(239,68,68,0.14)",
                    border: "1px solid rgba(239,68,68,0.35)",
                    color: "#f87171",
                  }
                : {
                    background: "#00FFF6",
                    color: "#000",
                    boxShadow: "0 0 14px rgba(0,255,246,0.35)",
                  }
            }
          >
            {applying ? (
              <Loader2 size={14} className="animate-spin" />
            ) : msg.applied ? (
              <>
                <RotateCcw size={14} /> {aiText.compareDialog.rollback}
              </>
            ) : (
              <>
                <Check size={14} /> {aiText.compareDialog.apply}
              </>
            )}
          </button>
        </div>
      </div>
    </div>
  );
}

function ParamCard({
  aiText,
  label,
  peq,
  muted,
  highlight,
}: {
  aiText: AILocale;
  label: string;
  peq: OptimizePeqPayload | null;
  muted?: boolean;
  highlight?: boolean;
}) {
  const filters = peq?.filters ?? [];
  const summary = formatText(aiText.compareDialog.paramSummary, {
    count: filters.length,
    preamp: (peq?.preamp ?? 0).toFixed(1),
  });
  return (
    <div
      className="rounded-[12px] p-2.5 text-[11px]"
      style={{
        background: highlight ? "rgba(0,255,246,0.06)" : "rgba(28,28,30,0.9)",
        border: highlight
          ? "1px solid rgba(0,255,246,0.25)"
          : "1px solid rgba(255,255,255,0.05)",
      }}
    >
      <div className={cn("font-medium mb-1", highlight ? "text-[#00FFF6]" : "text-white/50")}>
        {label}
      </div>
      <div className="text-white/70 mb-1.5">{summary}</div>
      <div className="space-y-0.5 max-h-28 overflow-y-auto pr-1">
        {filters.map((f, i) => {
          const freq = Number((f.fc ?? f.frequency) ?? 0);
          return (
            <div
              key={i}
              className={cn(
                "flex justify-between font-mono",
                muted ? "text-white/50" : "text-white/85",
              )}
            >
              <span>{freq >= 1000 ? `${(freq / 1000).toFixed(1)}K` : Math.round(freq)}Hz</span>
              <span>
                {f.gain > 0 ? "+" : ""}
                {Number(f.gain).toFixed(1)}dB
              </span>
              <span className="text-white/40">Q{Number(f.q).toFixed(1)}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}
