// =============================================================================
// 企微IT智能服务台 — H5用户端会话状态管理（Pinia Store）
// =============================================================================
// 说明：管理用户端的核心状态，包括：
//   1. 用户信息（从 employee store 获取）
//   2. 当前会话信息
//   3. 消息列表（含轮询逻辑）
//   4. 招手/敲桌子状态
//   5. AI 助手面板展开/收起状态
// 注意：OAuth2 认证逻辑已迁移至 @/stores/employee.ts
// =============================================================================

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import {
  getUser,
  getCurrentConversation,
  sendMessage,
  pollMessages,
  shake,
  getApprovalLinks,
  getSoftwareDownloads,
  leaveAsParticipant as leaveAsParticipantApi,
  type UserInfo,
  type ConversationInfo,
  type Message,
  type MessageType,
  type SendMessageRequest,
  type SendMessageResponse,
  type MsgContentType,
  type ApprovalLink,
  type SoftwareDownload,
  type ParticipantItem,
} from '@/api/conversation'
import { useEmployeeStore } from '@/stores/employee'

// --------------------------------------------------------------------------
// Store 定义
// --------------------------------------------------------------------------
export const useConversationStore = defineStore('conversation', () => {

  // ==========================================================================
  // 响应式状态
  // ==========================================================================

  /** 当前用户信息（通过 /h5/user 接口获取，兼容旧逻辑） */
  const userInfo = ref<UserInfo | null>(null)

  /** 当前会话信息 */
  const currentConversation = ref<ConversationInfo | null>(null)

  /** 消息列表 */
  const messages = ref<Message[]>([])

  /** 是否正在加载（发送消息、摇人等操作时） */
  const loading = ref<boolean>(false)

  /** 招手是否正在进行中（防止重复点击） */
  const shaking = ref<boolean>(false)

  /** 是否可以呼叫人工坐席（AI 实质性回复 >= 3 次时显示按钮） */
  const canCallAgent = ref<boolean>(false)

  /** 坐席是否在线（通过 WebSocket 或轮询获取） */
  const agentOnline = ref<boolean>(true) // 默认在线，阶段一简化处理

  /** 轮询定时器 ID */
  const pollTimer = ref<ReturnType<typeof setInterval> | null>(null)

  /** AI 助手面板是否展开（移动端使用） */
  const assistantPanelVisible = ref<boolean>(false)

  /** 审批流程链接列表 */
  const approvalLinks = ref<ApprovalLink[]>([])

  /** 审批卡片弹窗是否显示（关键词触发） */
  const approvalCardVisible = ref<boolean>(false)

  /** 触发审批卡片的关键词文本 */
  const approvalCardTriggerText = ref<string>('')

  /** 软件下载列表 */
  const softwareDownloads = ref<SoftwareDownload[]>([])

  /** 最后一条消息的 ID（用于增量轮询） */
  const lastMessageId = ref<string>('')

  /** 是否已初始化（完成数据加载） */
  const initialized = ref<boolean>(false)

  /** 排查步骤列表（从坐席端同步，通过 WebSocket 推送） */
  const troubleshootingSteps = ref<Array<{ label: string; status: 'done' | 'current' | 'pending' }>>([])

  /** 排查步骤模板名称 */
  const troubleshootingTemplateName = ref<string>('')

  /** 排查流程图当前活跃节点（交互式 — 用户可点击选项） */
  const troubleshootingCurrentNode = ref<{
    id: string
    type: 'step' | 'decision'
    label: string
    status?: 'done' | 'current' | 'pending'
    children?: any[]
    yes_branch?: any
    no_branch?: any
  } | null>(null)

  /** 参与者列表（邀请功能 P0-09~P0-11，从会话信息同步） */
  const participants = ref<ParticipantItem[]>([])

  /** 参与者面板是否展开 */
  const participantPanelVisible = ref<boolean>(false)

  // ==========================================================================
  // 消息去重相关状态（与 agent 端一致，WS-06 修复）
  // ==========================================================================

  /**
   * 已处理消息ID集合（用于消息去重）
   * 做什么：记录最近处理过的 message_id，防止网络抖动或将来接入 WS 后
   *       收到重复消息导致前端重复显示
   * 为什么：轮询和将来的 WS 推送可能同时到达，需要幂等去重
   * 最多保留 500 条，超过时删除最旧的一条（FIFO）
   * ES2015+ 规范：Set 迭代顺序 = 插入顺序，delete 后重新 add 会移到最后
   */
  const processedMessageIds = ref<Set<string>>(new Set())

  // ==========================================================================
  // 计算属性
  // ==========================================================================

  /** 是否已登录（委托给 employee store） */
  const isLoggedIn = computed(() => {
    const employeeStore = useEmployeeStore()
    return employeeStore.isAuthenticated
  })

  /** 是否有活跃会话（会话未结单） */
  const hasActiveConversation = computed(() => {
    if (!currentConversation.value) return false
    return currentConversation.value.status !== 'closed'
  })

  /** 当前用户是否为被邀请的参与者（非原始员工） */
  const isParticipant = computed(() => {
    if (!currentConversation.value || !userInfo.value) return false
    // 如果当前用户 ID 等于会话的 employee_id，说明是原始员工，不是被邀请人
    if (currentConversation.value.employee_id === userInfo.value.employee_id) return false
    // 检查是否在 participants 列表中
    return participants.value.some(p => p.id === userInfo.value?.employee_id)
  })

  /** 已加入的参与者数量（用于横幅展示 "👥 N人参与"） */
  const joinedParticipantCount = computed(() => {
    // 原始员工 + 坐席 + 已加入的参与者
    const joinedParticipants = participants.value.filter(p => p.joined)
    return joinedParticipants.length
  })

  /** 审批链接按分类分组 */
  const approvalLinksByCategory = computed(() => {
    const grouped: Record<string, ApprovalLink[]> = {}
    for (const link of approvalLinks.value) {
      if (!grouped[link.category]) {
        grouped[link.category] = []
      }
      grouped[link.category].push(link)
    }
    return grouped
  })

  /** 软件下载按分类分组 */
  const softwareDownloadsByCategory = computed(() => {
    const grouped: Record<string, SoftwareDownload[]> = {}
    for (const item of softwareDownloads.value) {
      if (!grouped[item.category]) {
        grouped[item.category] = []
      }
      grouped[item.category].push(item)
    }
    return grouped
  })

  // ==========================================================================
  // 消息去重辅助函数（WS-06 修复，与 agent 端一致）
  // ==========================================================================

  /**
   * 记录已处理的消息ID（用于去重）
   *
   * 做什么：将 message_id 加入 processedMessageIds，
   *       如果已存在则先删除再重新添加（移到"最新"位置），
   *       超过 500 条时删除最旧的一条（FIFO）。
   * 为什么：防止网络抖动或将来 WS 重连导致重复处理同一条消息，
   *       使用 Set + 插入顺序（ES2015+ 规范）实现 FIFO 淘汰。
   *
   * @param messageId - 消息ID
   */
  function trackProcessedMessageId(messageId: string): void {
    const set = processedMessageIds.value
    // 如果已存在，先删除（ES2015+：重新 add 会移到插入顺序末尾）
    set.delete(messageId)
    set.add(messageId)
    // 超过 500 条时，删除最旧的一条（Set 迭代第一个 = 最早插入）
    if (set.size > 500) {
      const first = set.values().next().value as string
      if (first) set.delete(first)
    }
  }

  /**
   * 处理 WebSocket 推送的新消息事件（将来接入 WS 时使用，与 agent 端对齐）
   *
   * 做什么：
   * 1. 【WS-06去重】先检查 message_id 是否已处理过，已处理则跳过
   * 2. 将新消息追加到消息列表
   * 3. 更新最后消息ID
   *
   * 为什么需要：
   * - 将来接入 WebSocket 后，WS 推送比轮询更实时
   * - 需要消息去重，防止 WS 重连后后端重发导致重复显示
   *
   * @param data - WebSocket 推送的消息数据
   */
  function handleNewMessage(data: {
    conversation_id: string
    message_id: string
    sender_type: string
    sender_id: string
    sender_name?: string
    content: string
    msg_type?: string
  }): void {
    // WS-06 消息去重：检查 message_id 是否已处理过
    if (processedMessageIds.value.has(data.message_id)) {
      console.log(`[H5 WS去重] 跳过重复消息: ${data.message_id}`)
      return
    }

    // 记录此消息ID为"已处理"
    trackProcessedMessageId(data.message_id)

    // 追加消息到本地列表
    // 修复：message_type 应使用 sender_type（employee/agent/ai/system），
    // 而非 msg_type（text/image/file），否则 MessageBubble 无法识别消息类型
    messages.value.push({
      message_id: data.message_id,
      conversation_id: data.conversation_id,
      message_type: (data.sender_type || 'system') as MessageType,
      msg_type: (data.msg_type || 'text') as MsgContentType,
      content: data.content,
      sender_name: data.sender_name || '',
      created_at: new Date().toISOString(),
    })

    // 更新最后消息ID
    lastMessageId.value = data.message_id
  }

  // ==========================================================================
  // 操作方法
  // ==========================================================================

  /**
   * 处理 OAuth2 授权回调
   * 委托给 employee store 处理
   * @param code 企微 OAuth2 授权码
   * @param state 企微 OAuth2 state 参数（可选）
   * @deprecated 请使用 employeeStore.handleOAuthCallback() 替代
   */
  async function handleOAuthCallback(code: string, state?: string): Promise<void> {
    const employeeStore = useEmployeeStore()
    await employeeStore.handleOAuthCallback(code, state)
  }

  /**
   * 加载当前用户信息
   * 从后端获取当前登录员工的详细信息
   */
  async function fetchUserInfo(): Promise<void> {
    try {
      // 优先使用 employee store 的信息
      const employeeStore = useEmployeeStore()
      if (employeeStore.employeeInfo) {
        userInfo.value = {
          employee_id: employeeStore.employeeInfo.employee_id,
          employee_name: employeeStore.employeeInfo.employee_name,
          department: employeeStore.employeeInfo.department,
          position: employeeStore.employeeInfo.position,
          level: '',
          is_vip: employeeStore.employeeInfo.is_vip,
          avatar_url: employeeStore.employeeInfo.avatar,
        }
      }

      // 同时从 /h5/user 获取最新信息（包含 is_vip 等）
      const data = await getUser()
      userInfo.value = data
      console.log('[Store] 获取用户信息成功:', data.employee_name)
    } catch (error) {
      console.error('[Store] 获取用户信息失败:', error)
      // 开发模式：API 失败时使用 mock 数据，不阻塞初始化
      if (!import.meta.env.VITE_WECOM_CORP_ID) {
        console.warn('[Store] 开发模式：使用 mock 用户信息')
        const employeeStore = useEmployeeStore()
        userInfo.value = {
          employee_id: employeeStore.employeeId || 'dev_test_employee',
          employee_name: employeeStore.employeeName || '开发测试用户',
          department: 'IT部',
          position: '开发工程师',
          level: '',
          is_vip: false,
          avatar_url: '',
        }
        return
      }
      throw error
    }
  }

  /**
   * 加载当前会话
   * 获取当前员工正在进行的会话
   * 同步 participants 到 store（邀请功能 P0-09~P0-11）
   */
  async function fetchCurrentConversation(): Promise<void> {
    try {
      const data = await getCurrentConversation()
      currentConversation.value = data
      // 同步 can_call_agent 状态
      canCallAgent.value = data?.can_call_agent ?? false
      // 同步 participants（邀请功能）
      participants.value = data?.participants || []
      console.log('[Store] 获取当前会话:', data ? data.conversation_id : '无活跃会话', '参与者:', participants.value.length)
    } catch (error) {
      console.error('[Store] 获取当前会话失败:', error)
    }
  }

  /**
   * 发送消息（含 AI 自动回复）- 乐观更新 UI
   * 在当前会话中发送一条文本消息。
   * 后端会自动生成 AI 回复，返回用户消息 + AI 回复。
   *
   * 乐观更新流程：
   * 1. 发送前生成临时消息，立即添加到列表显示"发送中..."
   * 2. API 返回成功后用真实消息替换，状态改为"已发送"
   * 3. API 失败时状态改为"发送失败"，用户可点击重试
   *
   * @param content 消息内容
   * @param tempMessageId 临时消息ID（用于重试时定位）
   */
  async function sendNewMessage(
    content: string,
    options?: {
      msg_type?: MsgContentType
      media_url?: string
      file_name?: string
      file_size?: number
    }
  ): Promise<void> {
    console.log('[Store] sendNewMessage 开始执行, content:', content, 'options:', options)
    if (!content.trim()) {
      console.warn('[Store] content 为空，直接返回')
      return
    }

    // 检查是否包含审批关键词，如果包含则先弹出卡片
    const hasApprovalKeyword = checkApprovalKeywords(content)
    if (hasApprovalKeyword) {
      console.log('[Store] 检测到审批关键词，弹窗后仍发送消息')
      // 审批弹窗显示，但不阻止消息发送
    }

    // ========================================================================
    // 步骤1：乐观更新 - 立即添加临时消息到列表
    // ========================================================================
    const employeeStore = useEmployeeStore()
    const tempMessageId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
    const tempMessage: Message = {
      message_id: tempMessageId,
      conversation_id: currentConversation.value?.conversation_id || '',
      message_type: 'employee',
      msg_type: options?.msg_type || 'text',
      content: content.trim(),
      sender_name: employeeStore.employeeName || '我',
      created_at: new Date().toISOString(),
      status: 'sending', // 乐观更新：发送中状态
    }
    messages.value.push(tempMessage)
    console.log('[Store] 乐观更新：临时消息已添加到列表, tempMessageId:', tempMessageId)

    // ========================================================================
    // 步骤2：调用后端 API
    // ========================================================================
    loading.value = true
    try {
      // 构建请求参数：文本消息只传 content，非文本消息额外传 msg_type/media_url 等
      const reqData: SendMessageRequest = {
        content: content.trim(),
      }
      if (options?.msg_type) {
        reqData.msg_type = options.msg_type
        reqData.media_url = options.media_url
        reqData.file_name = options.file_name
        reqData.file_size = options.file_size
      }
      console.log('[Store] 请求参数:', JSON.stringify(reqData))

      const resp: SendMessageResponse = await sendMessage(reqData)
      console.log('[Store] API 响应:', resp)

      // 防御性检查：确保 resp 和必要字段存在
      if (!resp) {
        console.error('[Store] API 响应为空')
        throw new Error('API 响应为空')
      }
      if (!resp.user_message) {
        console.error('[Store] API 响应缺少 user_message')
        throw new Error('API 响应格式错误：缺少 user_message')
      }

      // ========================================================================
      // 步骤3：乐观更新成功 - 用真实消息替换临时消息
      // ========================================================================
      // 找到临时消息并替换为真实消息
      const tempIndex = messages.value.findIndex(m => m.message_id === tempMessageId)
      if (tempIndex !== -1) {
        // 用真实消息替换，状态改为"已发送"
        messages.value[tempIndex] = {
          ...resp.user_message,
          status: 'sent',
        }
        console.log('[Store] 乐观更新成功：临时消息已替换为真实消息')
      } else {
        // 防御性：找不到临时消息时直接添加
        messages.value.push({ ...resp.user_message, status: 'sent' })
      }

      // 将 AI 回复追加到本地列表（如果存在）
      if (resp.ai_reply) {
        messages.value.push(resp.ai_reply)
        lastMessageId.value = resp.ai_reply.message_id
      }

      // 更新「是否可呼叫坐席」标志
      canCallAgent.value = resp.can_call_agent ?? false

      // 如果会话信息中也有更新，保持同步
      if (currentConversation.value) {
        currentConversation.value.can_call_agent = resp.can_call_agent ?? false
        currentConversation.value.ai_substantive_reply_count = resp.ai_reply_count ?? 0
      }

      console.log(
        '[Store] 消息发送成功, AI回复计数:',
        resp.ai_reply_count,
        '可呼叫坐席:',
        resp.can_call_agent,
        '是否引导:',
        resp.is_guidance
      )
    } catch (error: any) {
      console.error('[Store] 消息发送失败:', error)
      console.error('[Store] 错误详情:', error?.message, error?.response?.data, error?.stack)

      // ========================================================================
      // 步骤4：乐观更新失败 - 将临时消息状态改为"发送失败"
      // ========================================================================
      const tempIndex = messages.value.findIndex(m => m.message_id === tempMessageId)
      if (tempIndex !== -1) {
        // 状态改为"发送失败"，用户可点击重试
        messages.value[tempIndex].status = 'failed'
        console.log('[Store] 乐观更新失败：临时消息状态已改为 failed')
      }

      // 向上抛出错误，让调用方（InputBar）能感知并提示用户
      throw error
    } finally {
      loading.value = false
      console.log('[Store] sendNewMessage finally 执行完成')
    }
  }

  /**
   * 轮询新消息
   * 增量获取当前会话中 lastMessageId 之后的新消息
   * 获取到新消息后追加到本地列表，并更新 lastMessageId
   */
  async function pollNewMessages(): Promise<void> {
    // 未登录或无活跃会话时不轮询
    if (!isLoggedIn.value || !hasActiveConversation.value) return

    try {
      const params = lastMessageId.value
        ? { after_message_id: lastMessageId.value }
        : undefined
      const newMessages = await pollMessages(params)

      if (newMessages && newMessages.length > 0) {
        // ======================================================================
        // WS-06 消息去重：过滤掉已处理过的消息
        // ======================================================================
        // 为什么：轮询和将来的 WS 推送可能同时到达同一条消息，
        //       需要先做 message_id 幂等检查，避免重复显示
        const uniqueNewMessages = newMessages.filter(msg => {
          // 检查是否已处理过
          if (processedMessageIds.value.has(msg.message_id)) {
            console.log(`[H5轮询去重] 跳过重复消息: ${msg.message_id}`)
            return false
          }
          // 记录此消息ID为"已处理"（更新到 Set 的最新位置）
          trackProcessedMessageId(msg.message_id)
          return true
        })

        if (uniqueNewMessages.length > 0) {
          // 追加新消息到本地列表
          messages.value.push(...uniqueNewMessages)
          // 更新最后消息 ID
          lastMessageId.value = uniqueNewMessages[uniqueNewMessages.length - 1].message_id
          console.log('[Store] 轮询到新消息:', uniqueNewMessages.length, '条')
        }
      }
    } catch (error) {
      // 轮询失败不弹提示，静默处理避免干扰用户
      console.error('[Store] 轮询消息失败:', error)
    }
  }

  /**
   * 启动消息轮询
   * 使用 setInterval 每 3 秒轮询一次新消息
   * 在组件挂载时调用，组件卸载时务必调用 stopPolling 停止
   */
  function startPolling(): void {
    // 防止重复启动
    if (pollTimer.value) return

    console.log('[Store] 启动消息轮询（3秒间隔）')
    pollTimer.value = setInterval(() => {
      pollNewMessages()
    }, 3000)
  }

  /**
   * 停止消息轮询
   * 在组件卸载或会话结束时调用
   */
  function stopPolling(): void {
    if (pollTimer.value) {
      console.log('[Store] 停止消息轮询')
      clearInterval(pollTimer.value)
      pollTimer.value = null
    }
  }

  /**
   * 招手/敲桌子 — 呼叫 IT 坐席
   * 调用后端招手接口，返回趣味话术
   * 将话术以系统消息形式插入对话列表
   */
  async function shakeAgent(): Promise<void> {
    // 防止重复点击
    if (shaking.value) return

    shaking.value = true
    try {
      // 从 employee store 获取当前员工信息
      const employeeStore = useEmployeeStore()
      const data = await shake({
        employee_id: employeeStore.employeeId,
        employee_name: employeeStore.employeeName,
      })
      console.log('[Store] 招手成功:', data)

      // 将趣味话术以系统消息形式插入对话列表
      const systemMsg: Message = {
        message_id: `sys_shake_${Date.now()}`,
        conversation_id: currentConversation.value?.conversation_id || '',
        message_type: 'system',
        content: data.funny_phrase,
        sender_name: '系统',
        created_at: new Date().toISOString(),
      }
      messages.value.push(systemMsg)

      // 如果招手后坐席已接入（status === 'serving'），刷新会话信息
      if (data.conversation?.status === 'serving') {
        await fetchCurrentConversation()
      }
    } catch (error) {
      console.error('[Store] 招手失败:', error)
    } finally {
      shaking.value = false
    }
  }

  /**
   * 加载审批流程链接
   * 从后端获取所有可用的审批流程链接
   */
  async function fetchApprovalLinks(): Promise<void> {
    try {
      const data = await getApprovalLinks()
      approvalLinks.value = data
      console.log('[Store] 获取审批链接成功:', data.length, '条')
    } catch (error) {
      console.error('[Store] 获取审批链接失败:', error)
    }
  }

  // 审批关键词列表（静态配置，后续可从API获取）
  const APPROVAL_KEYWORDS = ['申请', '资源', '设备', '电脑', '笔记本']

  /** 检查文本是否包含审批关键词，触发审批卡片弹窗 */
  function checkApprovalKeywords(text: string): boolean {
    const lowerText = text.toLowerCase()
    const hasKeyword = APPROVAL_KEYWORDS.some((kw) => lowerText.includes(kw))

    if (hasKeyword) {
      approvalCardTriggerText.value = text
      approvalCardVisible.value = true
      console.log('[Store] 检测到审批关键词，触发卡片弹窗')
    }

    return hasKeyword
  }

  /** 关闭审批卡片弹窗 */
  function closeApprovalCard(): void {
    approvalCardVisible.value = false
    approvalCardTriggerText.value = ''
  }

  /** 显示审批卡片弹窗（快捷按钮触发） */
  function showApprovalCard(triggerText: string = ''): void {
    approvalCardTriggerText.value = triggerText
    approvalCardVisible.value = true
  }

  /**
   * 加载软件下载列表
   * 从后端获取所有可下载的软件列表
   */
  async function fetchSoftwareDownloads(): Promise<void> {
    try {
      const data = await getSoftwareDownloads()
      softwareDownloads.value = data
      console.log('[Store] 获取软件下载列表成功:', data.length, '条')
    } catch (error) {
      console.error('[Store] 获取软件下载列表失败:', error)
    }
  }

  /**
   * 切换 AI 助手面板展开/收起（移动端使用）
   */
  function toggleAssistantPanel(): void {
    assistantPanelVisible.value = !assistantPanelVisible.value
  }

  /**
   * 切换到指定会话（邀请链接加入后使用）
   * 做什么：加入邀请会话后，将当前视图切换到该会话
   * 为什么：被邀请人可能已有自己的会话，需要切换到邀请的会话
   *
   * 修复（2026-06-12）：原实现仅调用 fetchCurrentConversation() 重新获取
   * "当前会话"，未使用 conversationId 参数。如果后端不会自动切换
   * current conversation，则拿到的仍是用户原来的会话。
   * 现改为：先刷新当前会话（加入后后端会更新 current conversation），
   * 再验证会话ID是否匹配，确保切换成功。
   *
   * @param conversationId - 目标会话ID
   */
  async function switchToConversation(conversationId: string): Promise<void> {
    try {
      // 重新获取当前会话（加入后后端会更新 current conversation）
      await fetchCurrentConversation()

      // 验证：当前会话是否已切换到目标会话
      const conv = currentConversation.value
      if (conv && conv.conversation_id !== conversationId) {
        console.warn(
          '[Store] 当前会话ID不匹配, 期望:', conversationId,
          '实际:', conv.conversation_id
        )
        // 后端未自动切换时，仍按当前获取到的会话展示
        // （后端 /h5/conversations/current 理论上应返回刚加入的会话）
      }

      // 清空消息列表，重新加载
      messages.value = []
      lastMessageId.value = ''
      // 立即拉取一次消息，避免等3秒轮询
      await pollNewMessages()
      console.log('[Store] 已切换到邀请会话:', conversationId)
    } catch (error) {
      console.error('[Store] 切换会话失败:', error)
    }
  }

  /**
   * 参与者主动退出会话（邀请功能 P0-11）
   * 做什么：被邀请人退出当前会话
   * 为什么：参与者不再需要参与时，可自行退出
   * 副作用：退出后清空当前会话状态
   */
  async function leaveAsParticipant(): Promise<void> {
    if (!currentConversation.value) return

    const convId = currentConversation.value.conversation_id

    try {
      // H5 专用端点通过 Token 认证获取 employee_id，无需前端传递
      await leaveAsParticipantApi(convId)
      console.log('[Store] 已退出会话:', convId)
      // 退出后清空当前会话状态
      currentConversation.value = null
      participants.value = []
      messages.value = []
      lastMessageId.value = ''
      // 停止轮询
      stopPolling()
    } catch (error) {
      console.error('[Store] 退出会话失败:', error)
      throw error
    }
  }

  /**
   * 切换参与者面板展开/收起
   */
  function toggleParticipantPanel(): void {
    participantPanelVisible.value = !participantPanelVisible.value
  }

  /**
   * 重试发送失败的消息
   * 做什么：当用户点击"发送失败"消息的重试按钮时，删除失败消息并重新发送
   * 为什么：乐观更新失败时允许用户手动重试发送
   *
   * @param messageId 失败消息的 ID
   */
  async function retryMessage(messageId: string): Promise<void> {
    // 找到失败消息
    const msgIndex = messages.value.findIndex(m => m.message_id === messageId)
    if (msgIndex === -1) {
      console.warn('[Store] 重试消息找不到:', messageId)
      return
    }

    const failedMessage = messages.value[msgIndex]
    if (failedMessage.status !== 'failed') {
      console.warn('[Store] 消息状态不是 failed，无法重试:', messageId)
      return
    }

    const content = failedMessage.content
    // 删除失败消息
    messages.value.splice(msgIndex, 1)
    console.log('[Store] 删除失败消息:', messageId)

    // 重新发送
    await sendNewMessage(content, {
      msg_type: failedMessage.msg_type,
      media_url: failedMessage.media_url,
      file_name: failedMessage.file_name,
      file_size: failedMessage.file_size,
    })
  }

  // ==========================================================================
  // WebSocket 事件处理方法（H5 员工端）
  // ==========================================================================

  /**
   * 通过 WS 推送直接更新参与者列表
   * 做什么：用后端 WS 推送的 participants 数据直接替换 store 中的列表
   * 为什么：比等3秒轮询更实时，参与者变更（加入/退出/被移除）立即可见
   *
   * @param newParticipants - 后端推送的最新参与者列表
   */
  function updateParticipants(newParticipants: ParticipantItem[]): void {
    participants.value = newParticipants
    // 同步到 currentConversation（保持一致性）
    if (currentConversation.value) {
      currentConversation.value.participants = newParticipants
    }
    console.log('[Store] 参与者列表已通过WS更新:', newParticipants.length, '人')
  }

  /**
   * 当前用户被主责坐席从会话中移除
   * 做什么：清空当前会话状态，回到无会话状态
   * 为什么：被移除后不应再查看会话消息
   * 触发条件：WS 收到 participant_removed 事件，且 changed 中包含当前用户
   */
  function handleRemovedFromConversation(): void {
    console.log('[Store] 当前用户被移除会话，清空状态')
    currentConversation.value = null
    participants.value = []
    messages.value = []
    lastMessageId.value = ''
    participantPanelVisible.value = false
    // 停止轮询（WS 会继续监听，下次有新会话时会自动更新）
    stopPolling()
  }

  /**
   * 初始化应用
   * 1. 获取用户信息
   * 2. 获取当前会话
   * 3. 加载审批链接和软件下载
   * 4. 启动消息轮询
   */
  async function initialize(): Promise<void> {
    if (initialized.value) return

    try {
      console.log('[Store] 开始初始化应用...')
      // 获取用户信息
      await fetchUserInfo()
      // 获取当前会话
      await fetchCurrentConversation()
      // 加载右侧面板数据
      await Promise.all([
        fetchApprovalLinks(),
        fetchSoftwareDownloads(),
      ])
      // 启动消息轮询
      startPolling()
      initialized.value = true
      console.log('[Store] 应用初始化完成')
    } catch (error) {
      console.error('[Store] 应用初始化失败:', error)
    }
  }

  /**
   * 清理状态
   * 在组件卸载时调用，停止轮询，清理定时器
   */
  function cleanup(): void {
    stopPolling()
  }

  // ==========================================================================
  // 返回所有状态和方法
  // ==========================================================================
  return {
    // 状态
    userInfo,
    currentConversation,
    messages,
    loading,
    shaking,
    canCallAgent,
    agentOnline,
    assistantPanelVisible,
    approvalLinks,
    approvalCardVisible,
    approvalCardTriggerText,
    softwareDownloads,
    lastMessageId,
    initialized,
    troubleshootingSteps,
    troubleshootingTemplateName,
    troubleshootingCurrentNode,
    participants,
    participantPanelVisible,

    // 计算属性
    isLoggedIn,
    hasActiveConversation,
    isParticipant,
    joinedParticipantCount,
    approvalLinksByCategory,
    softwareDownloadsByCategory,

    // 方法
    handleOAuthCallback,
    fetchUserInfo,
    fetchCurrentConversation,
    sendNewMessage,
    pollNewMessages,
    startPolling,
    stopPolling,
    shakeAgent,
    fetchApprovalLinks,
    checkApprovalKeywords,
    closeApprovalCard,
    showApprovalCard,
    fetchSoftwareDownloads,
    toggleAssistantPanel,
    switchToConversation,
    leaveAsParticipant,
    toggleParticipantPanel,
    retryMessage,
    updateParticipants,
    handleRemovedFromConversation,
    initialize,
    cleanup,

    // WS-06 消息去重（与 agent 端对齐，WebSocket 接入时使用）
    handleNewMessage,
  }
})
