import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import {
  CheckSquare,
  Download,
  ExternalLink,
  FolderOpen,
  Image as ImageIcon,
  Loader2,
  MessageSquareText,
  Mic,
  Search,
  Square,
  Video,
  WandSparkles,
  X
} from 'lucide-react'
import type { ChatSession as AppChatSession, ContactInfo } from '../types/models'
import type { ExportOptions as ElectronExportOptions, ExportProgress } from '../types/electron'
import * as configService from '../services/config'
import './ExportPage.scss'

type ConversationTab = 'private' | 'group' | 'official'
type TaskStatus = 'queued' | 'running' | 'success' | 'error'
type TaskScope = 'single' | 'multi' | 'content'
type ContentType = 'text' | 'voice' | 'image' | 'video' | 'emoji'

type SessionLayout = 'shared' | 'per-session'

type DisplayNamePreference = 'group-nickname' | 'remark' | 'nickname'

type TextExportFormat = 'chatlab' | 'chatlab-jsonl' | 'json' | 'html' | 'txt' | 'excel' | 'weclone' | 'sql'

interface ExportOptions {
  format: TextExportFormat
  dateRange: { start: Date; end: Date } | null
  useAllTime: boolean
  exportAvatars: boolean
  exportMedia: boolean
  exportImages: boolean
  exportVoices: boolean
  exportVideos: boolean
  exportEmojis: boolean
  exportVoiceAsText: boolean
  excelCompactColumns: boolean
  txtColumns: string[]
  displayNamePreference: DisplayNamePreference
  exportConcurrency: number
}

interface SessionRow extends AppChatSession {
  kind: ConversationTab
  wechatId?: string
}

interface SessionMetrics {
  totalMessages?: number
  voiceMessages?: number
  imageMessages?: number
  videoMessages?: number
  emojiMessages?: number
  privateMutualGroups?: number
  groupMemberCount?: number
  groupMyMessages?: number
  groupActiveSpeakers?: number
  groupMutualFriends?: number
  firstTimestamp?: number
  lastTimestamp?: number
}

interface TaskProgress {
  current: number
  total: number
  currentName: string
  phaseLabel: string
  phaseProgress: number
  phaseTotal: number
}

interface ExportTaskPayload {
  sessionIds: string[]
  outputDir: string
  options: ElectronExportOptions
  scope: TaskScope
  contentType?: ContentType
  sessionNames: string[]
}

interface ExportTask {
  id: string
  title: string
  status: TaskStatus
  createdAt: number
  startedAt?: number
  finishedAt?: number
  error?: string
  payload: ExportTaskPayload
  progress: TaskProgress
}

interface ExportDialogState {
  open: boolean
  scope: TaskScope
  contentType?: ContentType
  sessionIds: string[]
  sessionNames: string[]
  title: string
}

interface CurrentUserProfile {
  wxid: string
  displayName: string
  avatarUrl?: string
}

const defaultTxtColumns = ['index', 'time', 'senderRole', 'messageType', 'content']
const contentTypeLabels: Record<ContentType, string> = {
  text: '聊天文本',
  voice: '语音',
  image: '图片',
  video: '视频',
  emoji: '表情包'
}

const formatOptions: Array<{ value: TextExportFormat; label: string; desc: string }> = [
  { value: 'chatlab', label: 'ChatLab', desc: '标准格式，支持其他软件导入' },
  { value: 'chatlab-jsonl', label: 'ChatLab JSONL', desc: '流式格式，适合大量消息' },
  { value: 'json', label: 'JSON', desc: '详细格式，包含完整消息信息' },
  { value: 'html', label: 'HTML', desc: '网页格式，可直接浏览' },
  { value: 'txt', label: 'TXT', desc: '纯文本，通用格式' },
  { value: 'excel', label: 'Excel', desc: '电子表格，适合统计分析' },
  { value: 'weclone', label: 'WeClone CSV', desc: 'WeClone 兼容字段格式（CSV）' },
  { value: 'sql', label: 'PostgreSQL', desc: '数据库脚本，便于导入到数据库' }
]

const displayNameOptions: Array<{ value: DisplayNamePreference; label: string; desc: string }> = [
  { value: 'group-nickname', label: '群昵称优先', desc: '仅群聊有效，私聊显示备注/昵称' },
  { value: 'remark', label: '备注优先', desc: '有备注显示备注，否则显示昵称' },
  { value: 'nickname', label: '微信昵称', desc: '始终显示微信昵称' }
]

const writeLayoutOptions: Array<{ value: configService.ExportWriteLayout; label: string; desc: string }> = [
  {
    value: 'A',
    label: 'A（类型分目录）',
    desc: '聊天文本、语音、视频、表情包、图片分别创建文件夹'
  },
  {
    value: 'B',
    label: 'B（文本根目录+媒体按会话）',
    desc: '聊天文本在根目录；媒体按类型目录后再按会话分目录'
  },
  {
    value: 'C',
    label: 'C（按会话分目录）',
    desc: '每个会话一个目录，目录内包含文本与媒体文件'
  }
]

const createEmptyProgress = (): TaskProgress => ({
  current: 0,
  total: 0,
  currentName: '',
  phaseLabel: '',
  phaseProgress: 0,
  phaseTotal: 0
})

const formatAbsoluteDate = (timestamp: number): string => {
  const d = new Date(timestamp)
  const y = d.getFullYear()
  const m = `${d.getMonth() + 1}`.padStart(2, '0')
  const day = `${d.getDate()}`.padStart(2, '0')
  return `${y}-${m}-${day}`
}

const formatRecentExportTime = (timestamp?: number, now = Date.now()): string => {
  if (!timestamp) return '未导出'
  const diff = Math.max(0, now - timestamp)
  const minute = 60 * 1000
  const hour = 60 * minute
  const day = 24 * hour
  if (diff < hour) {
    const minutes = Math.max(1, Math.floor(diff / minute))
    return `${minutes} 分钟前`
  }
  if (diff < day) {
    const hours = Math.max(1, Math.floor(diff / hour))
    return `${hours} 小时前`
  }
  return formatAbsoluteDate(timestamp)
}

const formatDateInputValue = (date: Date): string => {
  const y = date.getFullYear()
  const m = `${date.getMonth() + 1}`.padStart(2, '0')
  const d = `${date.getDate()}`.padStart(2, '0')
  return `${y}-${m}-${d}`
}

const parseDateInput = (value: string, endOfDay: boolean): Date => {
  const [year, month, day] = value.split('-').map(v => Number(v))
  const date = new Date(year, month - 1, day)
  if (endOfDay) {
    date.setHours(23, 59, 59, 999)
  } else {
    date.setHours(0, 0, 0, 0)
  }
  return date
}

const toKindByContactType = (session: AppChatSession, contact?: ContactInfo): ConversationTab => {
  if (session.username.endsWith('@chatroom')) return 'group'
  if (contact?.type === 'official') return 'official'
  return 'private'
}

const getAvatarLetter = (name: string): string => {
  if (!name) return '?'
  return [...name][0] || '?'
}

const valueOrDash = (value?: number): string => {
  if (value === undefined || value === null) return '--'
  return value.toLocaleString()
}

const timestampOrDash = (timestamp?: number): string => {
  if (!timestamp) return '--'
  return formatAbsoluteDate(timestamp * 1000)
}

const createTaskId = (): string => `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`

function ExportPage() {
  const location = useLocation()

  const [isLoading, setIsLoading] = useState(true)
  const [sessions, setSessions] = useState<SessionRow[]>([])
  const [contactMap, setContactMap] = useState<Record<string, ContactInfo>>({})
  const [groupMemberCountMap, setGroupMemberCountMap] = useState<Record<string, number>>({})
  const [sessionMetrics, setSessionMetrics] = useState<Record<string, SessionMetrics>>({})
  const [searchKeyword, setSearchKeyword] = useState('')
  const [activeTab, setActiveTab] = useState<ConversationTab>('private')
  const [selectedSessions, setSelectedSessions] = useState<Set<string>>(new Set())

  const [currentUser, setCurrentUser] = useState<CurrentUserProfile>({ wxid: '', displayName: '未识别用户' })

  const [exportFolder, setExportFolder] = useState('')
  const [writeLayout, setWriteLayout] = useState<configService.ExportWriteLayout>('A')
  const [showWriteLayoutSelect, setShowWriteLayoutSelect] = useState(false)

  const [options, setOptions] = useState<ExportOptions>({
    format: 'excel',
    dateRange: {
      start: new Date(new Date().setHours(0, 0, 0, 0)),
      end: new Date()
    },
    useAllTime: false,
    exportAvatars: true,
    exportMedia: false,
    exportImages: true,
    exportVoices: true,
    exportVideos: true,
    exportEmojis: true,
    exportVoiceAsText: false,
    excelCompactColumns: true,
    txtColumns: defaultTxtColumns,
    displayNamePreference: 'remark',
    exportConcurrency: 2
  })

  const [exportDialog, setExportDialog] = useState<ExportDialogState>({
    open: false,
    scope: 'single',
    sessionIds: [],
    sessionNames: [],
    title: ''
  })

  const [tasks, setTasks] = useState<ExportTask[]>([])
  const [lastExportBySession, setLastExportBySession] = useState<Record<string, number>>({})
  const [lastExportByContent, setLastExportByContent] = useState<Record<string, number>>({})
  const [nowTick, setNowTick] = useState(Date.now())

  const progressUnsubscribeRef = useRef<(() => void) | null>(null)
  const runningTaskIdRef = useRef<string | null>(null)
  const tasksRef = useRef<ExportTask[]>([])
  const loadingMetricsRef = useRef<Set<string>>(new Set())
  const preselectAppliedRef = useRef(false)

  useEffect(() => {
    tasksRef.current = tasks
  }, [tasks])

  const preselectSessionIds = useMemo(() => {
    const state = location.state as { preselectSessionIds?: unknown; preselectSessionId?: unknown } | null
    const rawList = Array.isArray(state?.preselectSessionIds)
      ? state?.preselectSessionIds
      : (typeof state?.preselectSessionId === 'string' ? [state.preselectSessionId] : [])

    return rawList
      .filter((item): item is string => typeof item === 'string')
      .map(item => item.trim())
      .filter(Boolean)
  }, [location.state])

  useEffect(() => {
    const timer = setInterval(() => setNowTick(Date.now()), 60 * 1000)
    return () => clearInterval(timer)
  }, [])

  const loadCurrentUser = useCallback(async () => {
    try {
      const wxid = await configService.getMyWxid()
      let displayName = wxid || '未识别用户'
      let avatarUrl: string | undefined

      if (wxid) {
        const myContact = await window.electronAPI.chat.getContact(wxid)
        const bestName = [myContact?.remark, myContact?.nickName, myContact?.alias, wxid].find(Boolean)
        if (bestName) displayName = bestName
      }

      const avatarResult = await window.electronAPI.chat.getMyAvatarUrl()
      if (avatarResult.success && avatarResult.avatarUrl) {
        avatarUrl = avatarResult.avatarUrl
      }

      setCurrentUser({ wxid: wxid || '', displayName, avatarUrl })
    } catch (error) {
      console.error('加载当前用户信息失败:', error)
    }
  }, [])

  const loadBaseConfig = useCallback(async () => {
    try {
      const [savedPath, savedFormat, savedMedia, savedVoiceAsText, savedExcelCompactColumns, savedTxtColumns, savedConcurrency, savedWriteLayout, savedSessionMap, savedContentMap] = await Promise.all([
        configService.getExportPath(),
        configService.getExportDefaultFormat(),
        configService.getExportDefaultMedia(),
        configService.getExportDefaultVoiceAsText(),
        configService.getExportDefaultExcelCompactColumns(),
        configService.getExportDefaultTxtColumns(),
        configService.getExportDefaultConcurrency(),
        configService.getExportWriteLayout(),
        configService.getExportLastSessionRunMap(),
        configService.getExportLastContentRunMap()
      ])

      if (savedPath) {
        setExportFolder(savedPath)
      } else {
        const downloadsPath = await window.electronAPI.app.getDownloadsPath()
        setExportFolder(downloadsPath)
      }

      setWriteLayout(savedWriteLayout)
      setLastExportBySession(savedSessionMap)
      setLastExportByContent(savedContentMap)

      const txtColumns = savedTxtColumns && savedTxtColumns.length > 0 ? savedTxtColumns : defaultTxtColumns
      setOptions(prev => ({
        ...prev,
        format: (savedFormat as TextExportFormat) || prev.format,
        exportMedia: savedMedia ?? prev.exportMedia,
        exportVoiceAsText: savedVoiceAsText ?? prev.exportVoiceAsText,
        excelCompactColumns: savedExcelCompactColumns ?? prev.excelCompactColumns,
        txtColumns,
        exportConcurrency: savedConcurrency ?? prev.exportConcurrency
      }))
    } catch (error) {
      console.error('加载导出配置失败:', error)
    }
  }, [])

  const loadSessions = useCallback(async () => {
    setIsLoading(true)
    try {
      const connectResult = await window.electronAPI.chat.connect()
      if (!connectResult.success) {
        console.error('连接失败:', connectResult.error)
        setIsLoading(false)
        return
      }

      const [sessionsResult, contactsResult, groupChatsResult] = await Promise.all([
        window.electronAPI.chat.getSessions(),
        window.electronAPI.chat.getContacts(),
        window.electronAPI.groupAnalytics.getGroupChats()
      ])

      const contacts: ContactInfo[] = contactsResult.success && contactsResult.contacts ? contactsResult.contacts : []
      const nextContactMap = contacts.reduce<Record<string, ContactInfo>>((map, contact) => {
        map[contact.username] = contact
        return map
      }, {})
      setContactMap(nextContactMap)

      const nextGroupMemberCountMap: Record<string, number> = {}
      if (groupChatsResult.success && groupChatsResult.data) {
        for (const group of groupChatsResult.data) {
          nextGroupMemberCountMap[group.username] = group.memberCount
        }
      }
      setGroupMemberCountMap(nextGroupMemberCountMap)

      if (sessionsResult.success && sessionsResult.sessions) {
        const nextSessions = sessionsResult.sessions
          .map((session) => {
            const contact = nextContactMap[session.username]
            const kind = toKindByContactType(session, contact)
            return {
              ...session,
              kind,
              wechatId: contact?.username || session.username,
              displayName: session.displayName || contact?.displayName || session.username,
              avatarUrl: session.avatarUrl || contact?.avatarUrl
            } as SessionRow
          })
          .sort((a, b) => (b.sortTimestamp || b.lastTimestamp || 0) - (a.sortTimestamp || a.lastTimestamp || 0))

        setSessions(nextSessions)
      }
    } catch (error) {
      console.error('加载会话失败:', error)
    } finally {
      setIsLoading(false)
    }
  }, [])

  useEffect(() => {
    loadCurrentUser()
    loadBaseConfig()
    loadSessions()
  }, [loadCurrentUser, loadBaseConfig, loadSessions])

  useEffect(() => {
    preselectAppliedRef.current = false
  }, [location.key, preselectSessionIds])

  useEffect(() => {
    if (preselectAppliedRef.current) return
    if (sessions.length === 0 || preselectSessionIds.length === 0) return

    const exists = new Set(sessions.map(session => session.username))
    const matched = preselectSessionIds.filter(id => exists.has(id))
    preselectAppliedRef.current = true

    if (matched.length > 0) {
      setSelectedSessions(new Set(matched))
    }
  }, [sessions, preselectSessionIds])

  const visibleSessions = useMemo(() => {
    const keyword = searchKeyword.trim().toLowerCase()
    return sessions.filter((session) => {
      if (session.kind !== activeTab) return false
      if (!keyword) return true
      return (
        (session.displayName || '').toLowerCase().includes(keyword) ||
        session.username.toLowerCase().includes(keyword)
      )
    })
  }, [sessions, activeTab, searchKeyword])

  const ensureSessionMetrics = useCallback(async (targetSessions: SessionRow[]) => {
    const pending = targetSessions.filter(session => !sessionMetrics[session.username] && !loadingMetricsRef.current.has(session.username))
    if (pending.length === 0) return

    for (const session of pending) {
      loadingMetricsRef.current.add(session.username)
    }

    const updates: Record<string, SessionMetrics> = {}

    for (const session of pending) {
      const metrics: SessionMetrics = {}
      try {
        const detailResult = await window.electronAPI.chat.getSessionDetail(session.username)
        if (detailResult.success && detailResult.detail) {
          metrics.totalMessages = detailResult.detail.messageCount
          metrics.firstTimestamp = detailResult.detail.firstMessageTime
          metrics.lastTimestamp = detailResult.detail.latestMessageTime
        }

        const exportStats = await window.electronAPI.export.getExportStats([session.username], {
          exportVoiceAsText: false,
          exportMedia: true,
          exportImages: true,
          exportVoices: true,
          exportVideos: true,
          exportEmojis: true,
          dateRange: null
        })
        metrics.voiceMessages = exportStats.voiceMessages
        if (metrics.totalMessages === undefined) {
          metrics.totalMessages = exportStats.totalMessages
        }

        if (session.kind === 'group') {
          metrics.groupMemberCount = groupMemberCountMap[session.username]

          const [mediaStatsResult, rankingResult] = await Promise.all([
            window.electronAPI.groupAnalytics.getGroupMediaStats(session.username),
            window.electronAPI.groupAnalytics.getGroupMessageRanking(session.username)
          ])

          if (mediaStatsResult.success && mediaStatsResult.data?.typeCounts) {
            for (const item of mediaStatsResult.data.typeCounts) {
              const n = item.name.toLowerCase()
              if (n.includes('图片')) metrics.imageMessages = item.count
              if (n.includes('视频')) metrics.videoMessages = item.count
              if (n.includes('语音')) metrics.voiceMessages = item.count
              if (n.includes('表情')) metrics.emojiMessages = item.count
            }
          }

          if (rankingResult.success && rankingResult.data) {
            metrics.groupActiveSpeakers = rankingResult.data.length
            const selfWxid = session.selfWxid || currentUser.wxid
            const me = rankingResult.data.find(item => item.member.username === selfWxid)
            if (me) {
              metrics.groupMyMessages = me.messageCount
            }
          }
        }
      } catch (error) {
        console.error('加载会话统计失败:', session.username, error)
      } finally {
        loadingMetricsRef.current.delete(session.username)
      }

      updates[session.username] = metrics
    }

    if (Object.keys(updates).length > 0) {
      setSessionMetrics(prev => ({ ...prev, ...updates }))
    }
  }, [sessionMetrics, groupMemberCountMap, currentUser.wxid])

  useEffect(() => {
    const targets = visibleSessions.slice(0, 40)
    void ensureSessionMetrics(targets)
  }, [visibleSessions, ensureSessionMetrics])

  const selectedCount = selectedSessions.size

  const toggleSelectSession = (sessionId: string) => {
    setSelectedSessions(prev => {
      const next = new Set(prev)
      if (next.has(sessionId)) {
        next.delete(sessionId)
      } else {
        next.add(sessionId)
      }
      return next
    })
  }

  const toggleSelectAllVisible = () => {
    const visibleIds = visibleSessions.map(session => session.username)
    if (visibleIds.length === 0) return

    setSelectedSessions(prev => {
      const next = new Set(prev)
      const allSelected = visibleIds.every(id => next.has(id))
      if (allSelected) {
        for (const id of visibleIds) {
          next.delete(id)
        }
      } else {
        for (const id of visibleIds) {
          next.add(id)
        }
      }
      return next
    })
  }

  const clearSelection = () => setSelectedSessions(new Set())

  const openExportDialog = (payload: Omit<ExportDialogState, 'open'>) => {
    setExportDialog({ open: true, ...payload })

    if (payload.scope === 'content' && payload.contentType) {
      if (payload.contentType === 'text') {
        setOptions(prev => ({ ...prev, exportMedia: false }))
      } else {
        setOptions(prev => ({
          ...prev,
          exportMedia: true,
          exportImages: payload.contentType === 'image',
          exportVoices: payload.contentType === 'voice',
          exportVideos: payload.contentType === 'video',
          exportEmojis: payload.contentType === 'emoji'
        }))
      }
    }
  }

  const closeExportDialog = () => {
    setExportDialog(prev => ({ ...prev, open: false }))
  }

  const buildExportOptions = (scope: TaskScope, contentType?: ContentType): ElectronExportOptions => {
    const sessionLayout: SessionLayout = writeLayout === 'C' ? 'per-session' : 'shared'

    const base: ElectronExportOptions = {
      format: options.format,
      exportAvatars: options.exportAvatars,
      exportMedia: options.exportMedia,
      exportImages: options.exportMedia && options.exportImages,
      exportVoices: options.exportMedia && options.exportVoices,
      exportVideos: options.exportMedia && options.exportVideos,
      exportEmojis: options.exportMedia && options.exportEmojis,
      exportVoiceAsText: options.exportVoiceAsText,
      excelCompactColumns: options.excelCompactColumns,
      txtColumns: options.txtColumns,
      displayNamePreference: options.displayNamePreference,
      exportConcurrency: options.exportConcurrency,
      sessionLayout,
      dateRange: options.useAllTime
        ? null
        : options.dateRange
          ? {
              start: Math.floor(options.dateRange.start.getTime() / 1000),
              end: Math.floor(options.dateRange.end.getTime() / 1000)
            }
          : null
    }

    if (scope === 'content' && contentType) {
      if (contentType === 'text') {
        return {
          ...base,
          exportMedia: false,
          exportImages: false,
          exportVoices: false,
          exportVideos: false,
          exportEmojis: false
        }
      }

      return {
        ...base,
        exportMedia: true,
        exportImages: contentType === 'image',
        exportVoices: contentType === 'voice',
        exportVideos: contentType === 'video',
        exportEmojis: contentType === 'emoji'
      }
    }

    return base
  }

  const markSessionExported = useCallback((sessionIds: string[], timestamp: number) => {
    setLastExportBySession(prev => {
      const next = { ...prev }
      for (const id of sessionIds) {
        next[id] = timestamp
      }
      void configService.setExportLastSessionRunMap(next)
      return next
    })
  }, [])

  const markContentExported = useCallback((sessionIds: string[], contentTypes: ContentType[], timestamp: number) => {
    setLastExportByContent(prev => {
      const next = { ...prev }
      for (const id of sessionIds) {
        for (const type of contentTypes) {
          next[`${id}::${type}`] = timestamp
        }
      }
      void configService.setExportLastContentRunMap(next)
      return next
    })
  }, [])

  const inferContentTypesFromOptions = (opts: ElectronExportOptions): ContentType[] => {
    const types: ContentType[] = ['text']
    if (opts.exportMedia) {
      if (opts.exportVoices) types.push('voice')
      if (opts.exportImages) types.push('image')
      if (opts.exportVideos) types.push('video')
      if (opts.exportEmojis) types.push('emoji')
    }
    return types
  }

  const updateTask = useCallback((taskId: string, updater: (task: ExportTask) => ExportTask) => {
    setTasks(prev => prev.map(task => (task.id === taskId ? updater(task) : task)))
  }, [])

  const runNextTask = useCallback(async () => {
    if (runningTaskIdRef.current) return

    const queue = [...tasksRef.current].reverse()
    const next = queue.find(task => task.status === 'queued')
    if (!next) return

    runningTaskIdRef.current = next.id
    updateTask(next.id, task => ({ ...task, status: 'running', startedAt: Date.now() }))

    progressUnsubscribeRef.current?.()
    progressUnsubscribeRef.current = window.electronAPI.export.onProgress((payload: ExportProgress) => {
      updateTask(next.id, task => ({
        ...task,
        progress: {
          current: payload.current,
          total: payload.total,
          currentName: payload.currentSession,
          phaseLabel: payload.phaseLabel || '',
          phaseProgress: payload.phaseProgress || 0,
          phaseTotal: payload.phaseTotal || 0
        }
      }))
    })

    try {
      const result = await window.electronAPI.export.exportSessions(
        next.payload.sessionIds,
        next.payload.outputDir,
        next.payload.options
      )

      if (!result.success) {
        updateTask(next.id, task => ({
          ...task,
          status: 'error',
          finishedAt: Date.now(),
          error: result.error || '导出失败'
        }))
      } else {
        const doneAt = Date.now()
        const contentTypes = next.payload.contentType
          ? [next.payload.contentType]
          : inferContentTypesFromOptions(next.payload.options)

        markSessionExported(next.payload.sessionIds, doneAt)
        markContentExported(next.payload.sessionIds, contentTypes, doneAt)

        updateTask(next.id, task => ({
          ...task,
          status: 'success',
          finishedAt: doneAt,
          progress: {
            ...task.progress,
            current: task.progress.total || next.payload.sessionIds.length,
            total: task.progress.total || next.payload.sessionIds.length,
            phaseLabel: '完成',
            phaseProgress: 1,
            phaseTotal: 1
          }
        }))
      }
    } catch (error) {
      updateTask(next.id, task => ({
        ...task,
        status: 'error',
        finishedAt: Date.now(),
        error: String(error)
      }))
    } finally {
      progressUnsubscribeRef.current?.()
      progressUnsubscribeRef.current = null
      runningTaskIdRef.current = null
      void runNextTask()
    }
  }, [updateTask, markSessionExported, markContentExported])

  useEffect(() => {
    void runNextTask()
  }, [tasks, runNextTask])

  useEffect(() => {
    return () => {
      progressUnsubscribeRef.current?.()
      progressUnsubscribeRef.current = null
    }
  }, [])

  const createTask = async () => {
    if (!exportDialog.open || exportDialog.sessionIds.length === 0 || !exportFolder) return

    const exportOptions = buildExportOptions(exportDialog.scope, exportDialog.contentType)
    const title =
      exportDialog.scope === 'single'
        ? `${exportDialog.sessionNames[0] || '会话'} 导出`
        : exportDialog.scope === 'multi'
          ? `批量导出（${exportDialog.sessionIds.length} 个会话）`
          : `${contentTypeLabels[exportDialog.contentType || 'text']}批量导出`

    const task: ExportTask = {
      id: createTaskId(),
      title,
      status: 'queued',
      createdAt: Date.now(),
      payload: {
        sessionIds: exportDialog.sessionIds,
        sessionNames: exportDialog.sessionNames,
        outputDir: exportFolder,
        options: exportOptions,
        scope: exportDialog.scope,
        contentType: exportDialog.contentType
      },
      progress: createEmptyProgress()
    }

    setTasks(prev => [task, ...prev])
    closeExportDialog()

    await configService.setExportDefaultFormat(options.format)
    await configService.setExportDefaultMedia(options.exportMedia)
    await configService.setExportDefaultVoiceAsText(options.exportVoiceAsText)
    await configService.setExportDefaultExcelCompactColumns(options.excelCompactColumns)
    await configService.setExportDefaultTxtColumns(options.txtColumns)
    await configService.setExportDefaultConcurrency(options.exportConcurrency)
  }

  const openSingleExport = (session: SessionRow) => {
    openExportDialog({
      scope: 'single',
      sessionIds: [session.username],
      sessionNames: [session.displayName || session.username],
      title: `导出会话：${session.displayName || session.username}`
    })
  }

  const openBatchExport = () => {
    const ids = Array.from(selectedSessions)
    if (ids.length === 0) return
    const nameMap = new Map(sessions.map(session => [session.username, session.displayName || session.username]))
    const names = ids.map(id => nameMap.get(id) || id)

    openExportDialog({
      scope: 'multi',
      sessionIds: ids,
      sessionNames: names,
      title: `批量导出（${ids.length} 个会话）`
    })
  }

  const openContentExport = (contentType: ContentType) => {
    const ids = sessions
      .filter(session => session.kind === 'private' || session.kind === 'group')
      .map(session => session.username)

    const names = sessions
      .filter(session => session.kind === 'private' || session.kind === 'group')
      .map(session => session.displayName || session.username)

    openExportDialog({
      scope: 'content',
      contentType,
      sessionIds: ids,
      sessionNames: names,
      title: `${contentTypeLabels[contentType]}批量导出`
    })
  }

  const runningSessionIds = useMemo(() => {
    const set = new Set<string>()
    for (const task of tasks) {
      if (task.status !== 'running') continue
      for (const id of task.payload.sessionIds) {
        set.add(id)
      }
    }
    return set
  }, [tasks])

  const queuedSessionIds = useMemo(() => {
    const set = new Set<string>()
    for (const task of tasks) {
      if (task.status !== 'queued') continue
      for (const id of task.payload.sessionIds) {
        set.add(id)
      }
    }
    return set
  }, [tasks])

  const contentCards = useMemo(() => {
    const scopeSessions = sessions.filter(session => session.kind === 'private' || session.kind === 'group')
    const total = scopeSessions.length

    return [
      { type: 'text' as ContentType, icon: MessageSquareText },
      { type: 'voice' as ContentType, icon: Mic },
      { type: 'image' as ContentType, icon: ImageIcon },
      { type: 'video' as ContentType, icon: Video },
      { type: 'emoji' as ContentType, icon: WandSparkles }
    ].map(item => {
      let exported = 0
      for (const session of scopeSessions) {
        if (lastExportByContent[`${session.username}::${item.type}`]) {
          exported += 1
        }
      }

      return {
        ...item,
        label: contentTypeLabels[item.type],
        total,
        exported
      }
    })
  }, [sessions, lastExportByContent])

  const activeTabLabel = useMemo(() => {
    if (activeTab === 'private') return '私聊'
    if (activeTab === 'group') return '群聊'
    return '公众号'
  }, [activeTab])

  const renderSessionName = (session: SessionRow) => {
    return (
      <div className="session-cell">
        <div className="session-avatar">
          {session.avatarUrl ? <img src={session.avatarUrl} alt="" /> : <span>{getAvatarLetter(session.displayName || session.username)}</span>}
        </div>
        <div className="session-meta">
          <div className="session-name">{session.displayName || session.username}</div>
          <div className="session-id">{session.wechatId || session.username}</div>
        </div>
      </div>
    )
  }

  const renderActionCell = (session: SessionRow) => {
    const isRunning = runningSessionIds.has(session.username)
    const isQueued = queuedSessionIds.has(session.username)
    const recent = formatRecentExportTime(lastExportBySession[session.username], nowTick)

    return (
      <div className="row-action-cell">
        <button
          className={`row-export-btn ${isRunning ? 'running' : ''}`}
          disabled={isRunning}
          onClick={() => openSingleExport(session)}
        >
          {isRunning ? (
            <>
              <Loader2 size={14} className="spin" />
              导出中
            </>
          ) : isQueued ? '排队中' : '导出'}
        </button>
        <span className="row-export-time">{recent}</span>
      </div>
    )
  }

  const renderTableHeader = () => {
    if (activeTab === 'private') {
      return (
        <tr>
          <th className="sticky-col">选择</th>
          <th>会话名（头像/昵称/微信号）</th>
          <th>总消息</th>
          <th>语音</th>
          <th>图片</th>
          <th>视频</th>
          <th>表情包</th>
          <th>共同群聊数</th>
          <th>最早时间</th>
          <th>最新时间</th>
          <th className="sticky-right">操作</th>
        </tr>
      )
    }

    if (activeTab === 'group') {
      return (
        <tr>
          <th className="sticky-col">选择</th>
          <th>会话名（群头像/群名称/群ID）</th>
          <th>总消息</th>
          <th>语音</th>
          <th>图片</th>
          <th>视频</th>
          <th>表情包</th>
          <th>我发的消息数</th>
          <th>群人数</th>
          <th>群发言人数</th>
          <th>群共同好友数</th>
          <th>最早时间</th>
          <th>最新时间</th>
          <th className="sticky-right">操作</th>
        </tr>
      )
    }

    return (
      <tr>
        <th className="sticky-col">选择</th>
        <th>会话名（头像/名称/微信号）</th>
        <th>总消息</th>
        <th>语音</th>
        <th>图片</th>
        <th>视频</th>
        <th>表情包</th>
        <th>最早时间</th>
        <th>最新时间</th>
        <th className="sticky-right">操作</th>
      </tr>
    )
  }

  const renderRow = (session: SessionRow) => {
    const metrics = sessionMetrics[session.username] || {}
    const checked = selectedSessions.has(session.username)

    return (
      <tr key={session.username} className={checked ? 'selected-row' : ''}>
        <td className="sticky-col">
          <button
            className={`select-icon-btn ${checked ? 'checked' : ''}`}
            onClick={() => toggleSelectSession(session.username)}
            title={checked ? '取消选择' : '选择会话'}
          >
            {checked ? <CheckSquare size={16} /> : <Square size={16} />}
          </button>
        </td>

        <td>{renderSessionName(session)}</td>
        <td>{valueOrDash(metrics.totalMessages)}</td>
        <td>{valueOrDash(metrics.voiceMessages)}</td>
        <td>{valueOrDash(metrics.imageMessages)}</td>
        <td>{valueOrDash(metrics.videoMessages)}</td>
        <td>{valueOrDash(metrics.emojiMessages)}</td>

        {activeTab === 'private' && (
          <>
            <td>{valueOrDash(metrics.privateMutualGroups)}</td>
            <td>{timestampOrDash(metrics.firstTimestamp)}</td>
            <td>{timestampOrDash(metrics.lastTimestamp)}</td>
          </>
        )}

        {activeTab === 'group' && (
          <>
            <td>{valueOrDash(metrics.groupMyMessages)}</td>
            <td>{valueOrDash(metrics.groupMemberCount)}</td>
            <td>{valueOrDash(metrics.groupActiveSpeakers)}</td>
            <td>{valueOrDash(metrics.groupMutualFriends)}</td>
            <td>{timestampOrDash(metrics.firstTimestamp)}</td>
            <td>{timestampOrDash(metrics.lastTimestamp)}</td>
          </>
        )}

        {activeTab === 'official' && (
          <>
            <td>{timestampOrDash(metrics.firstTimestamp)}</td>
            <td>{timestampOrDash(metrics.lastTimestamp)}</td>
          </>
        )}

        <td className="sticky-right">{renderActionCell(session)}</td>
      </tr>
    )
  }

  const visibleSelectedCount = useMemo(() => {
    const visibleSet = new Set(visibleSessions.map(session => session.username))
    let count = 0
    for (const id of selectedSessions) {
      if (visibleSet.has(id)) count += 1
    }
    return count
  }, [visibleSessions, selectedSessions])

  const writeLayoutLabel = writeLayoutOptions.find(option => option.value === writeLayout)?.label || 'A（类型分目录）'

  return (
    <div className="export-board-page">
      <div className="export-top-panel">
        <div className="current-user-box">
          <div className="avatar-wrap">
            {currentUser.avatarUrl ? <img src={currentUser.avatarUrl} alt="" /> : <span>{getAvatarLetter(currentUser.displayName)}</span>}
          </div>
          <div className="user-meta">
            <div className="user-name">{currentUser.displayName}</div>
            <div className="user-wxid">{currentUser.wxid || 'wxid 未识别'}</div>
          </div>
        </div>

        <div className="global-export-controls">
          <div className="path-control">
            <span className="control-label">导出位置</span>
            <div className="path-value" title={exportFolder}>{exportFolder || '未设置'}</div>
            <div className="path-actions">
              <button className="secondary-btn" onClick={() => exportFolder && void window.electronAPI.shell.openPath(exportFolder)}>
                <ExternalLink size={14} /> 打开目录
              </button>
              <button
                className="secondary-btn"
                onClick={async () => {
                  const result = await window.electronAPI.dialog.openFile({
                    title: '选择导出目录',
                    properties: ['openDirectory']
                  })
                  if (!result.canceled && result.filePaths.length > 0) {
                    const nextPath = result.filePaths[0]
                    setExportFolder(nextPath)
                    await configService.setExportPath(nextPath)
                  }
                }}
              >
                <FolderOpen size={14} /> 更换目录
              </button>
            </div>
          </div>

          <div className="write-layout-control">
            <span className="control-label">写入格式</span>
            <button className="layout-trigger" onClick={() => setShowWriteLayoutSelect(prev => !prev)}>
              {writeLayoutLabel}
            </button>
            {showWriteLayoutSelect && (
              <div className="layout-dropdown">
                {writeLayoutOptions.map(option => (
                  <button
                    key={option.value}
                    className={`layout-option ${writeLayout === option.value ? 'active' : ''}`}
                    onClick={async () => {
                      setWriteLayout(option.value)
                      setShowWriteLayoutSelect(false)
                      await configService.setExportWriteLayout(option.value)
                    }}
                  >
                    <span className="layout-option-label">{option.label}</span>
                    <span className="layout-option-desc">{option.desc}</span>
                  </button>
                ))}
              </div>
            )}
          </div>
        </div>
      </div>

      <div className="content-card-grid">
        {contentCards.map(card => {
          const Icon = card.icon
          return (
            <div key={card.type} className="content-card">
              <div className="card-header">
                <div className="card-title"><Icon size={16} /> {card.label}</div>
              </div>
              <div className="card-stats">
                <div className="stat-item">
                  <span>总会话数</span>
                  <strong>{card.total}</strong>
                </div>
                <div className="stat-item">
                  <span>已导出会话数</span>
                  <strong>{card.exported}</strong>
                </div>
              </div>
              <button className="card-export-btn" onClick={() => openContentExport(card.type)}>导出</button>
            </div>
          )
        })}
      </div>

      <div className="task-center">
        <div className="section-title">任务中心</div>
        {tasks.length === 0 ? (
          <div className="task-empty">暂无任务。点击会话导出或卡片导出后会在这里创建任务。</div>
        ) : (
          <div className="task-list">
            {tasks.map(task => (
              <div key={task.id} className={`task-card ${task.status}`}>
                <div className="task-main">
                  <div className="task-title">{task.title}</div>
                  <div className="task-meta">
                    <span className={`task-status ${task.status}`}>{task.status === 'queued' ? '排队中' : task.status === 'running' ? '进行中' : task.status === 'success' ? '已完成' : '失败'}</span>
                    <span>{new Date(task.createdAt).toLocaleString('zh-CN')}</span>
                  </div>
                  {task.status === 'running' && (
                    <>
                      <div className="task-progress-bar">
                        <div
                          className="task-progress-fill"
                          style={{ width: `${task.progress.total > 0 ? (task.progress.current / task.progress.total) * 100 : 0}%` }}
                        />
                      </div>
                      <div className="task-progress-text">
                        {task.progress.current} / {task.progress.total || task.payload.sessionIds.length}
                        {task.progress.phaseLabel ? ` · ${task.progress.phaseLabel}` : ''}
                      </div>
                    </>
                  )}
                  {task.status === 'error' && <div className="task-error">{task.error || '任务失败'}</div>}
                </div>
                <div className="task-actions">
                  <button className="secondary-btn" onClick={() => exportFolder && void window.electronAPI.shell.openPath(exportFolder)}>
                    <FolderOpen size={14} /> 目录
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="session-table-section">
        <div className="table-toolbar">
          <div className="table-tabs" role="tablist" aria-label="会话类型">
            <button className={`tab-btn ${activeTab === 'private' ? 'active' : ''}`} onClick={() => setActiveTab('private')}>私聊</button>
            <button className={`tab-btn ${activeTab === 'group' ? 'active' : ''}`} onClick={() => setActiveTab('group')}>群聊</button>
            <button className={`tab-btn ${activeTab === 'official' ? 'active' : ''}`} onClick={() => setActiveTab('official')}>公众号</button>
          </div>

          <div className="toolbar-actions">
            <div className="search-input-wrap">
              <Search size={14} />
              <input
                value={searchKeyword}
                onChange={(event) => setSearchKeyword(event.target.value)}
                placeholder={`搜索${activeTabLabel}会话...`}
              />
              {searchKeyword && (
                <button className="clear-search" onClick={() => setSearchKeyword('')}>
                  <X size={12} />
                </button>
              )}
            </div>

            <button className="secondary-btn" onClick={toggleSelectAllVisible}>
              {visibleSelectedCount > 0 && visibleSelectedCount === visibleSessions.length ? '取消全选' : '全选当前'}
            </button>

            {selectedCount > 0 && (
              <div className="selected-batch-actions">
                <span>已选中 {selectedCount} 个会话</span>
                <button className="primary-btn" onClick={openBatchExport}>
                  <Download size={14} /> 导出
                </button>
                <button className="secondary-btn" onClick={clearSelection}>清空</button>
              </div>
            )}
          </div>
        </div>

        <div className="table-wrap">
          <table className="session-table">
            <thead>{renderTableHeader()}</thead>
            <tbody>
              {isLoading ? (
                <tr>
                  <td colSpan={activeTab === 'group' ? 14 : activeTab === 'private' ? 11 : 10}>
                    <div className="table-state"><Loader2 size={16} className="spin" />加载中...</div>
                  </td>
                </tr>
              ) : visibleSessions.length === 0 ? (
                <tr>
                  <td colSpan={activeTab === 'group' ? 14 : activeTab === 'private' ? 11 : 10}>
                    <div className="table-state">暂无会话</div>
                  </td>
                </tr>
              ) : (
                visibleSessions.map(renderRow)
              )}
            </tbody>
          </table>
        </div>
      </div>

      {exportDialog.open && (
        <div className="export-dialog-overlay" onClick={closeExportDialog}>
          <div className="export-dialog" onClick={(event) => event.stopPropagation()}>
            <div className="dialog-header">
              <h3>{exportDialog.title}</h3>
              <button className="close-icon-btn" onClick={closeExportDialog}><X size={16} /></button>
            </div>

            <div className="dialog-section">
              <h4>导出范围</h4>
              <div className="scope-tag-row">
                <span className="scope-tag">{exportDialog.scope === 'single' ? '单会话' : exportDialog.scope === 'multi' ? '多会话' : `按内容批量（${contentTypeLabels[exportDialog.contentType || 'text']}）`}</span>
                <span className="scope-count">共 {exportDialog.sessionIds.length} 个会话</span>
              </div>
              <div className="scope-list">
                {exportDialog.sessionNames.slice(0, 20).map(name => (
                  <span key={name} className="scope-item">{name}</span>
                ))}
                {exportDialog.sessionNames.length > 20 && <span className="scope-item">... 还有 {exportDialog.sessionNames.length - 20} 个</span>}
              </div>
            </div>

            <div className="dialog-section">
              <h4>对话文本导出格式选择</h4>
              <div className="format-grid">
                {formatOptions.map(option => (
                  <button
                    key={option.value}
                    className={`format-card ${options.format === option.value ? 'active' : ''}`}
                    onClick={() => setOptions(prev => ({ ...prev, format: option.value }))}
                  >
                    <div className="format-label">{option.label}</div>
                    <div className="format-desc">{option.desc}</div>
                  </button>
                ))}
              </div>
            </div>

            <div className="dialog-section">
              <h4>时间范围</h4>
              <div className="switch-row">
                <span>导出全部时间</span>
                <label className="switch">
                  <input
                    type="checkbox"
                    checked={options.useAllTime}
                    onChange={(event) => setOptions(prev => ({ ...prev, useAllTime: event.target.checked }))}
                  />
                  <span className="switch-slider"></span>
                </label>
              </div>

              {!options.useAllTime && options.dateRange && (
                <div className="date-range-row">
                  <label>
                    开始
                    <input
                      type="date"
                      value={formatDateInputValue(options.dateRange.start)}
                      onChange={(event) => {
                        const start = parseDateInput(event.target.value, false)
                        setOptions(prev => ({
                          ...prev,
                          dateRange: prev.dateRange ? {
                            start,
                            end: prev.dateRange.end < start ? parseDateInput(event.target.value, true) : prev.dateRange.end
                          } : { start, end: new Date() }
                        }))
                      }}
                    />
                  </label>
                  <label>
                    结束
                    <input
                      type="date"
                      value={formatDateInputValue(options.dateRange.end)}
                      onChange={(event) => {
                        const end = parseDateInput(event.target.value, true)
                        setOptions(prev => ({
                          ...prev,
                          dateRange: prev.dateRange ? {
                            start: prev.dateRange.start > end ? parseDateInput(event.target.value, false) : prev.dateRange.start,
                            end
                          } : { start: new Date(), end }
                        }))
                      }}
                    />
                  </label>
                </div>
              )}
            </div>

            <div className="dialog-section">
              <h4>媒体与头像</h4>
              <div className="switch-row">
                <span>导出媒体文件</span>
                <label className="switch">
                  <input
                    type="checkbox"
                    checked={options.exportMedia}
                    onChange={(event) => setOptions(prev => ({ ...prev, exportMedia: event.target.checked }))}
                  />
                  <span className="switch-slider"></span>
                </label>
              </div>

              <div className="media-check-grid">
                <label><input type="checkbox" checked={options.exportImages} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportImages: event.target.checked }))} /> 图片</label>
                <label><input type="checkbox" checked={options.exportVoices} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportVoices: event.target.checked }))} /> 语音</label>
                <label><input type="checkbox" checked={options.exportVideos} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportVideos: event.target.checked }))} /> 视频</label>
                <label><input type="checkbox" checked={options.exportEmojis} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportEmojis: event.target.checked }))} /> 表情包</label>
                <label><input type="checkbox" checked={options.exportVoiceAsText} onChange={event => setOptions(prev => ({ ...prev, exportVoiceAsText: event.target.checked }))} /> 语音转文字</label>
                <label><input type="checkbox" checked={options.exportAvatars} onChange={event => setOptions(prev => ({ ...prev, exportAvatars: event.target.checked }))} /> 导出头像</label>
              </div>
            </div>

            <div className="dialog-section">
              <h4>发送者名称显示</h4>
              <div className="display-name-options">
                {displayNameOptions.map(option => (
                  <label key={option.value} className={`display-name-item ${options.displayNamePreference === option.value ? 'active' : ''}`}>
                    <input
                      type="radio"
                      checked={options.displayNamePreference === option.value}
                      onChange={() => setOptions(prev => ({ ...prev, displayNamePreference: option.value }))}
                    />
                    <span>{option.label}</span>
                    <small>{option.desc}</small>
                  </label>
                ))}
              </div>
            </div>

            <div className="dialog-actions">
              <button className="secondary-btn" onClick={closeExportDialog}>取消</button>
              <button className="primary-btn" onClick={() => void createTask()} disabled={!exportFolder || exportDialog.sessionIds.length === 0}>
                <Download size={14} /> 创建导出任务
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

export default ExportPage
