// ============================================================
// CamTalk — 主应用组件（Web 端两栏布局：视频 + 聊天）
// 侧边栏为 overlay 抽屉式，不挤压主界面空间
// ============================================================

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { useVisionSession } from "./hooks/useVisionSession";
import { useSessionList } from "./hooks/useSessionList";
import { useScenarios } from "./hooks/useScenarios";
import { VideoPreview } from "./components/VideoPreview";
import { ChatPanel } from "./components/ChatPanel";
import { ConfigPanel } from "./components/ConfigPanel";
import { SessionSidebar } from "./components/SessionSidebar";
import { ToastContainer } from "./components/Toast";
import { LandingPage } from "./components/LandingPage";
import { CreateScenarioModal } from "./components/CreateScenarioModal";
import { EditScenarioModal } from "./components/EditScenarioModal";
import { AuthProvider, useAuth } from "./lib/auth";
import { loadConfig, loadTheme, saveTheme } from "./lib/storage";
import { I18nContext, parseLocale, t } from "./lib/i18n";
import { renderScenarioIconById } from "./lib/scenarioIcons";
import type { Locale } from "./lib/i18n";
import type { Theme } from "./types";
import type { UserScenario } from "./lib/api/scenarios";
import "./App.css";

// ---- 统一 SVG 图标系统（Feather-style，stroke-based） ----

const SVG_PROPS = { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" as const, strokeLinejoin: "round" as const };

/** 系统情景 SVG 图标映射 */
function scenarioSvgIcon(id: string) {
  switch (id) {
    case "free_chat":
      return (<svg {...SVG_PROPS}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>);
    case "interviewer":
      return (<svg {...SVG_PROPS}><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>);
    case "english_teacher":
      return (<svg {...SVG_PROPS}><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>);
    case "debate":
      return (<svg {...SVG_PROPS}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><path d="M8 10h8"/><path d="M8 14h4"/></svg>);
    case "interpreter":
      return (<svg {...SVG_PROPS}><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>);
    default:
      return null;
  }
}

/** 内部组件，确保在 I18nContext.Provider 内部使用 hooks */
function AppContent() {
  const { isAuthenticated, isLoading, user, logout, accessToken } = useAuth();
  const [showConfig, setShowConfig] = useState(false);
  const [showCreateScenario, setShowCreateScenario] = useState(false);
  const [editingScenario, setEditingScenario] = useState<UserScenario | null>(null);
  const [theme, setTheme] = useState<Theme>(loadTheme);
  const [elapsed, setElapsed] = useState(0);
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);

  // ---- 自建情景管理 ----
  const {
    allScenarios,
    createScenario,
    updateScenario,
    deleteScenario,
  } = useScenarios(accessToken);

  // 切换主题时更新 <html> 的 data-theme 属性
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", theme);
  }, [theme]);

  const handleThemeChange = useCallback((t: Theme) => {
    setTheme(t);
    saveTheme(t);
  }, []);

  const formatTime = (s: number) => {
    const m = Math.floor(s / 60);
    const sec = s % 60;
    return `${m.toString().padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
  };

  // ---- 会话列表 ----
  const {
    sessions,
    activeSessionId,
    isLoading: sessionsLoading,
    createSession,
    deleteSession,
    renameSession,
    selectSession,
    loadSessions,
  } = useSessionList(accessToken);

  // ---- 视觉会话 ----
  const {
    messages,
    setMessages,
    currentReply,
    isProcessing,
    isAudioPlaying,
    isSpeaking,
    isVADReady,
    vadError,
    connectionStatus,
    videoRef,
    stream,
    config,
    updateConfig,
    stats,
    startSession,
    stopSession,
    stopVideo,
    interrupt,
    isCameraOn,
    isMicOn,
    toggleCamera,
    toggleMic,
    sendTextMessage,
    cameras,
    mics,
    switchDevice,
  } = useVisionSession(accessToken, activeSessionId);

  const isConnected = connectionStatus === "connected";

  // 连接计时器
  const elapsedRef = useRef(0);
  useEffect(() => {
    if (isConnected) {
      elapsedRef.current = 0;
      setElapsed(0); // eslint-disable-line react-hooks/set-state-in-effect -- 连接时重置计时器
    }
  }, [isConnected]);

  useEffect(() => {
    if (!isConnected) {
      if (timerRef.current) {
        clearInterval(timerRef.current);
        timerRef.current = null;
      }
      return;
    }
    const id = setInterval(() => {
      elapsedRef.current += 1;
      setElapsed(elapsedRef.current);
    }, 1000);
    timerRef.current = id;
    return () => clearInterval(id);
  }, [isConnected]);

  const { t: tr } = useMemo(() => ({ t: (key: string) => t(key, parseLocale(config.language)) }), [config.language]);

  // ---- 初始化：加载完成后如果没有会话，创建一个 ----
  const initializedRef = useRef(false);
  useEffect(() => {
    if (!sessionsLoading && !initializedRef.current) {
      initializedRef.current = true;
      if (sessions.length === 0) {
        createSession();
      }
    }
  }, [sessionsLoading, sessions.length, createSession]);

  // ---- 侧边栏打开时刷新会话列表 ----
  useEffect(() => {
    if (sidebarOpen) {
      loadSessions();
    }
  }, [sidebarOpen, loadSessions]);

  // ---- 侧边栏操作 ----
  const handleNewSession = useCallback(async () => {
    // 如果已连接，先断开
    if (connectionStatus === "connected") {
      await stopSession();
    }
    // 通过后端 API 创建会话
    await createSession();
    setMessages([]);
    setSidebarOpen(false);
  }, [createSession, setMessages, connectionStatus, stopSession]);

  const handleSelectSession = useCallback(async (id: string) => {
    // 如果已连接，先断开
    if (connectionStatus === "connected") {
      await stopSession();
    }
    // 从后端 API 加载目标会话的消息
    const loaded = await selectSession(id);
    setMessages(loaded);
    setSidebarOpen(false);
  }, [connectionStatus, stopSession, selectSession, setMessages]);

  const handleDeleteSession = useCallback(async (id: string) => {
    await deleteSession(id);
    if (id === activeSessionId) {
      // 断开连接并清空消息
      if (connectionStatus === "connected") {
        await stopSession();
      }
      setMessages([]);
    }
  }, [deleteSession, activeSessionId, setMessages, connectionStatus, stopSession]);

  // ---- 识别画面 ----
  const handleRecognize = useCallback(() => {
    if (isProcessing) return;
    sendTextMessage(tr("controls.recognize"));
  }, [isProcessing, sendTextMessage, tr]);

  // ---- 场景卡片点击 ----
  const handleSceneCard = useCallback((prompt: string) => {
    sendTextMessage(prompt);
  }, [sendTextMessage]);

  // ---- 情景选择 ----
  const handleSelectScenario = useCallback((scenarioId: string) => {
    const sc = allScenarios.find((s) => s.id === scenarioId);
    const updates: Partial<import("./types").SessionConfig> = { scenario: scenarioId };
    // 如果情景有默认语言，同步切换（仅系统预置情景）
    if (sc && !sc.isCustom) {
      const systemScenario = sc as any;
      if (systemScenario.defaultLanguage) {
        updates.language = systemScenario.defaultLanguage;
      }
    }
    updateConfig(updates);
    // 插入系统提示消息
    const scenarioName = sc
      ? `${sc.icon} ${sc.nameKey ? tr(sc.nameKey) : sc.name}`
      : scenarioId;
    setMessages(prev => [...prev, {
      id: uuidv4(),
      role: "system",
      content: tr("chat.scenarioSwitched").replace("{name}", scenarioName),
      timestamp: Date.now(),
    }]);
  }, [updateConfig, setMessages, tr]);

  // ---- 键盘快捷键 ----
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      // Cmd/Ctrl+H: 打开/关闭历史侧边栏
      if ((e.metaKey || e.ctrlKey) && e.key === "h") {
        e.preventDefault();
        setSidebarOpen((v) => !v);
      }
      // Escape: 关闭侧边栏
      if (e.key === "Escape" && sidebarOpen) {
        setSidebarOpen(false);
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [sidebarOpen]);

  // Auth guard：未登录时显示登录页（放在所有 hooks 之后以遵守 Rules of Hooks）
  if (isLoading) {
    return (
      <div className="auth-page">
        <div className="auth-card" style={{ textAlign: "center", border: "none", background: "transparent", boxShadow: "none" }}>
          <p style={{ color: "var(--color-text-muted)", fontSize: "0.88rem" }}>Loading...</p>
        </div>
      </div>
    );
  }

  if (!isAuthenticated) {
    return <LandingPage />;
  }

  return (
    <div className="app">
      {/* ---- 顶部导航栏 ---- */}
      <header className="header">
        <div className="header__left">
          {/* 侧边栏开关 */}
          <button
            className="header__menu-btn"
            onClick={() => setSidebarOpen((v) => !v)}
            title={tr("sidebar.expand")}
            aria-label={tr("sidebar.expand")}
          >
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <line x1="3" y1="6" x2="21" y2="6" />
              <line x1="3" y1="12" x2="21" y2="12" />
              <line x1="3" y1="18" x2="21" y2="18" />
            </svg>
          </button>
          <h1 className="header__title">CamTalk</h1>
          <span className="header__subtitle">{tr("app.title")}</span>
        </div>
        <div className="header__right">
          <span className={`badge badge--${connectionStatus}`}>
            {isConnected ? tr("status.connected") : connectionStatus === "connecting" ? tr("status.connecting") : tr("status.disconnected")}
          </span>
          <button
            className="btn-icon"
            onClick={() => setShowConfig((v) => !v)}
            title={tr("settings.title")}
            aria-label={tr("settings.title")}
          >
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="3" />
              <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
            </svg>
          </button>
        </div>
      </header>

      {/* ---- 会话历史侧边栏（Overlay 抽屉式） ---- */}
      <SessionSidebar
        sessions={sessions}
        activeSessionId={activeSessionId}
        open={sidebarOpen}
        onClose={() => setSidebarOpen(false)}
        onNewSession={handleNewSession}
        onSelectSession={handleSelectSession}
        onDeleteSession={handleDeleteSession}
        onRenameSession={renameSession}
      />

      <ConfigPanel
        config={config}
        theme={theme}
        username={user?.username}
        allScenarios={allScenarios}
        open={showConfig}
        onUpdate={updateConfig}
        onThemeChange={handleThemeChange}
        onLogout={logout}
        onClose={() => setShowConfig(false)}
        onCreateScenario={() => {
          setShowConfig(false);
          setShowCreateScenario(true);
        }}
        onEditScenario={(scenario) => {
          setShowConfig(false);
          setEditingScenario(scenario);
        }}
        onDeleteScenario={deleteScenario}
      />

      {/* 创建情景 Modal */}
      {showCreateScenario && (
        <CreateScenarioModal
          onClose={() => setShowCreateScenario(false)}
          onSubmit={async (data) => {
            await createScenario(data);
          }}
        />
      )}

      {/* 编辑情景 Modal */}
      {editingScenario && (
        <EditScenarioModal
          scenario={editingScenario}
          onClose={() => setEditingScenario(null)}
          onSubmit={async (id, data) => {
            await updateScenario(id, data);
          }}
        />
      )}

      {/* ---- 主体：视频 + 聊天（两栏） ---- */}
      <div className="workspace">
        {/* 左侧：视频预览 + 控制栏 */}
        <div className="video-panel">
          <div className="video-container">
            <VideoPreview ref={videoRef} isStreaming={!!stream} />
            {isConnected && (
              <div className="live-badge">
                <span className="live-badge__dot" />
                LIVE {formatTime(elapsed)}
              </div>
            )}
            {config.detailLevel === "high" && (
              <div className="detail-badge">HD</div>
            )}
            {/* AI 视觉状态指示 */}
            {isConnected && (
              <div className="ai-vision-indicator">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
                  <circle cx="12" cy="12" r="3" />
                </svg>
                <span>AI</span>
              </div>
            )}
            {isSpeaking && (
              <div className="video-indicator">{tr("video.listening")}</div>
            )}
            {isAudioPlaying && config.ttsEnabled && (
              <div className="video-indicator video-indicator--audio">{tr("video.playing")}</div>
            )}
            {vadError && (
              <div className="video-indicator video-indicator--error"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg> {vadError}</div>
            )}
            {!isConnected && !stream && (
              <div className="video-placeholder">
                <svg className="video-placeholder__icon" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
                  <circle cx="12" cy="13" r="4" />
                </svg>
                <span>{tr("video.placeholder")}</span>
              </div>
            )}
            {isConnected && !isCameraOn && (
              <div className="video-placeholder">
                <svg className="video-placeholder__icon" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
                  <circle cx="12" cy="13" r="4" />
                </svg>
                <span>{tr("video.cameraOff")}</span>
                <span className="video-placeholder__hint">{tr("video.cameraOff.hint")}</span>
              </div>
            )}
          </div>

          {/* ---- 情景选择芯片条（视频下方） ---- */}
          <div className="scenario-strip">
            <span className="scenario-strip__label">{tr("settings.scenario")}</span>
            <div className="scenario-strip__scroll">
              {allScenarios.map((sc) => (
                <button
                  key={sc.id}
                  className={`scenario-chip ${config.scenario === sc.id ? "scenario-chip--active" : ""}`}
                  onClick={() => handleSelectScenario(sc.id)}
                  title={sc.description ? sc.description : sc.descKey ? tr(sc.descKey) : sc.name}
                >
                  <span className="scenario-chip__icon">
                    {scenarioSvgIcon(sc.id) || renderScenarioIconById(sc.icon, 14) || sc.icon}
                  </span>
                  <span className="scenario-chip__name">{sc.nameKey ? tr(sc.nameKey) : sc.name}</span>
                </button>
              ))}
              {/* 新建情景快捷入口 */}
              <button
                className="scenario-chip scenario-chip--add"
                onClick={() => setShowCreateScenario(true)}
                title={tr("scenario.create.button")}
              >
                <span className="scenario-chip__icon">
                  <svg {...SVG_PROPS}><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
                </span>
                <span className="scenario-chip__name">{tr("scenario.createChip")}</span>
              </button>
            </div>
          </div>

          {/* 视频下方控制区（三层结构） */}
          <div className="video-controls">
            {!isConnected ? (
              <>
                {/* 未连接态：主 CTA */}
                <button className="btn btn--primary btn--lg" onClick={startSession}>
                  {connectionStatus === "connecting" ? tr("controls.connecting") : tr("controls.startVideo")}
                </button>
              </>
            ) : isCameraOn ? (
              <>
                {/* 视频通话态：核心控制工具栏 */}
                <div className="video-controls__toolbar">
                  <button
                    className={`btn-ctrl-round ${isCameraOn ? "btn-ctrl-round--on" : "btn-ctrl-round--off"}`}
                    onClick={toggleCamera}
                    title={isCameraOn ? tr("controls.cameraOff") : tr("controls.cameraOn")}
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                      {isCameraOn ? (
                        <>
                          <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
                          <circle cx="12" cy="13" r="4" />
                        </>
                      ) : (
                        <>
                          <path d="M16 16v1a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2m5.66 0H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10" />
                          <line x1="1" y1="1" x2="23" y2="23" />
                        </>
                      )}
                    </svg>
                  </button>
                  <button
                    className={`btn-ctrl-round ${isMicOn ? "btn-ctrl-round--on" : "btn-ctrl-round--off"} ${isSpeaking ? "btn-ctrl-round--speaking" : ""}`}
                    onClick={toggleMic}
                    title={isMicOn ? tr("controls.micOff") : tr("controls.micOn")}
                  >
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                      {isMicOn ? (
                        <>
                          <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
                          <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
                          <line x1="12" y1="19" x2="12" y2="23" />
                          <line x1="8" y1="23" x2="16" y2="23" />
                        </>
                      ) : (
                        <>
                          <line x1="1" y1="1" x2="23" y2="23" />
                          <path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" />
                          <path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .76-.12 1.49-.34 2.17" />
                          <line x1="12" y1="19" x2="12" y2="23" />
                          <line x1="8" y1="23" x2="16" y2="23" />
                        </>
                      )}
                    </svg>
                  </button>
                  <button
                    className="btn-ctrl-pill btn-ctrl-pill--recognize"
                    onClick={handleRecognize}
                    disabled={isProcessing}
                  >
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                      <circle cx="11" cy="11" r="8" />
                      <line x1="21" y1="21" x2="16.65" y2="16.65" />
                    </svg>
                    {tr("controls.recognize")}
                  </button>
                  {isProcessing && (
                    <button className="btn-ctrl-pill btn-ctrl-pill--interrupt" onClick={interrupt}>
                      {tr("controls.interrupt")}
                    </button>
                  )}
                  <button className="btn-ctrl-pill btn-ctrl-pill--stop" onClick={stopVideo}>
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                      <rect x="6" y="6" width="12" height="12" rx="2" />
                    </svg>
                    {tr("controls.stopVideo")}
                  </button>
                </div>
                {/* 设备选择器 */}
                <div className="video-controls__devices">
                  <div className="device-select-wrapper">
                    <label className="device-select-label"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg></label>
                    <select
                      className="device-select"
                      value={config.cameraDeviceId || "default"}
                      onChange={(e) => switchDevice("camera", e.target.value === "default" ? "" : e.target.value)}
                    >
                      <option value="default">{tr("controls.device.default")}</option>
                      {cameras.map((d) => (
                        <option key={d.deviceId} value={d.deviceId}>{d.label}</option>
                      ))}
                    </select>
                  </div>
                  <div className="device-select-wrapper">
                    <label className="device-select-label"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></label>
                    <select
                      className="device-select"
                      value={config.micDeviceId || "default"}
                      onChange={(e) => switchDevice("mic", e.target.value === "default" ? "" : e.target.value)}
                    >
                      <option value="default">{tr("controls.device.default")}</option>
                      {mics.map((d) => (
                        <option key={d.deviceId} value={d.deviceId}>{d.label}</option>
                      ))}
                    </select>
                  </div>
                </div>
              </>
            ) : (
              <>
                {/* 文字对话态：视频已结束 */}
                <div className="video-controls__text-only">
                  <div className="video-ended-hint">
                    <span className="video-ended-hint__title"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> {tr("video.ended")}</span>
                    <span className="video-ended-hint__sub">{tr("video.ended.hint")}</span>
                  </div>
                  <div className="video-controls__toolbar">
                    <button className="btn btn--primary" onClick={startSession}>
                      {tr("controls.resumeVideo")}
                    </button>
                    <button className="btn btn--danger btn--outline" onClick={stopSession}>
                      {tr("controls.endSession")}
                    </button>
                  </div>
                </div>
              </>
            )}
          </div>
        </div>

        {/* 右侧：聊天面板 */}
        <div className="chat-panel-wrapper">
          <div className="chat-panel-header">
            <span>{tr("chat.title")}</span>
            <div className="chat-panel-header__right">
              {isConnected && stats.queryCount > 0 && (
                <span className="chat-panel-header__stats">
                  {stats.queryCount} {tr("statusbar.recognitions")}
                  {stats.totalTokens > 0 && ` · ${stats.totalTokens.toLocaleString()} ${tr("statusbar.tokens")}`}
                  {` · ${formatTime(elapsed)}`}
                </span>
              )}
            </div>
          </div>
          <div className="chat-panel-body">
            {connectionStatus === "disconnected" && messages.length > 0 && (
              <div className="system-message system-message--warning">
                {tr("chat.reconnecting")}
              </div>
            )}
            <ChatPanel
              messages={messages}
              currentReply={currentReply}
              connectionStatus={connectionStatus}
              currentScenario={config.scenario}
              isProcessing={isProcessing}
              isVADReady={isVADReady}
              vadError={vadError ?? undefined}
              isCameraOn={isCameraOn}
              isMicOn={isMicOn}
              isSpeaking={isSpeaking}
              onSendText={sendTextMessage}
              onToggleMic={toggleMic}
              onSceneCard={handleSceneCard}
              onSelectScenario={handleSelectScenario}
            />
          </div>
        </div>
      </div>

      <ToastContainer />
    </div>
  );
}

function App() {
  const [locale, setLocale] = useState<Locale>(() => parseLocale(loadConfig().language));

  const i18nValue = useMemo(() => ({
    locale,
    t: (key: string) => t(key, locale),
  }), [locale]);

  // 监听配置变化以更新 locale
  useEffect(() => {
    const handler = () => {
      const cfg = loadConfig();
      setLocale(parseLocale(cfg.language));
    };
    // 自定义事件，由 saveConfig 触发
    window.addEventListener("camtalk-config-changed", handler);
    return () => window.removeEventListener("camtalk-config-changed", handler);
  }, []);

  return (
    <I18nContext.Provider value={i18nValue}>
      <AuthProvider>
        <AppContent />
      </AuthProvider>
    </I18nContext.Provider>
  );
}

export default App;
