import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Plus, Trash2, Sparkles, RefreshCw, ChevronRight, Loader, Settings2, X, Download, Upload } from 'lucide-react'
import dayjs from 'dayjs'
import {
  getGroups,
  createGroup,
  patchGroup,
  initGroup,
  deleteGroup,
  getTopics,
  getTopic,
  createTopic,
  deleteTopic,
  summarizeTopic,
  addTopicMessage,
  removeTopicMessage,
  getTask,
  getChatrooms,
  getChatlog,
  getChatroomMembers,
} from '../api'
import MessageBubble from '../components/MessageBubble'
import ReportDocumentView from '../components/ReportDocumentView'
import { exportWordDoc } from '../utils/wordExport'
import { uploadToKnowledgeBase, getWanchuanKnowledgeBases, getBoundKnowledgeBase } from '../api/wanchuan'

const TYPE_MAP = {
  1: 0,
  3: 1,
  34: 2,
  43: 3,
  47: 5,
  49: 7,
  10000: 80,
  10002: 81,
}

const ANALYZE_PRESETS = [
  { label: '今天', getDates: () => [dayjs().startOf('day'), dayjs()] },
  { label: '昨天', getDates: () => [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')] },
  { label: '近7天', getDates: () => [dayjs().subtract(7, 'day').startOf('day'), dayjs()] },
  { label: '近30天', getDates: () => [dayjs().subtract(30, 'day').startOf('day'), dayjs()] },
]

function convertTopicMsgType(rawType, subType) {
  if (Number(rawType) === 49 && Number(subType) === 62) return 82
  return TYPE_MAP[Number(rawType)] ?? 0
}

function toMsgBubbleFormat(m, index) {
  const rawType = Number(m.type ?? 1)
  const subType = Number(m.sub_type ?? m.subType ?? 0)
  const isFile = Boolean(m.is_file) || (rawType === 49 && subType === 6)
  const fileMd5 = m.file_md5 || m.fileMd5 || m.contents?.md5 || ''
  const fileName = m.file_name || m.fileName || m.contents?.title || ''
  const displayName = (
    m.sender_name ||
    m.senderName ||
    m.SenderName ||
    m.groupNickname ||
    m.accountName ||
    m.sender ||
    '未知'
  )

  return {
    id: String(m.seq || m.id || index),
    sender: m.sender || '',
    accountName: displayName,
    groupNickname: displayName,
    timestamp: m.create_time ? dayjs(m.create_time).unix() : (m.timestamp || 0),
    type: convertTopicMsgType(rawType, subType),
    content: m.content || '',
    subType,
    mediaKey: m.media_key || m.mediaKey || '',
    voiceKey: m.voice_key || m.voiceKey || '',
    mediaMd5: m.media_key || m.mediaMd5 || '',
    mediaPath: m.mediaPath || '',
    emojiUrl: m.contents?.cdnurl || m.emojiUrl || '',
    linkTitle: isFile ? '' : (m.link_title || m.linkTitle || m.contents?.title || ''),
    linkDesc: isFile ? '' : (m.link_desc || m.linkDesc || m.contents?.desc || ''),
    linkUrl: isFile ? '' : (m.link_url || m.linkUrl || m.contents?.url || m.content || ''),
    linkThumb: isFile ? '' : (m.link_thumb || m.linkThumb || m.contents?.thumbUrl || ''),
    linkSource: isFile ? '' : (m.link_source || m.linkSource || m.contents?.sourceName || ''),
    quote: m.quote || null,
    isFile,
    fileName,
    fileMd5,
    fileUrl: m.file_url || m.fileUrl || (fileMd5 ? `/api/files/${encodeURIComponent(fileMd5)}?filename=${encodeURIComponent(fileName || fileMd5)}` : ''),
  }
}

function topicMsgPreview(m) {
  return m.content || m.link_title || m.linkTitle || m.file_name || m.fileName || m.contents?.title || m.quote?.content || '[媒体消息]'
}

function compactText(value, max = 28) {
  const text = String(value || '').replace(/\s+/g, ' ').trim()
  if (!text) return ''
  return text.length > max ? `${text.slice(0, max)}...` : text
}

export default function TopicsPage({ sessions = [], onToast }) {
  const [groups, setGroups] = useState([])
  const [selectedGroup, setSelectedGroup] = useState(null)
  const [topics, setTopics] = useState([])
  const [selectedTopic, setSelectedTopic] = useState(null)
  const [topicDetail, setTopicDetail] = useState(null)
  const [loading, setLoading] = useState(false)
  const [newTopicTitle, setNewTopicTitle] = useState('')

  const [showAddGroup, setShowAddGroup] = useState(false)
  const [selectedTalker, setSelectedTalker] = useState('')
  const [addingGroup, setAddingGroup] = useState(false)
  const [deletingGroupId, setDeletingGroupId] = useState(null)
  const [addGroupKeyword, setAddGroupKeyword] = useState('')
  const [groupSearchResults, setGroupSearchResults] = useState([])
  const [groupSearchLoading, setGroupSearchLoading] = useState(false)
  const [groupSearchError, setGroupSearchError] = useState('')
  const [showGroupDropdown, setShowGroupDropdown] = useState(false)
  const groupSearchSeqRef = useRef(0)

  const [hoveredGroupId, setHoveredGroupId] = useState(null)
  const [showMsgManager, setShowMsgManager] = useState(false)
  const [allMsgs, setAllMsgs] = useState([])
  const [msgSearch, setMsgSearch] = useState('')
  const [msgLoading, setMsgLoading] = useState(false)
  const [msgStartDate, setMsgStartDate] = useState(dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DDTHH:mm'))
  const [msgEndDate, setMsgEndDate] = useState(dayjs().format('YYYY-MM-DDTHH:mm'))
  const [msgMembers, setMsgMembers] = useState([])
  const [msgAllMembers, setMsgAllMembers] = useState([])

  const [analyzeStartDate, setAnalyzeStartDate] = useState(dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DDTHH:mm'))
  const [analyzeEndDate, setAnalyzeEndDate] = useState(dayjs().format('YYYY-MM-DDTHH:mm'))
  const [analyzePreset, setAnalyzePreset] = useState('近7天')
  const [initStateByGroup, setInitStateByGroup] = useState({})
  const [summarizing, setSummarizing] = useState(false)
  const [summarizeProgress, setSummarizeProgress] = useState(null)
  const [showGroupPrompt, setShowGroupPrompt] = useState(false)
  const [groupPromptDraft, setGroupPromptDraft] = useState('')
  const [savingGroupPrompt, setSavingGroupPrompt] = useState(false)


  const [uploadingToKb, setUploadingToKb] = useState(false)
  const loadGroups = useCallback(async () => {
    try {
      const data = await getGroups()
      setGroups(Array.isArray(data) ? data : [])
      return Array.isArray(data) ? data : []
    } catch {
      setGroups([])
      return []
    }
  }, [])

  const resetAddGroupState = useCallback(() => {
    groupSearchSeqRef.current += 1
    setSelectedTalker('')
    setAddGroupKeyword('')
    setGroupSearchResults([])
    setGroupSearchError('')
    setGroupSearchLoading(false)
    setShowGroupDropdown(false)
  }, [])

  const openAddGroup = useCallback(() => {
    resetAddGroupState()
    setShowAddGroup(true)
    setShowGroupDropdown(true)
  }, [resetAddGroupState])

  const closeAddGroup = useCallback(() => {
    setShowAddGroup(false)
    resetAddGroupState()
  }, [resetAddGroupState])

  useEffect(() => {
    loadGroups()
  }, [loadGroups])

  const addGroupKeywordText = addGroupKeyword.trim()
  const recentGroupResults = useMemo(() => {
    const monitored = new Set()
    groups.forEach((group) => {
      if (group?.talker) monitored.add(group.talker)
      if (group?.id) monitored.add(group.id)
    })

    return sessions
      .filter((room) => room?.id && room.isGroup && !monitored.has(room.id))
      .slice()
      .sort((a, b) => (Number(b.lastTime) || 0) - (Number(a.lastTime) || 0))
      .slice(0, 100)
  }, [groups, sessions])
  const displayGroupResults = addGroupKeywordText ? groupSearchResults : recentGroupResults

  useEffect(() => {
    if (!showAddGroup) return undefined

    const keyword = addGroupKeywordText
    if (!keyword) {
      groupSearchSeqRef.current += 1
      setGroupSearchResults([])
      setGroupSearchError('')
      setGroupSearchLoading(false)
      return undefined
    }

    const seq = groupSearchSeqRef.current + 1
    groupSearchSeqRef.current = seq
    setGroupSearchLoading(true)
    setGroupSearchError('')

    const timer = setTimeout(async () => {
      try {
        const res = await getChatrooms(keyword)
        if (groupSearchSeqRef.current !== seq) return

        const monitored = new Set(groups.map((g) => g.talker))
        const items = (res?.data || [])
          .filter((room) => room?.id && !monitored.has(room.id))
          .slice(0, 100)
        setGroupSearchResults(items)
      } catch (e) {
        if (groupSearchSeqRef.current !== seq) return
        setGroupSearchResults([])
        setGroupSearchError(e?.response?.data?.detail || e?.message || '搜索失败')
      } finally {
        if (groupSearchSeqRef.current === seq) setGroupSearchLoading(false)
      }
    }, 250)

    return () => clearTimeout(timer)
  }, [showAddGroup, addGroupKeywordText, groups])

  const loadTopics = useCallback(async (groupId) => {
    setLoading(true)
    setTopics([])
    setSelectedTopic(null)
    setTopicDetail(null)
    try {
      const data = await getTopics({ group_id: groupId })
      setTopics(Array.isArray(data) ? data : [])
    } catch {
      setTopics([])
    } finally {
      setLoading(false)
    }
  }, [])

  const handleSelectGroup = useCallback((group) => {
    setSelectedGroup(group)
    loadTopics(group.id)
  }, [loadTopics])

  const openGroupPrompt = () => {
    if (!selectedGroup) return
    setGroupPromptDraft(selectedGroup.analysis_prompt || '')
    setShowGroupPrompt(true)
  }

  const handleSaveGroupPrompt = async () => {
    if (!selectedGroup || savingGroupPrompt) return
    setSavingGroupPrompt(true)
    try {
      const updated = await patchGroup(selectedGroup.id, { analysis_prompt: groupPromptDraft })
      const groupData = updated?.data ?? updated
      setGroups((prev) => prev.map((g) => (g.id === selectedGroup.id ? { ...g, ...groupData } : g)))
      setSelectedGroup((prev) => prev ? { ...prev, ...groupData } : prev)
      setShowGroupPrompt(false)
      onToast?.('AI 分析提示词已保存')
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '保存提示词失败', 'error')
    } finally {
      setSavingGroupPrompt(false)
    }
  }

  const handleDeleteGroup = async (e, group) => {
    e.stopPropagation()
    if (deletingGroupId) return
    if (!window.confirm(`确定删除“${group.name || group.talker}”吗？\n相关话题和报告也会一起删除。`)) return

    setDeletingGroupId(group.id)
    try {
      await deleteGroup(group.id)
      setGroups((prev) => prev.filter((g) => g.id !== group.id))
      if (selectedGroup?.id === group.id) {
        setSelectedGroup(null)
        setTopics([])
        setSelectedTopic(null)
        setTopicDetail(null)
      }
      resetAddGroupState()
      onToast?.('群聊已删除')
    } catch (e2) {
      onToast?.(e2?.response?.data?.detail || e2?.message || '删除失败', 'error')
    } finally {
      setDeletingGroupId(null)
    }
  }

  const handleAddGroup = async () => {
    if (!selectedTalker || addingGroup) return
    const result = groupSearchResults.find((s) => s.id === selectedTalker)
    const session = result || sessions.find((s) => s.id === selectedTalker)

    setAddingGroup(true)
    try {
      const created = await createGroup(selectedTalker, session?.name || addGroupKeyword || selectedTalker)
      onToast?.('群聊已添加')
      closeAddGroup()
      const nextGroups = await loadGroups()
      const added = nextGroups.find((g) => g.id === created?.id || g.talker === selectedTalker) || created
      if (added?.id) handleSelectGroup(added)
    } catch (e) {
      if (e?.response?.status === 409) {
        onToast?.('该群聊已在监控列表中', 'error')
        loadGroups()
      } else {
        onToast?.(e?.response?.data?.detail || e?.message || '添加失败', 'error')
      }
    } finally {
      setAddingGroup(false)
    }
  }

  const applyAnalyzePreset = (preset) => {
    const [start, end] = preset.getDates()
    setAnalyzeStartDate(start.format('YYYY-MM-DDTHH:mm'))
    setAnalyzeEndDate(end.format('YYYY-MM-DDTHH:mm'))
    setAnalyzePreset(preset.label)
  }

  const handleInitGroup = async (group) => {
    try {
      const cfg = await fetch('/api/settings').then((r) => r.json())
      if (!cfg.ai_api_key) {
        onToast?.('请先配置 AI API Key', 'error')
        return
      }
      if (!cfg.ai_model) {
        onToast?.('请先配置话题分析模型', 'error')
        return
      }
    } catch {
      onToast?.('读取 AI 设置失败', 'error')
      return
    }

    const startTime = dayjs(analyzeStartDate).unix()
    const endTime = dayjs(analyzeEndDate).unix()
    if (!startTime || !endTime || endTime <= startTime) {
      onToast?.('请选择有效的分析时间范围', 'error')
      return
    }

    setInitStateByGroup((prev) => ({
      ...prev,
      [group.id]: { taskId: null, status: 'running', progress: null },
    }))

    let taskId = null
    try {
      const res = await initGroup(group.id, { startTime, endTime })
      taskId = res?.data?.task_id ?? res?.task_id
      onToast?.('AI 分析已开始')
    } catch (e) {
      const detail = e?.response?.data?.detail || e?.message
      onToast?.(detail || 'AI 分析启动失败', 'error')
      setInitStateByGroup((prev) => {
        const next = { ...prev }
        delete next[group.id]
        return next
      })
      return
    }

    if (!taskId) {
      setTimeout(() => {
        loadTopics(group.id)
        setInitStateByGroup((prev) => {
          const next = { ...prev }
          delete next[group.id]
          return next
        })
      }, 5000)
      return
    }

    setInitStateByGroup((prev) => ({
      ...prev,
      [group.id]: { taskId, status: 'running', progress: null },
    }))

    let netFails = 0
    const poll = setInterval(async () => {
      try {
        const task = await getTask(taskId)
        const taskData = task?.data ?? task
        netFails = 0

        let progress = null
        try {
          progress = taskData.progress ? JSON.parse(taskData.progress) : null
        } catch {
          progress = null
        }

        setInitStateByGroup((prev) => ({
          ...prev,
          [group.id]: { taskId, status: taskData.status || 'running', progress },
        }))

        if (taskData.status === 'done') {
          clearInterval(poll)
          setSelectedTopic(null)
          setTopicDetail(null)
          loadTopics(group.id)
          onToast?.('AI 分析完成')
          setTimeout(() => {
            setInitStateByGroup((prev) => {
              const next = { ...prev }
              delete next[group.id]
              return next
            })
          }, 800)
        } else if (taskData.status === 'error') {
          clearInterval(poll)
          onToast?.(`AI 分析失败：${taskData.error || '未知错误'}`, 'error')
          setInitStateByGroup((prev) => {
            const next = { ...prev }
            delete next[group.id]
            return next
          })
        }
      } catch {
        netFails += 1
        if (netFails >= 5) {
          clearInterval(poll)
          setInitStateByGroup((prev) => {
            const next = { ...prev }
            delete next[group.id]
            return next
          })
        }
      }
    }, 3000)
  }

  const handleSelectTopic = async (topic) => {
    setSelectedTopic(topic)
    setTopicDetail(null)
    try {
      const detail = await getTopic(topic.id)
      setTopicDetail(detail)
    } catch {
      setTopicDetail(null)
    }
  }

  const handleCreateTopic = async () => {
    if (!newTopicTitle.trim() || !selectedGroup) return
    try {
      await createTopic(selectedGroup.id, newTopicTitle.trim())
      setNewTopicTitle('')
      onToast?.('话题已创建')
      loadTopics(selectedGroup.id)
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '创建话题失败', 'error')
    }
  }

  const handleDeleteTopic = async (topic) => {
    try {
      await deleteTopic(topic.id)
      onToast?.('话题已删除')
      setSelectedTopic(null)
      setTopicDetail(null)
      loadTopics(selectedGroup.id)
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '删除话题失败', 'error')
    }
  }

  const handleOpenMsgManager = async () => {
    if (!selectedTopic || !selectedGroup) return
    setShowMsgManager(true)
    setMsgLoading(true)
    setAllMsgs([])
    setMsgSearch('')

    const start = dayjs().subtract(7, 'day').startOf('day')
    const end = dayjs()
    setMsgStartDate(start.format('YYYY-MM-DDTHH:mm'))
    setMsgEndDate(end.format('YYYY-MM-DDTHH:mm'))
    setMsgMembers([])

    try {
      const [msgsRes, membersRes] = await Promise.all([
        getChatlog({ talker: selectedGroup.talker, startTime: start.unix(), endTime: end.unix(), limit: 200, offset: 0 }),
        getChatroomMembers(selectedGroup.talker),
      ])
      setAllMsgs(msgsRes.data?.messages || [])
      setMsgAllMembers(membersRes.data || [])
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '加载消息失败', 'error')
    } finally {
      setMsgLoading(false)
    }
  }

  const fetchMsgs = async () => {
    if (!selectedGroup) return
    setMsgLoading(true)
    try {
      const res = await getChatlog({
        talker: selectedGroup.talker,
        startTime: dayjs(msgStartDate).unix(),
        endTime: dayjs(msgEndDate).unix(),
        senders: msgMembers,
        keyword: msgSearch,
        limit: 200,
        offset: 0,
      })
      setAllMsgs(res.data?.messages || [])
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '搜索消息失败', 'error')
    } finally {
      setMsgLoading(false)
    }
  }

  const handleAddMsg = async (msg) => {
    try {
      await addTopicMessage(selectedTopic.id, Number(msg.id), selectedGroup.talker)
      const detail = await getTopic(selectedTopic.id)
      setTopicDetail(detail)
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '添加消息失败', 'error')
    }
  }

  const handleRemoveMsg = async (seq) => {
    try {
      await removeTopicMessage(selectedTopic.id, seq)
      const detail = await getTopic(selectedTopic.id)
      setTopicDetail(detail)
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '移除消息失败', 'error')
    }
  }

  const handleSummarize = async () => {
    if (!selectedTopic) return
    if (uploadingToKb) return
    setSummarizing(true)
    setSummarizeProgress(null)
    try {
      const res = await summarizeTopic(selectedTopic.id)
      onToast?.('AI 报告生成已开始')
      const taskId = res?.data?.task_id ?? res?.task_id

      if (!taskId) {
        setTimeout(() => {
          handleSelectTopic(selectedTopic)
          setSummarizing(false)
        }, 5000)
        return
      }

      let tries = 0
      let handled = false
      const poll = setInterval(async () => {
        if (handled) return
        tries += 1
        try {
          const task = await getTask(taskId)
          const taskData = task?.data ?? task
          try {
            const progress = taskData.progress ? JSON.parse(taskData.progress) : null
            if (progress) setSummarizeProgress(progress)
          } catch {
            // Ignore malformed progress payloads.
          }
          if (taskData.status === 'done') {
            handled = true
            clearInterval(poll)
            handleSelectTopic(selectedTopic)
            onToast?.('AI 报告生成完成')
            // 自动上传知识库已关闭：报告生成完成后不再自动同步到万川知识库。
            // 如需恢复，可在此处重新调用 uploadToKnowledgeBaseIfBound(selectedTopic.title, reportContent)。
            setSummarizeProgress(null)
            setSummarizing(false)
            return
          }
          if (taskData.status === 'error') {
            handled = true
            clearInterval(poll)
            setSummarizeProgress(null)
            setSummarizing(false)
            handleSelectTopic(selectedTopic)
            onToast?.(taskData.error || 'AI 报告生成失败，请检查模型/API 后重试', 'error')
            return
          }
          if (tries > 60) {
            handled = true
            clearInterval(poll)
            setSummarizeProgress(null)
            setSummarizing(false)
            handleSelectTopic(selectedTopic)
            onToast?.('AI 报告生成超时，请稍后重试', 'error')
          }
        } catch (e) {
          if (tries > 60) {
            handled = true
            clearInterval(poll)
            setSummarizeProgress(null)
            setSummarizing(false)
            handleSelectTopic(selectedTopic)
            onToast?.(e?.response?.data?.detail || e?.message || '获取 AI 报告进度失败', 'error')
          }
        }
      }, 3000)
    } catch (e) {
      onToast?.(e?.response?.data?.detail || e?.message || '报告生成失败', 'error')
      setSummarizing(false)
      setSummarizeProgress(null)
    }
  }

  const handleExportReport = async () => {
    const content = topicDetail?.knowledge_doc?.content || ''
    if (!selectedTopic || !content) {
      onToast?.('暂无可导出的报告内容', 'error')
      return
    }
    try {
      await exportWordDoc(selectedTopic.title || '话题报告', content)
      onToast?.('Word 文档已导出')
    } catch (e) {
      onToast?.(e?.message || 'Word 导出失败', 'error')
    }
  }

  // 上传报告到已绑定的万川知识库
  const uploadToKnowledgeBaseIfBound = async (reportTitle, content) => {
    if (uploadingToKb) return
    setUploadingToKb(true)
    try {
      const bound = await getBoundKnowledgeBase()
      if (!bound) return
      // 上传接口必须使用 datasetId
      const datasetId = bound.kbDatasetId || bound.id
      if (!bound.platformUrl || !datasetId) return

      const safeTitle = (reportTitle || 'report').replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '_').slice(0, 50)
      const file = new File([blob], `${safeTitle}.md`, { type: 'text/markdown' })

      // 上传接口使用 datasetId
      await uploadToKnowledgeBase(bound.platformUrl, datasetId, file)
      onToast?.('报告已同步上传到知识库')
    } catch (e) {
      console.error('[上传知识库失败]', e?.message || e)
      onToast?.(`报告生成成功，但上传到知识库失败: ${e.message}`, 'error')
    } finally {
      setUploadingToKb(false)
    }
  }

  const selectedInitState = selectedGroup ? initStateByGroup[selectedGroup.id] : null
  const analyzing = selectedInitState?.status === 'running'

  return (
    <>
      <div style={{ display: 'flex', flex: 1, overflow: 'hidden', height: '100%' }}>
        <div style={{ width: 220, borderRight: '1px solid var(--border)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
          <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
            <span style={{ fontWeight: 600, fontSize: 13 }}>群聊</span>
            <button className="btn btn-ghost" style={{ padding: '2px 8px', fontSize: 12 }} onClick={openAddGroup}>
              <Plus size={13} /> 添加
            </button>
          </div>

          {showAddGroup && (
            <div style={{ padding: '10px 12px', borderBottom: '1px solid var(--border)', background: 'var(--surface-2)' }}>
              <div style={{ position: 'relative', marginBottom: 6 }}>
                <input
                  className="filter-input"
                  style={{ width: '100%', boxSizing: 'border-box' }}
                  placeholder="搜索群聊名称..."
                  value={addGroupKeyword}
                  onChange={(e) => {
                    setAddGroupKeyword(e.target.value)
                    setSelectedTalker('')
                    setShowGroupDropdown(true)
                  }}
                  onFocus={() => setShowGroupDropdown(true)}
                />
                {showGroupDropdown && (
                  <div style={{
                    position: 'absolute',
                    top: '100%',
                    left: 0,
                    right: 0,
                    zIndex: 100,
                    background: 'var(--bg-surface)',
                    border: '1px solid var(--border)',
                    borderRadius: 6,
                    maxHeight: 220,
                    overflowY: 'auto',
                    boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
                  }}>
                    {!addGroupKeywordText && (
                      <div style={{ padding: '8px 10px', fontSize: 12, fontWeight: 600, color: 'var(--text-muted)' }}>最近群聊</div>
                    )}
                    {groupSearchLoading && (
                      <div style={{ padding: '8px 10px', fontSize: 12, color: 'var(--text-muted)' }}>正在搜索...</div>
                    )}
                    {groupSearchError && (
                      <div style={{ padding: '8px 10px', fontSize: 12, color: 'var(--danger)' }}>{groupSearchError}</div>
                    )}
                    {!groupSearchLoading && !groupSearchError && displayGroupResults.map((room) => (
                      <div
                        key={room.id}
                        onClick={() => {
                          setSelectedTalker(room.id)
                          setAddGroupKeyword(room.name || room.id)
                          setShowGroupDropdown(false)
                        }}
                        style={{
                          padding: '8px 10px',
                          cursor: 'pointer',
                          fontSize: 12,
                          background: selectedTalker === room.id ? 'var(--bg-overlay)' : 'var(--bg-surface)',
                          borderBottom: '1px solid var(--border)',
                        }}
                        onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--bg-hover)' }}
                        onMouseLeave={(e) => { e.currentTarget.style.background = selectedTalker === room.id ? 'var(--bg-overlay)' : 'var(--bg-surface)' }}
                      >
                        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                          <div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{room.name || room.id}</div>
                          {!addGroupKeywordText && room.lastTime > 0 && (
                            <div style={{ color: 'var(--text-muted)', fontSize: 10, whiteSpace: 'nowrap', flexShrink: 0 }}>
                              {dayjs.unix(room.lastTime).fromNow()}
                            </div>
                          )}
                        </div>
                        <div style={{ marginTop: 2, color: 'var(--text-muted)', fontSize: 10, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{room.id}</div>
                        {!addGroupKeywordText && room.lastContent && (
                          <div style={{ marginTop: 3, color: 'var(--text-muted)', fontSize: 11, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                            {compactText(room.lastContent, 32)}
                          </div>
                        )}
                      </div>
                    ))}
                    {addGroupKeywordText && !groupSearchLoading && !groupSearchError && groupSearchResults.length === 0 && (
                      <div style={{ padding: '8px 10px', fontSize: 12, color: 'var(--text-muted)' }}>没有找到匹配的群聊。</div>
                    )}
                    {!addGroupKeywordText && !groupSearchLoading && !groupSearchError && recentGroupResults.length === 0 && (
                      <div style={{ padding: '8px 10px', fontSize: 12, color: 'var(--text-muted)' }}>暂无可添加的最近群聊。</div>
                    )}
                  </div>
                )}
              </div>
              <div style={{ display: 'flex', gap: 6 }}>
                <button className="btn btn-primary" style={{ flex: 1, fontSize: 12 }} disabled={!selectedTalker || addingGroup} onClick={handleAddGroup}>
                  {addingGroup ? '添加中...' : '确定'}
                </button>
                <button className="btn btn-ghost" style={{ fontSize: 12 }} disabled={addingGroup} onClick={closeAddGroup}>取消</button>
              </div>
            </div>
          )}

          <div style={{ flex: 1, overflowY: 'auto' }}>
            {groups.length === 0 && (
              <div style={{ padding: 20, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>
                暂无监控群聊。
              </div>
            )}
            {groups.map((group) => (
              <div
                key={group.id}
                onClick={() => handleSelectGroup(group)}
                onMouseEnter={() => setHoveredGroupId(group.id)}
                onMouseLeave={() => setHoveredGroupId(null)}
                style={{
                  padding: '10px 14px',
                  cursor: 'pointer',
                  borderBottom: '1px solid var(--border)',
                  background: selectedGroup?.id === group.id ? 'var(--surface-2)' : 'transparent',
                  display: 'flex',
                  alignItems: 'center',
                  gap: 8,
                }}
              >
                <div style={{ flex: 1, overflow: 'hidden' }}>
                  <div style={{ fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {group.name || group.talker}
                  </div>
                </div>
                {hoveredGroupId === group.id ? (
                  <button
                    className="btn btn-ghost"
                    onClick={(e) => handleDeleteGroup(e, group)}
                    disabled={deletingGroupId === group.id}
                    title="删除群聊"
                    style={{ color: 'var(--danger)', padding: '2px 4px', borderRadius: 4, flexShrink: 0 }}
                  >
                    {deletingGroupId === group.id ? <Loader size={13} /> : <Trash2 size={13} />}
                  </button>
                ) : (
                  <ChevronRight size={13} color="var(--text-muted)" />
                )}
              </div>
            ))}
          </div>
        </div>

        <div style={{ width: 260, borderRight: '1px solid var(--border)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
          {!selectedGroup ? (
            <div className="empty-state" style={{ flex: 1 }}>
              <div className="empty-state-icon">AI</div>
              <div className="empty-state-title">请选择群聊</div>
            </div>
          ) : (
            <>
              <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginBottom: 8 }}>
                  <div style={{ fontWeight: 600, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {selectedGroup.name || selectedGroup.talker}
                  </div>
                  <button
                    className="btn btn-ghost"
                    style={{ fontSize: 12, padding: '4px 6px', flexShrink: 0 }}
                    onClick={openGroupPrompt}
                    title="AI 分析提示词"
                  >
                    <Settings2 size={12} />
                  </button>
                </div>

                <div style={{ marginBottom: 8 }}>
                  <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 }}>分析所选时间段全部消息</div>
                  <div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
                    {ANALYZE_PRESETS.map((preset) => (
                      <div
                        key={preset.label}
                        className={`chip ${analyzePreset === preset.label ? 'active' : ''}`}
                        style={{ fontSize: 11, padding: '3px 8px' }}
                        onClick={() => applyAnalyzePreset(preset)}
                      >
                        {preset.label}
                      </div>
                    ))}
                  </div>
                  <div style={{ display: 'flex', gap: 4, alignItems: 'center', fontSize: 11 }}>
                    <input
                      type="datetime-local"
                      className="filter-input"
                      value={analyzeStartDate}
                      onChange={(e) => { setAnalyzeStartDate(e.target.value); setAnalyzePreset('') }}
                      style={{ fontSize: 11, flex: 1, minWidth: 0 }}
                    />
                    <span style={{ color: 'var(--text-muted)' }}>至</span>
                    <input
                      type="datetime-local"
                      className="filter-input"
                      value={analyzeEndDate}
                      onChange={(e) => { setAnalyzeEndDate(e.target.value); setAnalyzePreset('') }}
                      style={{ fontSize: 11, flex: 1, minWidth: 0 }}
                    />
                  </div>
                </div>

                <div style={{ display: 'flex', gap: 6 }}>
                  <button
                    className="btn btn-primary"
                    style={{ flex: 1, fontSize: 12 }}
                    onClick={() => handleInitGroup(selectedGroup)}
                    disabled={analyzing}
                  >
                    {analyzing ? <><Loader size={12} /> 分析中...</> : <><Sparkles size={12} /> AI 分析</>}
                  </button>
                  <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => loadTopics(selectedGroup.id)}>
                    <RefreshCw size={12} />
                  </button>
                </div>

                {analyzing && (
                  <div style={{ marginTop: 8 }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 }}>
                      <span>分析中...</span>
                      <span>
                        {selectedInitState.progress
                          ? `${selectedInitState.progress.processed ?? 0} / ${selectedInitState.progress.total > 0 ? selectedInitState.progress.total : '?'}`
                          : '准备中...'}
                      </span>
                    </div>
                    <div style={{ height: 4, background: 'var(--border)', borderRadius: 2, overflow: 'hidden' }}>
                      <div style={{
                        height: '100%',
                        width: selectedInitState.progress && selectedInitState.progress.total > 0
                          ? `${Math.min(100, (selectedInitState.progress.processed / selectedInitState.progress.total) * 100)}%`
                          : '0%',
                        background: 'var(--accent)',
                        borderRadius: 2,
                        transition: 'width 0.5s ease',
                      }} />
                    </div>
                  </div>
                )}
              </div>

              <div style={{ padding: '8px 12px', borderBottom: '1px solid var(--border)', display: 'flex', gap: 6 }}>
                <input
                  className="filter-input"
                  style={{ flex: 1, fontSize: 12 }}
                  placeholder="新建手动话题..."
                  value={newTopicTitle}
                  onChange={(e) => setNewTopicTitle(e.target.value)}
                  onKeyDown={(e) => e.key === 'Enter' && handleCreateTopic()}
                />
                <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={handleCreateTopic}>
                  <Plus size={13} />
                </button>
              </div>

              <div style={{ flex: 1, overflowY: 'auto' }}>
                {loading && (
                  <div style={{ padding: 20, textAlign: 'center', fontSize: 12, color: 'var(--text-muted)' }}>加载中...</div>
                )}
                {!loading && topics.length === 0 && (
                  <div style={{ padding: 20, fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>
                    暂无话题。
                  </div>
                )}
                {topics.map((topic) => (
                  <div
                    key={topic.id}
                    onClick={() => handleSelectTopic(topic)}
                    style={{
                      padding: '10px 14px',
                      cursor: 'pointer',
                      borderBottom: '1px solid var(--border)',
                      background: selectedTopic?.id === topic.id ? 'var(--surface-2)' : 'transparent',
                      display: 'flex',
                      alignItems: 'flex-start',
                      gap: 8,
                    }}
                  >
                    <div style={{ flex: 1, overflow: 'hidden' }}>
                      <div style={{ fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {topic.title}
                      </div>
                      <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>
                        {dayjs(topic.created_at).format('MM-DD HH:mm')} / {topic.status}
                      </div>
                    </div>
                    <button
                      className="btn btn-ghost"
                      style={{ padding: '2px 4px', opacity: 0.5 }}
                      onClick={(e) => { e.stopPropagation(); handleDeleteTopic(topic) }}
                    >
                      <Trash2 size={12} />
                    </button>
                  </div>
                ))}
              </div>
            </>
          )}
        </div>

        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
          {!selectedTopic ? (
            <div className="empty-state" style={{ flex: 1 }}>
              <div className="empty-state-icon">话题</div>
              <div className="empty-state-title">请选择话题</div>
            </div>
          ) : (
            <>
              <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <div style={{ fontWeight: 600, fontSize: 15 }}>{selectedTopic.title}</div>
                <div style={{ display: 'flex', gap: 8 }}>
                  <button className="btn btn-ghost" style={{ borderColor: 'rgba(99,102,241,0.4)', color: 'var(--accent-light)' }} onClick={handleOpenMsgManager}>
                    <Settings2 size={13} /> 管理消息
                  </button>
                  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4 }}>
                    <button
                      className="btn btn-ghost"
                      style={{ borderColor: 'rgba(99,102,241,0.4)', color: 'var(--accent-light)' }}
                      onClick={handleSummarize}
                      disabled={summarizing}
                    >
                      {summarizing ? <Loader size={13} /> : <Sparkles size={13} />}
                      {summarizing ? '生成中...' : 'AI 报告'}
                    </button>
                    {summarizing && (
                      <div style={{ width: 180 }}>
                        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--text-muted)', marginBottom: 3 }}>
                          <span>处理中...</span>
                          <span>{summarizeProgress ? `${summarizeProgress.processed ?? 0}/${summarizeProgress.total > 0 ? summarizeProgress.total : '?'}` : '准备中...'}</span>
                        </div>
                        <div style={{ height: 3, background: 'var(--border)', borderRadius: 2, overflow: 'hidden' }}>
                          <div style={{
                            height: '100%',
                            width: summarizeProgress && summarizeProgress.total > 0
                              ? `${Math.min(100, (summarizeProgress.processed / summarizeProgress.total) * 100)}%`
                              : '30%',
                            background: 'var(--accent)',
                            borderRadius: 2,
                            transition: 'width 0.5s ease',
                          }} />
                        </div>
                      </div>
                    )}
                  </div>
                </div>
              </div>

              <div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px' }}>
                {topicDetail?.knowledge_doc && (
                  <div style={{ marginBottom: 24 }}>
                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 10 }}>
                      <div style={{ fontSize: 12, color: 'var(--text-muted)', fontWeight: 600 }}>报告</div>
                      <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={handleExportReport}>
                        <Download size={13} /> 导出 Word
                      </button>
                    </div>
                    <ReportDocumentView content={topicDetail.knowledge_doc.content} />
                    <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 6 }}>
                      更新于 {dayjs(topicDetail.knowledge_doc.updated_at).format('YYYY-MM-DD HH:mm')}
                    </div>
                  </div>
                )}

                {topicDetail && (
                  <div>
                    <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8, fontWeight: 600 }}>
                      关联消息（{topicDetail.messages?.length ?? 0} 条）
                    </div>
                    {(topicDetail.messages || []).length === 0 && (
                      <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>该话题暂无消息。</div>
                    )}
                    {(topicDetail.messages || []).map((message, index) => (
                      <MessageBubble key={message.seq || index} msg={toMsgBubbleFormat(message, index)} />
                    ))}
                  </div>
                )}

                {!topicDetail && (
                  <div style={{ textAlign: 'center', padding: 20, color: 'var(--text-muted)', fontSize: 13 }}>加载中...</div>
                )}
              </div>
            </>
          )}
        </div>
      </div>

      {showMsgManager && selectedTopic && (
        <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <div style={{ background: '#ffffff', borderRadius: 12, width: 900, maxWidth: '95vw', height: '80vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}>
            <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
              <span style={{ fontWeight: 600, fontSize: 15 }}>{selectedTopic.title} / 消息管理</span>
              <button onClick={() => setShowMsgManager(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)' }}>
                <X size={18} />
              </button>
            </div>

            <div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border)', flexShrink: 0, display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
              <input type="datetime-local" className="filter-input" value={msgStartDate} onChange={(e) => setMsgStartDate(e.target.value)} style={{ fontSize: 12 }} />
              <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>至</span>
              <input type="datetime-local" className="filter-input" value={msgEndDate} onChange={(e) => setMsgEndDate(e.target.value)} style={{ fontSize: 12 }} />
              <select className="filter-input" style={{ fontSize: 12 }} value={msgMembers[0] || ''} onChange={(e) => setMsgMembers(e.target.value ? [e.target.value] : [])}>
                <option value="">全部成员</option>
                {msgAllMembers.map((member) => (
                  <option key={member.platformId} value={member.platformId}>{member.accountName}</option>
                ))}
              </select>
              <input className="filter-input" style={{ fontSize: 12, flex: 1, minWidth: 120 }} placeholder="搜索消息..." value={msgSearch} onChange={(e) => setMsgSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && fetchMsgs()} />
              <button className="btn btn-primary btn-sm" onClick={fetchMsgs} style={{ fontSize: 12 }}>搜索</button>
            </div>

            <div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
              <div style={{ flex: 1, borderRight: '1px solid var(--border)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
                <div style={{ padding: '8px 14px', fontSize: 12, color: 'var(--text-muted)', fontWeight: 600, borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
                  聊天记录消息
                </div>
                <div style={{ flex: 1, overflowY: 'auto' }}>
                  {msgLoading && <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>加载中...</div>}
                  {!msgLoading && (() => {
                    const topicSeqs = new Set((topicDetail?.messages || []).map((m) => Number(m.seq)))
                    return allMsgs.map((message) => (
                      <div key={message.id} style={{ display: 'flex', alignItems: 'flex-start', borderBottom: '1px solid var(--border)' }}>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <MessageBubble msg={message} />
                        </div>
                        <div style={{ flexShrink: 0, padding: '12px 10px' }}>
                          {topicSeqs.has(Number(message.id)) ? (
                            <button
                              className="btn btn-ghost btn-sm"
                              style={{ fontSize: 12, color: 'var(--danger)', borderColor: 'rgba(243,139,168,0.4)' }}
                              title="从话题中移除"
                              onClick={() => handleRemoveMsg(Number(message.id))}
                            >
                              移除
                            </button>
                          ) : (
                            <button className="btn btn-ghost btn-sm" style={{ fontSize: 12 }} onClick={() => handleAddMsg(message)}>添加</button>
                          )}
                        </div>
                      </div>
                    ))
                  })()}
                </div>
              </div>

              <div style={{ width: 340, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
                <div style={{ padding: '8px 14px', fontSize: 12, color: 'var(--text-muted)', fontWeight: 600, borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
                  话题消息
                </div>
                <div style={{ flex: 1, overflowY: 'auto', padding: '8px 0' }}>
                  {(topicDetail?.messages || []).length === 0 && (
                    <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>暂无已选消息。</div>
                  )}
                  {(topicDetail?.messages || []).map((message) => (
                    <div key={message.seq} style={{ display: 'flex', alignItems: 'flex-start', gap: 8, padding: '6px 14px', borderBottom: '1px solid var(--border)' }}>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 2 }}>{message.sender_name || message.sender}</div>
                        <div style={{ fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                          {topicMsgPreview(message)}
                        </div>
                      </div>
                      <button className="btn btn-ghost btn-sm" style={{ flexShrink: 0, fontSize: 12, color: 'var(--danger)' }} onClick={() => handleRemoveMsg(message.seq)}>移除</button>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          </div>
        </div>
      )}

      {showGroupPrompt && selectedGroup && (
        <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <div style={{ background: '#fff', borderRadius: 10, width: 620, maxWidth: '92vw', boxShadow: '0 8px 32px rgba(0,0,0,0.2)', overflow: 'hidden' }}>
            <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <div>
                <div style={{ fontWeight: 600, fontSize: 15 }}>AI 分析提示词</div>
                <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>{selectedGroup.name || selectedGroup.talker}</div>
              </div>
              <button onClick={() => setShowGroupPrompt(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)' }}>
                <X size={18} />
              </button>
            </div>
            <div style={{ padding: 18 }}>
              <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>
                留空则使用设置页的全局默认提示词；填写后只对当前群生效。
              </div>
              <textarea
                value={groupPromptDraft}
                placeholder="例如：这个群主要分析某企业的设备售后问题，请按设备型号、故障现象、处理进度归类；不要把日常闲聊单独生成售后话题。"
                onChange={(e) => setGroupPromptDraft(e.target.value)}
                style={{
                  width: '100%',
                  minHeight: 180,
                  boxSizing: 'border-box',
                  border: '1px solid var(--border)',
                  borderRadius: 8,
                  padding: '10px 12px',
                  fontSize: 13,
                  lineHeight: 1.6,
                  resize: 'vertical',
                  outline: 'none',
                  fontFamily: 'inherit',
                }}
              />
              <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }}>
                <button className="btn btn-ghost" onClick={() => setShowGroupPrompt(false)} disabled={savingGroupPrompt}>取消</button>
                <button className="btn btn-primary" onClick={handleSaveGroupPrompt} disabled={savingGroupPrompt}>
                  {savingGroupPrompt ? <Loader size={13} /> : <Settings2 size={13} />}
                  {savingGroupPrompt ? '保存中...' : '保存提示词'}
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  )
}
