import { useEffect, useMemo, useRef, useState } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import {
  Avatar,
  Button,
  Dropdown,
  Empty,
  Input,
  List,
  Modal,
  Popover,
  Select,
  Space,
  Spin,
  Tag,
  Typography,
  message,
} from 'antd'
import {
  FolderOutlined,
  EditOutlined,
  DeleteOutlined,
  MoreOutlined,
  PlusOutlined,
  RobotOutlined,
  SearchOutlined,
  ArrowUpOutlined,
  UserOutlined,
  DownOutlined,
  CopyOutlined,
  CheckOutlined,
} from '@ant-design/icons'
import ReactMarkdown from 'react-markdown'
import Highlighter from 'react-highlight-words'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
import 'highlight.js/styles/github.css'
import { createChatSession, deleteChatMessage, deleteChatSession, getChatMessages, getChatSessions, searchChatMessages, sendChatMessageStream, updateChatSessionTitle } from '@/api/chat'
import { getMyProjects } from '@/api/project'
import { getLLMModelConfigs } from '@/api/llmModelConfigs'
import useUserStore from '@/stores/userStore'
import './Chat.css'

const { TextArea } = Input
const { Text } = Typography

const CITATION_RE = /\[(\d+)\]/g

// rehype 插件：将正文中的 [n] 引用编号转换为上角标 <sup>，便于与正文区分。
// 跳过 code/pre 节点，避免破坏代码块中的方括号内容。
function rehypeCitationSup() {
  const walk = (node, inCode) => {
    if (!node.children) return
    const nextChildren = []
    for (const child of node.children) {
      const childInCode = inCode || child.tagName === 'code' || child.tagName === 'pre'
      if (child.type === 'text' && !childInCode && CITATION_RE.test(child.value)) {
        CITATION_RE.lastIndex = 0
        let lastIndex = 0
        let match
        while ((match = CITATION_RE.exec(child.value)) !== null) {
          if (match.index > lastIndex) {
            nextChildren.push({ type: 'text', value: child.value.slice(lastIndex, match.index) })
          }
          nextChildren.push({
            type: 'element',
            tagName: 'sup',
            properties: { className: ['chat-citation'], 'data-citation-id': match[1] },
            children: [{ type: 'text', value: `[${match[1]}]` }],
          })
          lastIndex = match.index + match[0].length
        }
        if (lastIndex < child.value.length) {
          nextChildren.push({ type: 'text', value: child.value.slice(lastIndex) })
        }
      } else {
        if (child.type === 'element') walk(child, childInCode)
        nextChildren.push(child)
      }
    }
    node.children = nextChildren
  }
  return (tree) => walk(tree, false)
}

function formatTime(value) {
  if (!value) return ''
  return new Date(value).toLocaleString('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function getDateKey(value) {
  if (!value) return 'unknown'
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return 'unknown'
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}

function formatDateGroup(value) {
  if (!value || value === 'unknown') return '未知日期'
  const date = new Date(`${value}T00:00:00`)
  const today = new Date()
  const yesterday = new Date()
  yesterday.setDate(today.getDate() - 1)
  const todayKey = getDateKey(today)
  const yesterdayKey = getDateKey(yesterday)

  if (value === todayKey) return '今天'
  if (value === yesterdayKey) return '昨天'
  if (date.getFullYear() === today.getFullYear()) {
    return `${date.getMonth() + 1}月${date.getDate()}日`
  }
  return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
}

function stripMarkdown(text) {
  return (text || '')
    .replace(/```[\s\S]*?```/g, ' ')
    .replace(/[#>*_`~\-\[\]()]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

function buildDocumentPreviewUrl(projectId, filePath) {
  if (!projectId || !filePath) return ''
  return `/projects/${projectId}/docs?file=${encodeURIComponent(filePath)}`
}

function openDocument(ref, projectId) {
  const previewUrl = buildDocumentPreviewUrl(
    ref?.project_id || projectId,
    ref?.file_path
  )
  if (previewUrl) window.open(previewUrl, '_blank', 'noopener,noreferrer')
}

function getReferenceExcerpt(ref) {
  return (ref?.excerpt || ref?.anchor_text || '').trim()
}

function SearchHighlight({ text, keyword }) {
  const value = text || ''
  if (!keyword) return value
  return (
    <Highlighter
      autoEscape
      highlightClassName="chat-search-highlight"
      searchWords={[keyword]}
      textToHighlight={value}
    />
  )
}

async function copyText(text) {
  const value = text || ''
  try {
    if (navigator.clipboard?.writeText) {
      await navigator.clipboard.writeText(value)
      return true
    }
  } catch {
    // 回退到 execCommand
  }
  try {
    const textarea = document.createElement('textarea')
    textarea.value = value
    textarea.style.position = 'fixed'
    textarea.style.opacity = '0'
    document.body.appendChild(textarea)
    textarea.select()
    const ok = document.execCommand('copy')
    document.body.removeChild(textarea)
    return ok
  } catch {
    return false
  }
}

function MessageActions({ content, onCopy, onDelete }) {
  const [copied, setCopied] = useState(false)
  const handleCopy = async () => {
    const ok = await copyText(content)
    if (ok) {
      setCopied(true)
      window.setTimeout(() => setCopied(false), 1500)
    } else {
      message.error('复制失败')
    }
    onCopy?.(ok)
  }
  return (
    <div className="chat-message-actions">
      <button type="button" className="chat-message-action" onClick={handleCopy} aria-label="复制">
        {copied ? <CheckOutlined /> : <CopyOutlined />}
      </button>
      {onDelete && (
        <button type="button" className="chat-message-action danger" onClick={onDelete} aria-label="删除">
          <DeleteOutlined />
        </button>
      )}
    </div>
  )
}

function Chat() {
  const navigate = useNavigate()
  const location = useLocation()
  const { user: currentUser } = useUserStore()
  const [searchParams, setSearchParams] = useSearchParams()
  const [sessions, setSessions] = useState([])
  const [projects, setProjects] = useState([])
  const [models, setModels] = useState([])
  const [currentSession, setCurrentSession] = useState(null)
  const [messages, setMessages] = useState([])
  const [loadingSessions, setLoadingSessions] = useState(false)
  const [loadingMessages, setLoadingMessages] = useState(false)
  const [sending, setSending] = useState(false)
  const [inputValue, setInputValue] = useState('')
  const [searchVisible, setSearchVisible] = useState(false)
  const [searchKeyword, setSearchKeyword] = useState('')
  const [searchResults, setSearchResults] = useState([])
  const [searchLoading, setSearchLoading] = useState(false)
  const [searchedKeyword, setSearchedKeyword] = useState('')
  const [hasSearched, setHasSearched] = useState(false)
  const [newQuestion, setNewQuestion] = useState('')
  const [newProjectId, setNewProjectId] = useState(undefined)
  const [newModelId, setNewModelId] = useState(undefined)
  const [projectPickerOpen, setProjectPickerOpen] = useState(false)
  const [renameVisible, setRenameVisible] = useState(false)
  const [renameSession, setRenameSession] = useState(null)
  const [renameTitle, setRenameTitle] = useState('')
  const messageRefs = useRef(new Map())
  const messagesEndRef = useRef(null)
  const skipNextOpenSessionRef = useRef(null)
  const searchRequestIdRef = useRef(0)
  const newMode = location.pathname === '/chat/new'

  const sessionId = useMemo(() => {
    const value = searchParams.get('session_id') || searchParams.get('sessionId')
    return value ? Number(value) : null
  }, [searchParams])

  const currentProject = useMemo(
    () => projects.find((item) => item.id === newProjectId),
    [projects, newProjectId]
  )
  const currentModel = useMemo(
    () => models.find((item) => item.config_id === newModelId),
    [models, newModelId]
  )
  const canCreateSession = Boolean(newQuestion.trim() && currentProject && currentModel)
  const canSendMessage = Boolean(inputValue.trim() && currentSession)

  // 与全站侧边栏/个人资料一致的用户头像：avatar 字段是相对路径时转换为 API 端点
  const userAvatarUrl = useMemo(() => {
    const avatar = currentUser?.avatar
    if (!avatar) return null
    if (avatar.startsWith('http')) return avatar
    const parts = avatar.split('/')
    if (parts.length >= 3) {
      return `/api/v1/auth/avatar/${parts[0]}/${parts[2]}`
    }
    return null
  }, [currentUser])
  const userDisplayName = currentUser?.nickname || currentUser?.username

  const groupedSessions = useMemo(() => {
    const groups = []
    const groupMap = new Map()
    sessions.forEach((item) => {
      const key = getDateKey(item.updated_at || item.created_at)
      if (!groupMap.has(key)) {
        const group = {
          key,
          label: formatDateGroup(key),
          items: [],
        }
        groupMap.set(key, group)
        groups.push(group)
      }
      groupMap.get(key).items.push(item)
    })
    return groups
  }, [sessions])

  const loadProjects = async () => {
    try {
      const res = await getMyProjects()
      setProjects(res.data || [])
    } catch (error) {
      console.error(error)
    }
  }

  const loadModels = async () => {
    try {
      const res = await getLLMModelConfigs({ page: 1, page_size: 100, model_type: 'chat', is_active: true })
      const nextModels = res.data || []
      setModels(nextModels)
      const defaultModel = nextModels.find((item) => item.is_default) || nextModels[0]
      setNewModelId((current) => current || defaultModel?.config_id)
    } catch (error) {
      console.error(error)
    }
  }

  const loadSessions = async () => {
    setLoadingSessions(true)
    try {
      const res = await getChatSessions()
      setSessions(res.data || [])
    } catch (error) {
      message.error('加载对话列表失败')
    } finally {
      setLoadingSessions(false)
    }
  }

  const openSession = async (id, extraQuery = {}) => {
    if (!id) return
    const nextQuery = new URLSearchParams()
    nextQuery.set('session_id', String(id))
    if (extraQuery.message_id) {
      nextQuery.set('message_id', String(extraQuery.message_id))
    }
    navigate({ pathname: '/chat', search: `?${nextQuery.toString()}` }, { replace: true })
    setLoadingMessages(true)
    try {
      const res = await getChatMessages(id)
      setMessages(res.data || [])
      setCurrentSession(sessions.find((item) => item.session_id === id) || null)
    } catch (error) {
      message.error('加载对话失败')
    } finally {
      setLoadingMessages(false)
    }
  }

  useEffect(() => {
    loadProjects()
    loadModels()
    loadSessions()
  }, [])

  useEffect(() => {
    if (newMode) {
      setCurrentSession(null)
      setMessages([])
    }
  }, [newMode])

  useEffect(() => {
    if (!sessionId || newMode) {
      if (!newMode) {
        setCurrentSession(null)
        setMessages([])
      }
      return
    }
    if (skipNextOpenSessionRef.current === sessionId) {
      skipNextOpenSessionRef.current = null
      return
    }
    openSession(sessionId)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sessionId, sessions.length, newMode])

  // 新会话打开或消息生成/更新时，自动滚动到对话底部
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })
  }, [messages, currentSession])

  useEffect(() => {
    const target = searchParams.get('message_id')
    if (!target) return
    const targetMessage = messageRefs.current.get(String(target))
    if (!targetMessage) return

    targetMessage.scrollIntoView({ behavior: 'smooth', block: 'center' })
    const nextSearchParams = new URLSearchParams(searchParams)
    nextSearchParams.delete('message_id')
    setSearchParams(nextSearchParams, { replace: true })
  }, [messages, searchParams, setSearchParams])

  const handleStartNew = () => {
    setNewQuestion('')
    setNewProjectId(undefined)
    setNewModelId(models.find((item) => item.is_default)?.config_id || models[0]?.config_id)
    navigate('/chat/new')
  }

  const handleCreateSession = async () => {
    if (sending) return
    const question = newQuestion.trim()
    if (!question) {
      message.warning('请输入问题')
      return
    }
    if (!newProjectId) {
      message.warning('请选择项目')
      return
    }
    if (!newModelId) {
      message.warning('请选择模型')
      return
    }
    const title = question.length > 24 ? `${question.slice(0, 24)}...` : question
    setSending(true)
    let session = null
    try {
      const res = await createChatSession(newProjectId, newModelId, title)
      session = res.data
    } catch (error) {
      message.error(error.response?.data?.detail || error.message || '新建对话失败')
      setSending(false)
      return
    }

    const nextSession = {
      session_id: session.session_id,
      project_id: session.project_id ?? newProjectId,
      llm_config_id: session.llm_config_id ?? newModelId,
      title: session.title || title,
      created_at: session.created_at,
      updated_at: session.updated_at || session.created_at,
    }
    const optimisticMessage = {
      id: `tmp-${Date.now()}`,
      role: 'user',
      content: question,
      created_at: new Date().toISOString(),
    }

    setSessions((prev) => [
      nextSession,
      ...prev.filter((item) => item.session_id !== nextSession.session_id),
    ])
    setCurrentSession(nextSession)
    setNewQuestion('')
    skipNextOpenSessionRef.current = session.session_id
    navigate(`/chat?session_id=${session.session_id}`, { replace: true })

    try {
      await sendMessageWithStream(nextSession, question, {
        initialMessages: [optimisticMessage],
        initialUserMessageId: optimisticMessage.id,
        includeUserMessage: false,
      })
      await loadSessions()
    } catch (error) {
      message.error(error.response?.data?.detail || error.message || '对话已创建，但首条消息发送失败')
    } finally {
      setSending(false)
    }
  }

  const handleSend = async () => {
    if (sending) return
    const content = inputValue.trim()
    if (!content || !currentSession) return
    setSending(true)
    setInputValue('')
    try {
      await sendMessageWithStream(currentSession, content)
      await loadSessions()
    } catch (error) {
      message.error(error.response?.data?.detail || error.message || '发送消息失败')
    } finally {
      setSending(false)
    }
  }

  const sendMessageWithStream = async (session, content, options = {}) => {
    const userMessage = {
      id: `tmp-user-${Date.now()}`,
      role: 'user',
      content,
      created_at: new Date().toISOString(),
    }
    const assistantMessage = {
      id: `tmp-assistant-${Date.now()}`,
      role: 'assistant',
      content: '',
      references: [],
      status: 'thinking',
      referencesVisible: false,
      created_at: new Date().toISOString(),
    }
    const baseMessages = options.initialMessages ?? messages
    const nextMessages = options.includeUserMessage === false
      ? [...baseMessages, assistantMessage]
      : [...baseMessages, userMessage, assistantMessage]

    setMessages(nextMessages)

    // 流式过程中后端会回传真实消息 id（ids 事件），这里用可变变量持续追踪当前
    // 助手消息的 id：替换为真实 id 后，后续 onChunk/onDone 仍能命中同一条消息。
    let assistantMsgId = assistantMessage.id
    const tmpUserId = options.initialUserMessageId ?? userMessage.id

    try {
      await sendChatMessageStream(session.session_id, content, {
        onIds: (data) => {
          const realUserId = data?.user_message_id
          const realAssistantId = data?.assistant_message_id
          if (realAssistantId != null) assistantMsgId = realAssistantId
          setMessages((prev) => prev.map((item) => {
            if (realUserId != null && item.id === tmpUserId) {
              return { ...item, id: realUserId }
            }
            if (realAssistantId != null && item.id === assistantMessage.id) {
              return { ...item, id: realAssistantId }
            }
            return item
          }))
        },
        onReferences: (refs) => {
          setMessages((prev) => prev.map((item) => (
            item.id === assistantMsgId ? { ...item, references: refs || [] } : item
          )))
        },
        onChunk: (chunk) => {
          setMessages((prev) => prev.map((item) => (
            item.id === assistantMsgId ? { ...item, content: `${item.content}${chunk}`, status: 'streaming' } : item
          )))
        },
        onTitle: (data) => {
          const nextTitle = data?.title
          if (!nextTitle) return
          const targetId = data.session_id ?? session.session_id
          setSessions((prev) => prev.map((item) => (
            item.session_id === targetId ? { ...item, title: nextTitle } : item
          )))
          setCurrentSession((prev) => (
            prev && prev.session_id === targetId ? { ...prev, title: nextTitle } : prev
          ))
        },
        onDone: (data) => {
          setMessages((prev) => prev.map((item) => (
            item.id === assistantMsgId
              ? {
                  ...item,
                  content: data?.content ?? item.content,
                  status: 'done',
                  referencesVisible: true,
                }
              : item
          )))
        },
      })
    } catch (error) {
      setMessages((prev) => prev.map((item) => (
        item.id === assistantMsgId
          ? {
              ...item,
              status: 'error',
              error: error.message || '回答生成失败，请稍后重试',
              referencesVisible: true,
            }
          : item
      )))
      throw error
    }
  }

  const handleDeleteMessage = (item) => {
    const isTemp = typeof item.id === 'string' && item.id.startsWith('tmp-')
    if (isTemp) {
      setMessages((prev) => prev.filter((m) => m.id !== item.id))
      return
    }
    Modal.confirm({
      title: '删除消息',
      content: '确定删除这条消息吗？删除后无法恢复。',
      okText: '删除',
      okType: 'danger',
      cancelText: '取消',
      onOk: async () => {
        try {
          await deleteChatMessage(item.id)
          setMessages((prev) => prev.filter((m) => m.id !== item.id))
        } catch (error) {
          message.error(error.response?.data?.detail || '删除失败')
        }
      },
    })
  }

  const handleSearch = async () => {
    const keyword = searchKeyword.trim()
    if (!keyword) {
      searchRequestIdRef.current += 1
      setSearchResults([])
      setSearchedKeyword('')
      setHasSearched(false)
      setSearchLoading(false)
      return
    }
    const requestId = ++searchRequestIdRef.current
    setSearchLoading(true)
    try {
      const res = await searchChatMessages(keyword)
      if (requestId !== searchRequestIdRef.current) return
      setSearchResults((res.data || []).filter((item) => item.role === 'user' || item.role === 'assistant'))
      setSearchedKeyword(keyword)
      setHasSearched(true)
    } catch (error) {
      if (requestId === searchRequestIdRef.current) {
        message.error(error.response?.data?.detail || error.message || '搜索失败')
      }
    } finally {
      if (requestId === searchRequestIdRef.current) {
        setSearchLoading(false)
      }
    }
  }

  const handleSearchKeywordChange = (event) => {
    const value = event.target.value
    setSearchKeyword(value)
    if (!value.trim()) {
      searchRequestIdRef.current += 1
      setSearchResults([])
      setSearchedKeyword('')
      setHasSearched(false)
      setSearchLoading(false)
    }
  }

  const handleSelectSearchResult = async (item) => {
    setSearchVisible(false)
    skipNextOpenSessionRef.current = item.session_id
    await openSession(item.session_id, { message_id: item.message_id })
  }

  const handleRenameSession = async () => {
    const title = renameTitle.trim()
    if (!renameSession || !title) {
      message.warning('请输入对话名称')
      return
    }
    try {
      await updateChatSessionTitle(renameSession.session_id, title)
      setSessions((prev) => prev.map((item) => (
        item.session_id === renameSession.session_id ? { ...item, title } : item
      )))
      if (currentSession?.session_id === renameSession.session_id) {
        setCurrentSession((prev) => ({ ...prev, title }))
      }
      setRenameVisible(false)
      setRenameSession(null)
      setRenameTitle('')
    } catch (error) {
      message.error(error.response?.data?.detail || '重命名失败')
    }
  }

  const handleDeleteSession = (session) => {
    Modal.confirm({
      title: '删除对话',
      content: `确定删除「${session.title}」吗？删除后无法恢复。`,
      okText: '删除',
      okType: 'danger',
      cancelText: '取消',
      onOk: async () => {
        try {
          await deleteChatSession(session.session_id)
          await loadSessions()
          if (currentSession?.session_id === session.session_id) {
            setCurrentSession(null)
            setMessages([])
            navigate('/chat', { replace: true })
          }
        } catch (error) {
          message.error(error.response?.data?.detail || '删除失败')
        }
      },
    })
  }

  const renderSessionList = () => (
    <div className="chat-history-panel">
      {loadingSessions ? (
        <div className="chat-panel-loading">
          <Spin />
        </div>
      ) : sessions.length === 0 ? (
        <Empty description="暂无对话记录" />
      ) : (
        groupedSessions.map((group) => (
          <div className="chat-session-group" key={group.key}>
            <div className="chat-session-group-title">{group.label}</div>
            <List
              dataSource={group.items}
              renderItem={(item) => {
                const active = currentSession?.session_id === item.session_id
                const project = projects.find((project) => project.id === item.project_id)
                const menuItems = [
                  {
                    key: 'rename',
                    icon: <EditOutlined />,
                    label: '重命名',
                  },
                  {
                    key: 'delete',
                    icon: <DeleteOutlined />,
                    label: '删除',
                    danger: true,
                  },
                ]
                return (
                  <div
                    className={`chat-session-item${active ? ' active' : ''}`}
                    onClick={() => openSession(item.session_id)}
                  >
                    <div className="chat-session-item-top">
                      <div className="chat-session-title-area">
                        <div className="chat-session-title-row">
                          <div className="chat-session-title">{item.title}</div>
                          <Dropdown
                            menu={{
                              items: menuItems,
                              onClick: ({ key, domEvent }) => {
                                domEvent.stopPropagation()
                                if (key === 'rename') {
                                  setRenameSession(item)
                                  setRenameTitle(item.title)
                                  setRenameVisible(true)
                                }
                                if (key === 'delete') {
                                  handleDeleteSession(item)
                                }
                              },
                            }}
                            trigger={['click']}
                          >
                            <button
                              type="button"
                              className="chat-session-more"
                              onClick={(event) => event.stopPropagation()}
                              aria-label="更多操作"
                            >
                              <MoreOutlined />
                            </button>
                          </Dropdown>
                        </div>
                        <div className="chat-session-meta">
                          <Tag className="chat-session-meta-text" icon={<FolderOutlined />}>{project?.name || '未命名项目'}</Tag>
                        </div>
                      </div>
                    </div>
                  </div>
                )
              }}
            />
          </div>
        ))
      )}
    </div>
  )

  const renderMessages = () => (
    <>
      <div className="chat-message-list">
        {loadingMessages ? (
          <div className="chat-panel-loading">
            <Spin />
          </div>
        ) : messages.length === 0 ? (
          <Empty description="开始输入问题" />
        ) : (
          messages.map((item) => {
            const isUser = item.role === 'user'
            const refs = item.references || []
            const hasContent = Boolean((item.content || '').trim())
            // 流式中的占位（status=thinking）或刷新后读到的空助手消息都显示「思考中」
            const isThinking = !isUser && !hasContent && !['streaming', 'error'].includes(item.status)
            const isStreaming = !isUser && item.status === 'streaming'
            const isError = !isUser && item.status === 'error'
            const showReferences = !isUser && item.referencesVisible !== false && refs.length > 0
            const canDelete = typeof item.id === 'number' || /^\d+$/.test(String(item.id))
            const projectId = currentSession?.project_id
            return (
              <div
                key={item.id}
                ref={(node) => {
                  if (node) {
                    messageRefs.current.set(String(item.id), node)
                  } else {
                    messageRefs.current.delete(String(item.id))
                  }
                }}
                className={`chat-message-row ${isUser ? 'user' : 'assistant'}`}
              >
                {!isUser && <Avatar className="chat-avatar assistant" icon={<RobotOutlined />} />}
                <div className="chat-message-column">
                  <div className={`chat-message-bubble ${isUser ? 'user' : 'assistant'}`}>
                    {isUser ? (
                      <div className="chat-plain-text">{item.content}</div>
                    ) : (
                      <>
                        {isError && !hasContent ? (
                          <div className="chat-message-error">
                            {item.error || '回答生成失败，请稍后重试'}
                          </div>
                        ) : isThinking ? (
                          <div className="chat-thinking">
                            <span className="chat-thinking-text">正在思考</span>
                            <span className="chat-thinking-dots">
                              <i />
                              <i />
                              <i />
                            </span>
                          </div>
                        ) : (
                          <div className={`chat-markdown${isStreaming ? ' streaming' : ''}`}>
                            <ReactMarkdown
                              remarkPlugins={[remarkGfm]}
                              rehypePlugins={[rehypeHighlight, rehypeCitationSup]}
                              components={{
                                sup: ({ children, ...props }) => {
                                  const citationId = Number(props['data-citation-id'])
                                  const ref = refs.find((itemRef) => Number(itemRef.citation_id) === citationId)
                                  if (!ref) {
                                    return <sup {...props}>{children}</sup>
                                  }
                                  return (
                                    <Popover
                                      trigger="hover"
                                      placement="top"
                                      overlayClassName="chat-citation-popover"
                                      content={(
                                        <div className="chat-citation-preview">
                                          <Text type="secondary" className="chat-citation-preview-file">
                                            {ref.file_name || ref.file_path}
                                          </Text>
                                          <div className="chat-citation-preview-content">
                                            <ReactMarkdown
                                              remarkPlugins={[remarkGfm]}
                                              rehypePlugins={[rehypeHighlight]}
                                            >
                                              {getReferenceExcerpt(ref) || '暂无引用片段'}
                                            </ReactMarkdown>
                                          </div>
                                        </div>
                                      )}
                                    >
                                      <sup {...props}>{children}</sup>
                                    </Popover>
                                  )
                                },
                              }}
                            >
                              {item.content}
                            </ReactMarkdown>
                            {isError && (
                              <div className="chat-message-error chat-message-error-inline">
                                {item.error || '回答生成中断，请稍后重试'}
                              </div>
                            )}
                          </div>
                        )}
                        {showReferences && (
                          <div className="chat-references">
                            <Text type="secondary" className="chat-references-label">关联文档</Text>
                            <Space wrap>
                              {refs.map((ref) => {
                                return (
                                  <Tag
                                    key={`${ref.citation_id}-${ref.file_path || ref.file_name}`}
                                    color="blue"
                                    className="chat-reference-link"
                                    onClick={() => {
                                      openDocument(ref, projectId)
                                    }}
                                  >
                                    [{ref.citation_id}] {ref.file_name}
                                  </Tag>
                                )
                              })}
                            </Space>
                          </div>
                        )}
                      </>
                    )}
                  </div>
                  {(isUser || (!isThinking && !isStreaming)) && (
                    <MessageActions
                      content={item.content}
                      onDelete={canDelete ? () => handleDeleteMessage(item) : undefined}
                    />
                  )}
                </div>
                {isUser && (
                  <Avatar className="chat-avatar user" src={userAvatarUrl} style={{ backgroundColor: '#1677ff' }}>
                    {userDisplayName?.[0]?.toUpperCase() || 'U'}
                  </Avatar>
                )}
              </div>
            )
          })
        )}
        <div ref={messagesEndRef} />
      </div>
      <div className="chat-composer-shell">
        <TextArea
          className="chat-composer-input"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          autoSize={{ minRows: 1, maxRows: 8 }}
          placeholder="随心输入"
          onPressEnter={(e) => {
            if (!e.shiftKey) {
              e.preventDefault()
              handleSend()
            }
          }}
        />
        <div className="chat-composer-actions">
          <div className="chat-composer-left">
            <span className="chat-composer-pill chat-composer-kb">
              <FolderOutlined className="chat-composer-pill-icon" />
              <span className="chat-composer-pill-text">
                {projects.find((project) => project.id === currentSession?.project_id)?.name || '-'}
              </span>
            </span>
          </div>
          <div className="chat-composer-right">
            <span className="chat-composer-pill chat-composer-model">
              <RobotOutlined className="chat-composer-pill-icon" />
              <span className="chat-composer-pill-text">
                {models.find((model) => model.config_id === currentSession?.llm_config_id)?.model_name || '-'}
              </span>
            </span>
            <Button
              shape="circle"
              icon={<ArrowUpOutlined />}
              onClick={handleSend}
              loading={sending}
              disabled={!canSendMessage}
              aria-disabled={!canSendMessage || sending}
              className="chat-send-button"
            />
          </div>
        </div>
      </div>
    </>
  )

  const renderChatShell = () => (
    <div className="chat-shell">
      <div className="chat-header">
        <div className="chat-header-main">
          <Text className="chat-header-title">{currentSession?.title || '对话'}</Text>
        </div>
      </div>
      {renderMessages()}
    </div>
  )

  const renderNewShell = () => (
    <div className="chat-start-shell">
      <div className="chat-new-page-card">
        <div className="chat-start-title">你希望了解什么？</div>
        <div className="chat-composer-shell chat-new-composer">
          <TextArea
            value={newQuestion}
            onChange={(e) => setNewQuestion(e.target.value)}
            className="chat-composer-input"
            autoSize={{ minRows: 2, maxRows: 10 }}
            placeholder="随心输入"
            onPressEnter={(e) => {
              if (!e.shiftKey) {
                e.preventDefault()
                handleCreateSession()
              }
            }}
          />
          <div className="chat-composer-actions">
            <div className="chat-composer-left">
              <Popover
                open={projectPickerOpen}
                onOpenChange={setProjectPickerOpen}
                trigger="click"
                placement="topLeft"
                content={
                  <div className="chat-project-picker">
                    <Select
                      value={newProjectId}
                      placeholder="选择知识库"
                      options={projects.map((item) => ({ label: item.name, value: item.id }))}
                      onChange={(value) => {
                        setNewProjectId(value)
                        setProjectPickerOpen(false)
                      }}
                      suffixIcon={<DownOutlined />}
                      showSearch
                      optionFilterProp="label"
                      className="chat-project-picker-select"
                    />
                  </div>
                }
              >
                <button type="button" className="chat-composer-plus" aria-label="选择知识库">
                  <PlusOutlined />
                </button>
              </Popover>
              {currentProject && (
                <span className="chat-composer-pill chat-composer-kb">
                  <FolderOutlined className="chat-composer-pill-icon" />
                  <span className="chat-composer-pill-text">{currentProject.name}</span>
                </span>
              )}
            </div>
            <div className="chat-composer-right">
              <Select
                value={newModelId}
                placeholder="选择模型"
                options={models.map((item) => ({ label: item.model_name, value: item.config_id }))}
                onChange={setNewModelId}
                suffixIcon={<DownOutlined />}
                showSearch
                optionFilterProp="label"
                className="chat-composer-model-select"
              />
              <Button
                shape="circle"
                icon={<ArrowUpOutlined />}
                onClick={handleCreateSession}
                loading={sending}
                disabled={!canCreateSession}
                aria-disabled={!canCreateSession || sending}
                className="chat-send-button"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  )

  return (
    <div className="chat-page-shell">
      <aside className="chat-left-panel">
        <div className="chat-left-actions">
          <button className="chat-action-entry" onClick={handleStartNew}>
            <EditOutlined />
            <span>新建对话</span>
          </button>
          <button className="chat-action-entry" onClick={() => setSearchVisible(true)}>
            <SearchOutlined />
            <span>搜索对话</span>
          </button>
        </div>
        {renderSessionList()}
      </aside>

      <main className="chat-main-panel">
        {currentSession && !newMode ? renderChatShell() : renderNewShell()}
      </main>

      <Modal
        title="搜索聊天内容"
        open={searchVisible}
        onCancel={() => setSearchVisible(false)}
        footer={null}
        width={840}
        destroyOnClose
        className="chat-search-modal"
      >
        <Space.Compact className="chat-search-bar">
          <Input
            value={searchKeyword}
            onChange={handleSearchKeywordChange}
            placeholder="输入聊天关键词，仅搜索对话内容"
            allowClear
            onPressEnter={handleSearch}
          />
          <Button type="primary" icon={<SearchOutlined />} loading={searchLoading} onClick={handleSearch}>
            搜索
          </Button>
        </Space.Compact>
        <div className="chat-search-result-list">
          {searchLoading ? (
            <div className="chat-panel-loading">
              <Spin />
            </div>
          ) : (
            <List
              dataSource={searchResults}
              locale={{
                emptyText: (
                  <Empty description={hasSearched ? `未找到“${searchedKeyword}”相关内容` : '输入关键词搜索聊天内容'} />
                ),
              }}
              renderItem={(item) => (
                <List.Item className="chat-search-result-item" onClick={() => handleSelectSearchResult(item)}>
                  <List.Item.Meta
                    avatar={<Avatar icon={item.role === 'assistant' ? <RobotOutlined /> : <UserOutlined />} />}
                    title={
                      <Space wrap>
                        <Text strong>{item.session_title}</Text>
                        <Tag>{item.project_name}</Tag>
                        <Tag>{item.model_name}</Tag>
                      </Space>
                    }
                    description={
                      <>
                        <div className="chat-search-result-snippet">
                          <SearchHighlight
                            text={item.snippet || stripMarkdown(item.content)}
                            keyword={searchedKeyword}
                          />
                        </div>
                        <div className="chat-search-result-time">{formatTime(item.created_at)}</div>
                      </>
                    }
                  />
                </List.Item>
              )}
            />
          )}
        </div>
      </Modal>
      <Modal
        title="重命名对话"
        open={renameVisible}
        onCancel={() => setRenameVisible(false)}
        onOk={handleRenameSession}
        okText="保存"
        cancelText="取消"
      >
        <Input
          value={renameTitle}
          onChange={(event) => setRenameTitle(event.target.value)}
          placeholder="输入新的对话名称"
          maxLength={80}
          showCount
        />
      </Modal>
    </div>
  )
}

export default Chat
