import {
    Check,
    ChevronDown,
    Copy,
    Download,
    Mic,
    MoreVertical,
    Paperclip,
    RefreshCw,
    Send,
    Settings,
    Trash2,
    Waves,
    Zap
} from 'lucide-react'
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {useNavigate} from 'react-router-dom'
import {type Conversation, conversationApi, type Message} from '../api/conversation'
import {type Character, extractFrontendCard} from '../api/character'
import {type AIConfig, aiConfigApi} from '../api/aiConfig'
import {type Preset, presetApi} from '../api/preset'
import {type RegexScript, regexAPI} from '../api/regex'
import MessageContent from './MessageContent'
import StatusBarIframe from './StatusBarIframe'
import {useAppStore} from '../store'
import {streamSSE} from '../lib/sse'

interface ChatAreaProps {
  conversation: Conversation
  character: Character
  onConversationUpdate: (conversation: Conversation) => void
}

/** 解析 conversation.settings（兼容 string 与 object 两种形式） */
function parseSettings(raw: unknown): Record<string, unknown> {
  if (!raw) return {}
  if (typeof raw === 'string') {
    try { return JSON.parse(raw) } catch { return {} }
  }
  if (typeof raw === 'object') return raw as Record<string, unknown>
  return {}
}

export default function ChatArea({ conversation, character, onConversationUpdate }: ChatAreaProps) {
  const navigate = useNavigate()
  const [messages, setMessages] = useState<Message[]>([])
  const [inputValue, setInputValue] = useState('')
  const [sending, setSending] = useState(false)
  const [loading, setLoading] = useState(true)
  const [showMenu, setShowMenu] = useState(false)
  const [copiedId, setCopiedId] = useState<number | null>(null)
  const [aiConfigs, setAiConfigs] = useState<AIConfig[]>([])
  const [selectedConfigId, setSelectedConfigId] = useState<number>()
  const [showModelSelector, setShowModelSelector] = useState(false)
  const [streamEnabled, setStreamEnabled] = useState(true)
  const [presets, setPresets] = useState<Preset[]>([])
  const [selectedPresetId, setSelectedPresetId] = useState<number>()
  const [showPresetSelector, setShowPresetSelector] = useState(false)
  const [regexScripts, setRegexScripts] = useState<RegexScript[]>([])
  /** 当前正在流式输出的消息 ID（流结束后清除为 null） */
  const [streamingMsgId, setStreamingMsgId] = useState<number | null>(null)
  /** 发送/重新生成失败时的错误提示（显示在输入框上方，自动清除） */
  const [sendError, setSendError] = useState<string | null>(null)

  const messagesEndRef = useRef<HTMLDivElement>(null)
  const textareaRef = useRef<HTMLTextAreaElement>(null)
  const modelSelectorRef = useRef<HTMLDivElement>(null)
  const presetSelectorRef = useRef<HTMLDivElement>(null)
  const menuRef = useRef<HTMLDivElement>(null)
  // 用 ref 跟踪 sending 状态，避免事件监听器的 stale closure 问题
  const sendingRef = useRef(false)
  /**
   * 稳定 key 映射：tempId → stableKey（字符串）
   * 流式期间 key 保持不变，流结束后服务端 ID 替换 tempId 时 key 也不变，
   * 防止 React 因 key 变化而卸载/重新挂载消息节点，消除闪屏。
   */
  const stableKeyMap = useRef<Map<number, string>>(new Map())

  const { user, variables } = useAppStore()

  // ---- click-outside 关闭下拉菜单 ----
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      const target = event.target as HTMLElement
      if (showModelSelector && modelSelectorRef.current && !modelSelectorRef.current.contains(target)) {
        setShowModelSelector(false)
      }
      if (showPresetSelector && presetSelectorRef.current && !presetSelectorRef.current.contains(target)) {
        setShowPresetSelector(false)
      }
      if (showMenu && menuRef.current && !menuRef.current.contains(target)) {
        setShowMenu(false)
      }
    }
    document.addEventListener('mousedown', handleClickOutside)
    return () => document.removeEventListener('mousedown', handleClickOutside)
  }, [showModelSelector, showPresetSelector, showMenu])

  // ---- 初始化加载 ----
  useEffect(() => {
    loadMessages()
    loadAIConfigs()
    loadPresets()
    loadRegexScripts()
    const settings = parseSettings(conversation.settings)
    if (settings.aiConfigId) setSelectedConfigId(settings.aiConfigId as number)
    // 优先用 conversation.presetId 字段，再降级到 settings.presetId
    const presetId = conversation.presetId ?? (settings.presetId as number | undefined)
    setSelectedPresetId(presetId)
  }, [conversation.id])

  // ---- 消息滚动 ----
  useEffect(() => {
    scrollToBottom()
  }, [messages])

  // ---- 状态栏操作监听（用 ref 跟踪 sending，消除 stale closure） ----
  // handleSendMessage 通过 useCallback 保持稳定引用
  const handleSendMessage = useCallback(async (message: string) => {
    if (!message.trim() || sendingRef.current) return

    const userMessage = message.trim()
    setSending(true)
    sendingRef.current = true
    setSendError(null)

    const tempUserMsg: Message = {
      id: Date.now(),
      conversationId: conversation.id,
      role: 'user',
      content: userMessage,
      tokenCount: 0,
      createdAt: new Date().toISOString(),
    }
    setMessages(prev => [...prev, tempUserMsg])

    const tempAIMsg: Message = {
      id: Date.now() + 1,
      conversationId: conversation.id,
      role: 'assistant',
      content: '',
      tokenCount: 0,
      createdAt: new Date().toISOString(),
    }

    try {
      if (streamEnabled) {
        setMessages(prev => [...prev, tempAIMsg])
        setStreamingMsgId(tempAIMsg.id)
        let fullContent = ''
        for await (const ev of streamSSE(
          `/app/conversation/${conversation.id}/message?stream=true`,
          'POST',
          { content: userMessage }
        )) {
          if (ev.event === 'message') {
            fullContent += ev.data
            setMessages(prev =>
              prev.map(m => m.id === tempAIMsg.id ? { ...m, content: fullContent } : m)
            )
          } else if (ev.event === 'done') {
            break
          } else if (ev.event === 'error') {
            throw new Error(ev.data)
          }
        }
        // 先原地清除 streaming 标记（内容已完整），不触发任何额外渲染
        setStreamingMsgId(null)
        // 后台静默拉取服务端最终数据（同步真实 ID 和 tokenCount），不显示 loading
        loadMessages(true).then(() => {
          conversationApi.getConversationById(conversation.id).then(convResp => {
            onConversationUpdate(convResp.data)
          }).catch(() => {})
        }).catch(() => {})
      } else {
        await conversationApi.sendMessage(conversation.id, { content: userMessage })
        await loadMessages()
        const convResp = await conversationApi.getConversationById(conversation.id)
        onConversationUpdate(convResp.data)
      }
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : '发送消息失败，请重试'
      console.error('发送消息失败:', err)
      setStreamingMsgId(null)
      // 撤回用户消息到输入框，移除临时消息气泡
      setMessages(prev => prev.filter(m => m.id !== tempUserMsg.id && m.id !== tempAIMsg.id))
      setInputValue(userMessage)
      setSendError(msg)
    } finally {
      setSending(false)
      sendingRef.current = false
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [conversation.id, streamEnabled])

  useEffect(() => {
    const handleStatusBarAction = (event: CustomEvent) => {
      const action = event.detail
      if (action && typeof action === 'string' && !sendingRef.current) {
        console.log('[ChatArea] 收到状态栏操作，自动发送消息:', action)
        handleSendMessage(action)
      }
    }
    window.addEventListener('sendMessageFromStatusBar', handleStatusBarAction as EventListener)
    return () => window.removeEventListener('sendMessageFromStatusBar', handleStatusBarAction as EventListener)
  }, [handleSendMessage])

  /**
   * 处理来自 iframe 状态栏的操作：
   *  - fillInput / playerAction → 填充输入框（不发送）
   *  - triggerAction → 解析 /send <text> 并自动发送
   *    命令格式示例："/send 我选择休息|/trigger"
   */
  const handleIframeAction = useCallback((
    type: 'fillInput' | 'playerAction' | 'triggerAction' | 'setChatMessage',
    payload: string,
    messageIndex?: number
  ) => {
    if (type === 'fillInput' || type === 'playerAction') {
      setInputValue(payload)
      textareaRef.current?.focus()
    } else if (type === 'triggerAction') {
      // 解析 ST slash 命令：取第一段 /send 之后的文本
      // 例："  /send 我选择攻击|/trigger  " → "我选择攻击"
      const sendMatch = payload.match(/\/send\s+([^|]+)/i)
      const text = sendMatch ? sendMatch[1].trim() : payload.trim()
      if (text) {
        handleSendMessage(text)
      }
    } else if (type === 'setChatMessage' && messageIndex !== undefined) {
      // 就地替换指定消息内容（不发送 AI 请求）
      // 用于前端卡路由按钮切换页面内容（如 Clannad 路由）
      setMessages(prev => prev.map((msg, i) =>
        i === messageIndex ? { ...msg, content: payload } : msg
      ))
    }
  }, [handleSendMessage])

  // ---- 数据加载 ----
  /**
   * @param silent - true 时不设置 loading 状态（后台静默刷新，不触发整屏 loading）
   */
  const loadMessages = async (silent = false) => {
    try {
      if (!silent) setLoading(true)
      const response = await conversationApi.getMessageList(conversation.id, { page: 1, pageSize: 100 })
      setMessages(response.data.list || [])
    } catch (err) {
      console.error('加载消息失败:', err)
    } finally {
      if (!silent) setLoading(false)
    }
  }

  const loadAIConfigs = async () => {
    try {
      const response = await aiConfigApi.getAIConfigList()
      setAiConfigs(response.data.list.filter((c: AIConfig) => c.isActive))
    } catch (err) {
      console.error('加载 AI 配置失败:', err)
    }
  }

  const loadPresets = async () => {
    try {
      const response = await presetApi.getPresetList({ page: 1, pageSize: 100 })
      setPresets(response.data.list)
    } catch (err) {
      console.error('加载预设失败:', err)
    }
  }

  /**
   * 将角色卡 extensions.regex_scripts（ST 格式）转换为 RegexScript 格式。
   * ST 与我们系统的 placement 值映射：
   *   ST placement 数组含 1 = user_input  → 我们的 0
   *   ST placement 数组含 2 = ai_output   → 我们的 1（markdownOnly 时仅在显示层执行）
   * 使用负数 id 避免与后端 id 冲突。
   */
  const cardRegexScripts = useMemo((): RegexScript[] => {
    const raw = character.extensions?.regex_scripts
    if (!Array.isArray(raw)) return []
    return raw
      .filter((s: any) => !s.disabled)
      .map((s: any, idx: number): RegexScript => {
        // ST placement 是数组，我们取第一个有效值并映射
        const stPlacements: number[] = Array.isArray(s.placement)
          ? s.placement
          : (typeof s.placement === 'number' ? [s.placement] : [2])
        // 含 1 (user_input)且不含 2 → 用户输入；其余默认 ai_output
        const placement = (stPlacements.includes(1) && !stPlacements.includes(2)) ? 0 : 1
        return {
          id: -(idx + 1),
          userId: 0,
          name: s.scriptName || s.name || `card-script-${idx}`,
          findRegex: s.findRegex || '',
          replaceWith: s.replaceString ?? s.replaceWith ?? '',
          trimStrings: Array.isArray(s.trimStrings) ? s.trimStrings : [],
          placement,
          disabled: false,
          markdownOnly: s.markdownOnly || false,
          runOnEdit: s.runOnEdit || false,
          promptOnly: s.promptOnly || false,
          substituteRegex: s.substituteRegex || false,
          minDepth: s.minDepth,
          maxDepth: s.maxDepth,
          scope: 1,
          ownerCharId: character.id,
          order: typeof s.order === 'number' ? s.order : idx,
          extensions: {},
          createdAt: '',
          updatedAt: '',
        }
      })
  }, [character.id, character.extensions])

  const loadRegexScripts = async () => {
    try {
      // 并行加载：全局脚本（scope=0）+ 当前角色专属脚本（scope=1, ownerCharId=character.id）
      const globalScope = 0
      const charScope = 1
      const [globalResp, charResp] = await Promise.all([
        regexAPI.getList({ page: 1, pageSize: 200, scope: globalScope }),
        regexAPI.getList({ page: 1, pageSize: 200, scope: charScope, ownerCharId: character.id }),
      ])
      const globalScripts: RegexScript[] = (globalResp.data.list || []).filter((s: RegexScript) => !s.disabled)
      const charScripts: RegexScript[] = (charResp.data.list || []).filter((s: RegexScript) => !s.disabled)
      // 合并并按 order 去重（以 id 为主键）
      const merged = [...globalScripts, ...charScripts]
      const seen = new Set<number>()
      setRegexScripts(merged.filter(s => {
        if (seen.has(s.id)) return false
        seen.add(s.id)
        return true
      }))
    } catch (err) {
      console.error('加载正则脚本失败:', err)
    }
  }

  /** 合并卡片内置脚本（优先执行）和后端脚本，按 findRegex 去重防止重复执行 */
  const allRegexScripts = useMemo(() => {
    const seen = new Set<string>()
    const result: RegexScript[] = []
    // 卡片内置脚本优先（权威来源），防止与后端导入的同一脚本重复
    for (const s of cardRegexScripts) {
      if (s.findRegex && !seen.has(s.findRegex)) {
        seen.add(s.findRegex)
        result.push(s)
      }
    }
    // 后端脚本（跳过已由卡片内置脚本覆盖的条目）
    for (const s of regexScripts) {
      if (s.findRegex && !seen.has(s.findRegex)) {
        seen.add(s.findRegex)
        result.push(s)
      }
    }
    return result
  }, [cardRegexScripts, regexScripts])

  // ---- 设置更新 ----
  const handlePresetChange = async (presetId: number | null) => {
    try {
      const settings = parseSettings(conversation.settings)
      if (presetId === null) delete settings.presetId
      else settings.presetId = presetId
      await conversationApi.updateConversationSettings(conversation.id, { settings })
      setSelectedPresetId(presetId ?? undefined)
      setShowPresetSelector(false)
      const convResp = await conversationApi.getConversationById(conversation.id)
      onConversationUpdate(convResp.data)
    } catch (err) {
      console.error('更新预设失败:', err)
      alert('更新失败，请重试')
    }
  }

  const handleModelChange = async (configId: number | null) => {
    try {
      const settings = parseSettings(conversation.settings)
      if (configId === null) delete settings.aiConfigId
      else settings.aiConfigId = configId
      await conversationApi.updateConversationSettings(conversation.id, { settings })
      setSelectedConfigId(configId ?? undefined)
      setShowModelSelector(false)
      const convResp = await conversationApi.getConversationById(conversation.id)
      onConversationUpdate(convResp.data)
    } catch (err) {
      console.error('更新模型配置失败:', err)
      alert('更新失败，请重试')
    }
  }

  // ---- 消息操作 ----
  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
  }

  const handleSend = async () => {
    if (!inputValue.trim() || sending) return
    const userMessage = inputValue.trim()
    setInputValue('')
    await handleSendMessage(userMessage)
  }

  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === 'Enter' && !e.shiftKey && !sending && inputValue.trim()) {
      e.preventDefault()
      handleSend()
    }
  }

  const handleCopyMessage = (content: string, id: number) => {
    navigator.clipboard.writeText(content)
    setCopiedId(id)
    setTimeout(() => setCopiedId(null), 2000)
  }

  const handleRegenerateResponse = async () => {
    if (messages.length === 0 || sending) return
    const hasAssistantMsg = messages.some(m => m.role === 'assistant')
    if (!hasAssistantMsg) return

    setSending(true)
    sendingRef.current = true
    setSendError(null)

    // 记录被移除的最后一条 assistant 消息，失败时可恢复
    const lastAssistantIndex = [...messages].map(m => m.role).lastIndexOf('assistant')
    const removedMsg = lastAssistantIndex !== -1 ? messages[lastAssistantIndex] : null
    if (lastAssistantIndex !== -1) {
      setMessages(prev => prev.filter((_, i) => i !== lastAssistantIndex))
    }

    const tempAIMsg: Message = {
      id: Date.now(),
      conversationId: conversation.id,
      role: 'assistant',
      content: '',
      tokenCount: 0,
      createdAt: new Date().toISOString(),
    }

    try {
      if (streamEnabled) {
        setMessages(prev => [...prev, tempAIMsg])
        setStreamingMsgId(tempAIMsg.id)
        let fullContent = ''
        for await (const ev of streamSSE(
          `/app/conversation/${conversation.id}/regenerate?stream=true`,
          'POST'
        )) {
          if (ev.event === 'message') {
            fullContent += ev.data
            setMessages(prev =>
              prev.map(m => m.id === tempAIMsg.id ? { ...m, content: fullContent } : m)
            )
          } else if (ev.event === 'done') {
            break
          } else if (ev.event === 'error') {
            throw new Error(ev.data)
          }
        }
        // 先原地清除 streaming 标记，再后台静默同步
        setStreamingMsgId(null)
        loadMessages(true).then(() => {
          conversationApi.getConversationById(conversation.id).then(convResp => {
            onConversationUpdate(convResp.data)
          }).catch(() => {})
        }).catch(() => {})
      } else {
        await conversationApi.regenerateMessage(conversation.id)
        await loadMessages()
        const convResp = await conversationApi.getConversationById(conversation.id)
        onConversationUpdate(convResp.data)
      }
    } catch (err: unknown) {
      const msg = err instanceof Error ? err.message : '重新生成失败，请重试'
      console.error('重新生成失败:', err)
      setStreamingMsgId(null)
      // 移除临时 AI 消息，恢复原来被删除的 assistant 消息
      setMessages(prev => {
        const withoutTemp = prev.filter(m => m.id !== tempAIMsg.id)
        return removedMsg ? [...withoutTemp, removedMsg] : withoutTemp
      })
      setSendError(msg)
    } finally {
      setSending(false)
      sendingRef.current = false
    }
  }

  const handleDeleteConversation = async () => {
    if (!confirm('确定要删除这个对话吗？')) return
    try {
      await conversationApi.deleteConversation(conversation.id)
      navigate('/chat')
    } catch (err) {
      console.error('删除对话失败:', err)
      alert('删除失败')
    }
  }

  const handleExportConversation = () => {
    const content = messages.map(msg => `[${msg.role}] ${msg.content}`).join('\n\n')
    const blob = new Blob([content], { type: 'text/plain' })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `${conversation.title}.txt`
    a.click()
    URL.revokeObjectURL(url)
  }

  const formatTime = (dateString: string) => {
    const date = new Date(dateString)
    const now = new Date()
    const diff = now.getTime() - date.getTime()
    const minutes = Math.floor(diff / 60000)
    const hours = Math.floor(diff / 3600000)
    const days = Math.floor(diff / 86400000)
    if (minutes < 1) return '刚刚'
    if (minutes < 60) return `${minutes}分钟前`
    if (hours < 24) return `${hours}小时前`
    if (days < 7) return `${days}天前`
    return date.toLocaleDateString('zh-CN')
  }

  const selectedConfig = aiConfigs.find(c => c.id === selectedConfigId)
  const selectedPreset = presets.find(p => p.id === selectedPresetId)
  const lastAssistantMsgId = [...messages].reverse().find(m => m.role === 'assistant')?.id

  /** 从角色卡 extensions 中提取前端卡配置（memo 缓存，避免不必要重计算） */
  const frontendCard = useMemo(() => extractFrontendCard(character.extensions), [character.id, character.extensions])

  /**
   * 前端卡用的最新消息内容：始终取消息列表最后一条的原始内容。
   * 流式期间每个 token 都会变，但前端卡 iframe 本身有防抖（isStreaming=true 时冻结刷新）。
   */
  const latestMessageContent = messages.length > 0 ? messages[messages.length - 1].content : ''
  const latestMessageIndex = Math.max(0, messages.length - 1)

  /**
   * 稳定的消息内容数组引用：只有消息数量或内容实际变化时才重建，
   * 避免每个 SSE token 都产生新数组引用，防止 StatusBarIframe 无限重建。
   */
  const allMessageContents = useMemo(
    () => messages.map(m => m.content),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [messages.length, messages.map(m => m.content).join('\x00')]
  )

  /**
   * 为每条消息分配稳定 key：
   * - 如果 stableKeyMap 中已有该消息 ID 的 key，直接复用（保持 DOM 节点稳定）
   * - 否则分配新 key（conversationId + 消息在列表中的位置索引，流式期间不会变）
   * 这样即使服务端刷新后 msg.id 从 tempId 变为真实 ID，React key 也不变，不会触发重新挂载。
   */
  const getStableKey = useCallback((msg: Message, index: number): string => {
    if (stableKeyMap.current.has(msg.id)) {
      return stableKeyMap.current.get(msg.id)!
    }
    const key = `${conversation.id}-${index}`
    stableKeyMap.current.set(msg.id, key)
    return key
  }, [conversation.id])

  return (
    <div className="flex-1 flex flex-col min-w-0">
      {/* 顶部工具栏 */}
      <div className="px-4 py-3 glass border-b border-white/10">
        <div className="flex items-center justify-between gap-3">
          <div className="min-w-0 flex-1">
            <h2 className="text-base font-semibold truncate">{conversation.title}</h2>
            <p className="text-xs text-white/50 truncate">与 {character.name} 对话中</p>
          </div>

          <div className="flex items-center gap-1 flex-shrink-0">
            {/* 模型选择器 */}
            <div className="relative" ref={modelSelectorRef}>
              <button
                onClick={() => {
                  setShowModelSelector(v => !v)
                  setShowPresetSelector(false)
                  setShowMenu(false)
                }}
                className={`flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all cursor-pointer
                  ${showModelSelector ? 'bg-white/15 text-white' : 'glass-hover text-white/70 hover:text-white'}`}
                title="切换模型"
              >
                <Zap className="w-3.5 h-3.5 text-yellow-400 flex-shrink-0" />
                <span className="max-w-[80px] truncate">
                  {selectedConfig ? selectedConfig.name : '默认模型'}
                </span>
                <ChevronDown className={`w-3 h-3 flex-shrink-0 transition-transform ${showModelSelector ? 'rotate-180' : ''}`} />
              </button>

              {showModelSelector && (
                <div className="absolute right-0 top-full mt-1 z-50 w-56 glass rounded-xl shadow-2xl border border-white/15 overflow-hidden">
                  <div className="p-1">
                    <button
                      onClick={() => handleModelChange(null)}
                      className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left cursor-pointer transition-all
                        ${!selectedConfigId ? 'bg-primary/20 text-white' : 'text-white/60 hover:bg-white/10 hover:text-white'}`}
                    >
                      <span className="flex-1">默认模型</span>
                      {!selectedConfigId && <Check className="w-3.5 h-3.5 text-primary" />}
                    </button>
                    {aiConfigs.length > 0 && <div className="my-1 border-t border-white/10" />}
                    {aiConfigs.map(config => (
                      <button
                        key={config.id}
                        onClick={() => handleModelChange(config.id)}
                        className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left cursor-pointer transition-all
                          ${selectedConfigId === config.id ? 'bg-primary/20 text-white' : 'text-white/70 hover:bg-white/10 hover:text-white'}`}
                      >
                        <div className="flex-1 min-w-0">
                          <div className="font-medium truncate">{config.name}</div>
                          <div className="text-xs text-white/40 truncate">{config.provider} · {config.defaultModel}</div>
                        </div>
                        {selectedConfigId === config.id && <Check className="w-3.5 h-3.5 text-primary flex-shrink-0" />}
                      </button>
                    ))}
                    {aiConfigs.length === 0 && (
                      <div className="px-3 py-3 text-xs text-white/40 text-center">暂无可用配置</div>
                    )}
                  </div>
                </div>
              )}
            </div>

            {/* 预设选择器 */}
            <div className="relative" ref={presetSelectorRef}>
              <button
                onClick={() => {
                  setShowPresetSelector(v => !v)
                  setShowModelSelector(false)
                  setShowMenu(false)
                }}
                className={`flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all cursor-pointer
                  ${showPresetSelector ? 'bg-white/15 text-white' : 'glass-hover text-white/70 hover:text-white'}`}
                title="选择预设"
              >
                <Settings className="w-3.5 h-3.5 text-purple-400 flex-shrink-0" />
                <span className="max-w-[80px] truncate">
                  {selectedPreset ? selectedPreset.name : '默认预设'}
                </span>
                <ChevronDown className={`w-3 h-3 flex-shrink-0 transition-transform ${showPresetSelector ? 'rotate-180' : ''}`} />
              </button>

              {showPresetSelector && (
                <div className="absolute right-0 top-full mt-1 z-50 w-56 glass rounded-xl shadow-2xl border border-white/15 overflow-hidden">
                  <div className="p-1">
                    <button
                      onClick={() => handlePresetChange(null)}
                      className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left cursor-pointer transition-all
                        ${!selectedPresetId ? 'bg-primary/20 text-white' : 'text-white/60 hover:bg-white/10 hover:text-white'}`}
                    >
                      <span className="flex-1">默认预设</span>
                      {!selectedPresetId && <Check className="w-3.5 h-3.5 text-primary" />}
                    </button>
                    {presets.length > 0 && <div className="my-1 border-t border-white/10" />}
                    {presets.map(preset => (
                      <button
                        key={preset.id}
                        onClick={() => handlePresetChange(preset.id)}
                        className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left cursor-pointer transition-all
                          ${selectedPresetId === preset.id ? 'bg-primary/20 text-white' : 'text-white/70 hover:bg-white/10 hover:text-white'}`}
                      >
                        <div className="flex-1 min-w-0">
                          <div className="font-medium truncate">{preset.name}</div>
                          <div className="text-xs text-white/40">温度 {preset.temperature}</div>
                        </div>
                        {selectedPresetId === preset.id && <Check className="w-3.5 h-3.5 text-primary flex-shrink-0" />}
                      </button>
                    ))}
                    {presets.length === 0 && (
                      <div className="px-3 py-3 text-xs text-white/40 text-center">暂无可用预设</div>
                    )}
                  </div>
                </div>
              )}
            </div>

            <div className="w-px h-5 bg-white/10 mx-1" />

            {/* 流式传输切换 */}
            <button
              onClick={() => setStreamEnabled(v => !v)}
              className={`p-1.5 rounded-lg cursor-pointer transition-all ${
                streamEnabled
                  ? 'text-emerald-400 bg-emerald-400/10 hover:bg-emerald-400/20'
                  : 'text-white/30 glass-hover hover:text-white/60'
              }`}
              title={streamEnabled ? '流式传输已启用（点击关闭）' : '流式传输已关闭（点击开启）'}
            >
              <Waves className="w-4 h-4" />
            </button>

            {/* 更多菜单 */}
            <div className="relative" ref={menuRef}>
              <button
                onClick={() => {
                  setShowMenu(v => !v)
                  setShowModelSelector(false)
                  setShowPresetSelector(false)
                }}
                className={`p-1.5 rounded-lg cursor-pointer transition-all
                  ${showMenu ? 'bg-white/15 text-white' : 'glass-hover text-white/60 hover:text-white'}`}
              >
                <MoreVertical className="w-4 h-4" />
              </button>

              {showMenu && (
                <div className="absolute right-0 top-full mt-1 z-50 w-44 glass rounded-xl shadow-2xl border border-white/15 overflow-hidden">
                  <div className="p-1">
                    <button
                      onClick={() => { handleExportConversation(); setShowMenu(false) }}
                      className="w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-left cursor-pointer text-white/70 hover:bg-white/10 hover:text-white transition-all"
                    >
                      <Download className="w-4 h-4" />
                      导出对话
                    </button>
                    <div className="my-1 border-t border-white/10" />
                    <button
                      onClick={() => { handleDeleteConversation(); setShowMenu(false) }}
                      className="w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-left cursor-pointer text-red-400 hover:bg-red-400/10 transition-all"
                    >
                      <Trash2 className="w-4 h-4" />
                      删除对话
                    </button>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>

      {/* 前端卡（顶部，position='top' 或未设置） */}
      {frontendCard && frontendCard.enabled !== false && frontendCard.position !== 'bottom' && (
        <div className="px-4 pt-3 flex-shrink-0 min-w-0 overflow-x-hidden">
          <StatusBarIframe
            rawMessage={latestMessageContent}
            allMessages={allMessageContents}
            messageIndex={latestMessageIndex}
            htmlContent={frontendCard.html}
            minHeight={500}
            onAction={handleIframeAction}
          />
        </div>
      )}

      {/* 消息列表 */}
      <div className="flex-1 overflow-y-auto px-4 py-6 space-y-5">
        {loading ? (
          <div className="flex justify-center items-center py-12">
            <div className="flex gap-1.5">
              {[0, 0.15, 0.3].map((delay, i) => (
                <div key={i} className="w-2 h-2 bg-primary/60 rounded-full animate-bounce" style={{ animationDelay: `${delay}s` }} />
              ))}
            </div>
          </div>
        ) : messages.length === 0 ? (
          <div className="flex flex-col items-center justify-center py-16 text-center">
            {character.avatar && (
              <img src={character.avatar} alt={character.name} className="w-16 h-16 rounded-full object-cover mb-4 ring-2 ring-white/10" />
            )}
            <p className="text-white/50 text-sm">发送第一条消息，开始和 {character.name} 对话吧</p>
          </div>
        ) : (
          messages.map((msg, msgIndex) => {
            const isLastAssistant = msg.id === lastAssistantMsgId
            return (
              <div
                key={getStableKey(msg, msgIndex)}
                className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'} group`}
              >
                {/* 助手头像 */}
                {msg.role === 'assistant' && (
                  <div className="flex-shrink-0 mr-2.5 mt-1">
                    {character.avatar ? (
                      <img src={character.avatar} alt={character.name} className="w-8 h-8 rounded-full object-cover ring-1 ring-white/10" />
                    ) : (
                      <div className="w-8 h-8 rounded-full bg-primary/30 flex items-center justify-center text-xs font-medium">
                        {character.name[0]}
                      </div>
                    )}
                  </div>
                )}

                <div className={`min-w-0 flex flex-col ${msg.role === 'user' ? 'max-w-[70%] items-end' : 'w-[70%] items-start'}`}>
                  {msg.role === 'assistant' && (
                    <span className="text-xs text-white/40 mb-1 ml-1">{character.name}</span>
                  )}

                  <div
                    className={`relative px-4 py-3 rounded-2xl ${
                      msg.role === 'user'
                        ? 'bg-primary/25 border border-primary/20 rounded-br-md'
                        : 'glass rounded-bl-md w-full'
                    }`}
                    style={{ wordBreak: 'break-word', overflowWrap: 'anywhere' }}
                  >
                    <MessageContent
                      content={msg.content}
                      role={msg.role as 'user' | 'assistant'}
                      messageIndex={msgIndex}
                      regexDepth={messages.length - 1 - msgIndex}
                      characterName={character.name}
                      userName={variables.user || user?.username || ''}
                      regexScripts={allRegexScripts}
                      allMessages={allMessageContents}
                      onChoiceSelect={(choice) => {
                        setInputValue(choice)
                        textareaRef.current?.focus()
                      }}
                      onAction={handleIframeAction}
                      isStreaming={msg.id === streamingMsgId}
                    />
                  </div>

                  {/* 底部操作栏 */}
                  <div className={`flex items-center gap-1 mt-1 px-1 opacity-0 group-hover:opacity-100 transition-opacity ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}>
                    <span className="text-xs text-white/25">{formatTime(msg.createdAt)}</span>
                    <button
                      onClick={() => handleCopyMessage(msg.content, msg.id)}
                      className="p-1 rounded hover:bg-white/10 transition-colors cursor-pointer"
                      title="复制"
                    >
                      {copiedId === msg.id
                        ? <Check className="w-3.5 h-3.5 text-emerald-400" />
                        : <Copy className="w-3.5 h-3.5 text-white/40 hover:text-white/70" />
                      }
                    </button>
                    {msg.role === 'assistant' && isLastAssistant && (
                      <button
                        onClick={handleRegenerateResponse}
                        disabled={sending}
                        className="p-1 rounded hover:bg-white/10 transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
                        title="重新生成"
                      >
                        <RefreshCw className={`w-3.5 h-3.5 text-white/40 hover:text-white/70 ${sending ? 'animate-spin' : ''}`} />
                      </button>
                    )}
                  </div>
                </div>

                {/* 用户头像 */}
                {msg.role === 'user' && (
                  <div className="flex-shrink-0 ml-2.5 mt-1 w-8 h-8 rounded-full bg-white/10 flex items-center justify-center text-xs">我</div>
                )}
              </div>
            )
          })
        )}

        {/* 发送中动画（非流式模式下显示） */}
        {sending && !streamEnabled && (
          <div className="flex justify-start">
            <div className="flex-shrink-0 mr-2.5 mt-1 w-8 h-8 rounded-full bg-primary/30 flex items-center justify-center text-xs">
              {character.name[0]}
            </div>
            <div className="glass rounded-2xl rounded-bl-md px-4 py-3">
              <div className="flex items-center gap-1.5">
                {[0, 0.2, 0.4].map((delay, i) => (
                  <div key={i} className="w-1.5 h-1.5 bg-primary/60 rounded-full animate-bounce" style={{ animationDelay: `${delay}s` }} />
                ))}
              </div>
            </div>
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>

      {/* 前端卡（底部，position='bottom' 时显示） */}
      {frontendCard && frontendCard.enabled !== false && frontendCard.position === 'bottom' && (
        <div className="px-4 pb-2 flex-shrink-0 min-w-0 overflow-x-hidden">
          <StatusBarIframe
            rawMessage={latestMessageContent}
            allMessages={allMessageContents}
            messageIndex={latestMessageIndex}
            htmlContent={frontendCard.html}
            minHeight={500}
            onAction={handleIframeAction}
          />
        </div>
      )}

      {/* 输入区域 */}
      <div className="px-4 pb-4 pt-2 glass border-t border-white/10">
        {/* 发送失败错误提示 */}
        {sendError && (
          <div className="flex items-center justify-between gap-2 mb-2 px-3 py-2 rounded-lg bg-red-500/10 border border-red-500/20 text-xs text-red-400">
            <span className="flex-1 truncate">{sendError}</span>
            <button
              onClick={() => setSendError(null)}
              className="flex-shrink-0 hover:text-red-300 transition-colors cursor-pointer"
              title="关闭"
            >
              ✕
            </button>
          </div>
        )}
        <div className="flex items-end gap-2">
          <button className="p-2.5 glass-hover rounded-xl cursor-not-allowed opacity-30" title="附件（开发中）" disabled>
            <Paperclip className="w-5 h-5" />
          </button>

          <div className="flex-1 glass rounded-2xl px-4 py-2.5 focus-within:ring-1 focus-within:ring-primary/50 transition-all">
            <textarea
              ref={textareaRef}
              value={inputValue}
              onChange={(e) => {
                setInputValue(e.target.value)
                if (sendError) setSendError(null)
                if (textareaRef.current) {
                  textareaRef.current.style.height = 'auto'
                  textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 120) + 'px'
                }
              }}
              onKeyDown={handleKeyDown}
              placeholder={sending ? 'AI 正在思考...' : `和 ${character.name} 说点什么... (Enter 发送，Shift+Enter 换行)`}
              rows={1}
              className="w-full bg-transparent resize-none focus:outline-none text-sm placeholder:text-white/25"
              style={{ maxHeight: '120px', minHeight: '22px' }}
              disabled={sending}
            />
          </div>

          <button className="p-2.5 glass-hover rounded-xl cursor-not-allowed opacity-30" title="语音（开发中）" disabled>
            <Mic className="w-5 h-5" />
          </button>

          <button
            onClick={handleSend}
            disabled={!inputValue.trim() || sending}
            className="p-2.5 bg-gradient-to-br from-primary to-secondary rounded-xl hover:opacity-90 active:scale-95 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:active:scale-100"
            title="发送 (Enter)"
          >
            <Send className="w-5 h-5" />
          </button>
        </div>

        <div className="flex items-center justify-between mt-2 px-1 text-xs text-white/25">
          <span>{conversation.messageCount} 条消息 · {conversation.tokenCount} tokens</span>
          {sending && <span className="text-primary/70 animate-pulse">AI 正在思考...</span>}
        </div>
      </div>
    </div>
  )
}
