// =============================================================================
// 企微IT智能服务台 — 会话状态管理（Pinia Store）
// =============================================================================
// 说明：管理坐席工作台的会话列表、当前选中会话、消息列表等状态
// 核心功能：
//   1. 会话列表数据 + 轮询逻辑（每3秒刷新会话列表）
//   2. 当前选中会话
//   3. 消息列表 + 轮询新消息
//   4. 标记操作方法（置顶/代办/结单/接单/转接）
// =============================================================================

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Conversation } from '@/api/conversation'
import type { Message } from '@/api/message'
import {
  getConversations,
  assignConversation,
  resolveConversation,
  togglePin,
  toggleTodo,
  transferConversation,
  grabConversation,
  inviteCollaborator,
  leaveCollaboration,
  inviteParticipant,
  removeParticipant as removeParticipantApi,
  leaveAsParticipant as leaveAsParticipantApi,
} from '@/api/conversation'
import type { InviteParticipantParams } from '@/api/conversation'
import { getMessages, sendMessage, pollMessages } from '@/api/message'
import {
  generateDraft,
  generateSummary,
  suggestTags,
} from '@/api/wingman'
import type { DraftResult, SummaryResult } from '@/api/wingman'
import { mockConversationListData, mockMessageListData } from '@/mock/data'

// --------------------------------------------------------------------------
// 会话排序权重配置
// --------------------------------------------------------------------------
// 排序规则（来自PRD）：紧急→招手→需介入→活跃→AI处理中→已结单
// 权重越小排越前面
const STATUS_WEIGHT: Record<string, number> = {
  queued: 1,       // 排队等待（招手等待区）— 最优先
  serving: 2,      // 人工处理中
  ai_handling: 3,  // AI处理中
  resolved: 4,     // 已结单 — 最后
}

/**
 * 计算会话的排序权重
 * 综合考虑：置顶 > 紧急度 > 标签(招手/需介入/情绪急) > 状态 > 时间
 *
 * @param conv - 会话对象
 * @returns 排序权重（越小越靠前）
 */
function calcSortWeight(conv: Conversation): number {
  let weight = 0

  // 置顶的会话最优先（权重 -10000）
  if (conv.is_pinned) {
    weight -= 10000
  }

  // 代办的会话也靠前（权重 -5000）
  if (conv.is_todo) {
    weight -= 5000
  }

  // 招手标记（权重 -2000）
  if (conv.tags?.hand_raise) {
    weight -= 2000
  }

  // 需介入标记（权重 -1500）
  if (conv.tags?.need_intervene) {
    weight -= 1500
  }

  // 情绪紧急/愤怒（权重 -1000）
  if (conv.tags?.emotion === 'angry' || conv.tags?.emotion === 'urgent') {
    weight -= 1000
  }

  // VIP标记（权重 -800）
  if (conv.is_vip) {
    weight -= 800
  }

  // 紧急度（1-5，越紧急权重越低）
  // urgency_score 5 → weight -500, urgency_score 1 → weight -100
  weight -= (conv.urgency_score || 1) * 100

  // 状态权重
  weight += (STATUS_WEIGHT[conv.status] || 3) * 10000

  return weight
}

// --------------------------------------------------------------------------
// Store 定义
// --------------------------------------------------------------------------
export const useConversationStore = defineStore('conversation', () => {
  // ==========================================================================
  // 响应式状态
  // ==========================================================================

  /** 会话列表（原始数据） */
  const conversations = ref<Conversation[]>([])

  /** 当前选中的会话ID */
  const currentConversationId = ref<string | null>(null)

  /** 当前会话的消息列表 */
  const messages = ref<Message[]>([])

  /** 是否正在加载会话列表 */
  const loadingConversations = ref<boolean>(false)

  /** 是否正在加载消息 */
  const loadingMessages = ref<boolean>(false)

  /** 待填充到输入框的文本（由快速回复模板设置） */
  const pendingReplyText = ref<string>('')

  /** 工作区视图模式：'chat' 为对话模式，'task' 为任务模式 */
  const workspaceView = ref<'chat' | 'task'>('chat')

  // ==========================================================================
  // 输入指示器（Typing Indicator）相关状态
  // ==========================================================================

  /**
   * 正在输入的用户映射：conversationId → { sender_id, sender_name, timestamp }
   * 用于在聊天区域显示"xxx正在输入..."提示
   * 自动过期：5秒内没有新的 typing 事件则自动清除
   */
  const typingUsers = ref<Map<string, { sender_id: string; sender_name: string; timestamp: number }>>(new Map())

  // ==========================================================================
  // 消息去重相关状态（WS-06 修复）
  // ==========================================================================

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

  // ==========================================================================
  // AI Wingman 相关状态
  // ==========================================================================

  /** AI 草稿数据：conversationId → messageId → DraftResult */
  const aiDrafts = ref<Map<string, Map<string, DraftResult>>>(new Map())

  /** 当前会话的摘要数据 */
  const currentSummary = ref<SummaryResult | null>(null)

  /** 当前会话的建议标签 */
  const suggestedTags = ref<string[]>([])

  /** 当前建议的分类 */
  const suggestedCategory = ref<string>('')

  /** 当前建议的优先级 */
  const suggestedPriority = ref<string>('medium')

  /** 是否正在加载草稿 */
  const loadingDraft = ref<boolean>(false)

  /** 是否正在加载摘要 */
  const loadingSummary = ref<boolean>(false)

  /** 是否正在加载标签建议 */
  const loadingTags = ref<boolean>(false)

  /** 会话列表轮询定时器ID */
  let conversationPollTimer: ReturnType<typeof setInterval> | null = null

  /** 消息轮询定时器ID */
  let messagePollTimer: ReturnType<typeof setInterval> | null = null

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

  /**
   * 排序后的会话列表
   * 排序规则：置顶→招手→需介入→情绪急→VIP→紧急度高→状态→时间
   */
  const sortedConversations = computed(() => {
    return [...conversations.value].sort((a, b) => {
      const weightA = calcSortWeight(a)
      const weightB = calcSortWeight(b)
      if (weightA !== weightB) return weightA - weightB
      // 权重相同时按最后消息时间倒序
      const timeA = a.last_message_at ? new Date(a.last_message_at).getTime() : 0
      const timeB = b.last_message_at ? new Date(b.last_message_at).getTime() : 0
      return timeB - timeA
    })
  })

  /**
   * 招手等待区会话列表
   * 状态为 queued 的会话（等待坐席接入）
   */
  const handRaiseConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'queued')
  })

  /**
   * 我的会话列表
   * 状态为 serving 且 is_mine 为 true 的会话
   */
  const myServingConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'serving' && c.is_mine)
  })

  /**
   * 其他坐席会话列表
   * 状态为 serving 且 is_mine 为 false 的会话（可查看 + 接手）
   */
  const otherAgentConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'serving' && !c.is_mine)
  })

  /**
   * 人工处理区会话列表（兼容旧逻辑：所有 serving 会话）
   */
  const servingConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'serving')
  })

  /**
   * AI处理区会话列表
   * 状态为 ai_handling 的会话（AI自动回复中）
   */
  const aiHandlingConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'ai_handling')
  })

  /**
   * 已结单区会话列表
   * 状态为 resolved 的会话
   */
  const resolvedConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'resolved')
  })

  /**
   * 协作会话列表（我是协作者但不是主责的会话）
   * is_collaborator=true 且 status=serving 的会话
   * 这些会话显示在「协作会话」分区，排在「我的会话」之后
   */
  const collaboratingConversations = computed(() => {
    return sortedConversations.value.filter(c => c.is_collaborator && c.status === 'serving')
  })

  /**
   * 📌 我的会话 — 合并：待接单 + 我的会话 + 协作会话
   * 对应左栏三段折叠的第一段（默认展开）
   */
  const myConversations = computed(() => {
    return sortedConversations.value.filter(c => {
      // 待接单
      if (c.status === 'queued') return true
      // 我的会话（状态 serving 且 is_mine）
      if (c.status === 'serving' && c.is_mine) return true
      // 协作会话
      if (c.is_collaborator && c.status === 'serving') return true
      return false
    })
  })

  /**
   * 👥 同事会话 — 合并：其他坐席会话 + AI处理区
   * 对应左栏三段折叠的第二段（默认折叠）
   */
  const colleagueConversations = computed(() => {
    return sortedConversations.value.filter(c => {
      // 其他坐席会话（serving 但非我的）
      if (c.status === 'serving' && !c.is_mine && !c.is_collaborator) return true
      // AI处理区
      if (c.status === 'ai_handling') return true
      return false
    })
  })

  /**
   * 🕙 历史会话 — 已结单
   * 对应左栏三段折叠的第三段（默认折叠）
   */
  const historyConversations = computed(() => {
    return sortedConversations.value.filter(c => c.status === 'resolved')
  })

  /**
   * 当前选中的会话对象
   */
  const currentConversation = computed(() => {
    if (!currentConversationId.value) return null
    return conversations.value.find(c => c.id === currentConversationId.value) || null
  })

  // ==========================================================================
  // 方法
  // ==========================================================================

  /**
   * 加载会话列表
   * 调用后端 API 获取所有会话数据
   */
  async function fetchConversations(): Promise<void> {
    try {
      loadingConversations.value = true
      const data = await getConversations({ page: 1, page_size: 100 })
      conversations.value = data.items
    } catch (error) {
      console.error('获取会话列表失败:', error)
      // 使用 mock 数据作为 fallback（开发/演示用）
      if (import.meta.env.DEV) {
        console.warn('[Mock] 使用模拟会话数据')
        conversations.value = mockConversationListData.items
      }
    } finally {
      loadingConversations.value = false
    }
  }

  /**
   * 选中某个会话
   * 切换当前会话，并加载该会话的消息
   *
   * @param conversationId - 要选中的会话ID
   */
  async function selectConversation(conversationId: string): Promise<void> {
    currentConversationId.value = conversationId
    // 清空上一个会话的 Wingman 数据
    clearWingmanData()
    // 加载该会话的消息
    await fetchMessages(conversationId)
  }

  /**
   * 加载指定会话的消息列表
   *
   * @param conversationId - 会话ID
   */
  async function fetchMessages(conversationId: string): Promise<void> {
    try {
      loadingMessages.value = true
      const data = await getMessages(conversationId, { limit: 50 })
      messages.value = data.items
    } catch (error) {
      console.error('获取消息列表失败:', error)
      // 使用 mock 数据作为 fallback（开发/演示用）
      if (import.meta.env.DEV) {
        console.warn('[Mock] 使用模拟消息数据')
        messages.value = mockMessageListData.items
      }
    } finally {
      loadingMessages.value = false
    }
  }

  /**
   * 发送消息
   * 发送后将新消息追加到消息列表
   *
   * @param content - 消息内容
   * @param replyToId - 引用回复的消息ID（可选）
   */
  async function sendReply(content: string, replyToId?: string): Promise<void> {
    if (!currentConversationId.value) return
    try {
      const options: Record<string, string> = {}
      if (replyToId) {
        options.reply_to_id = replyToId
      }
      const newMessage = await sendMessage(currentConversationId.value, content, 'text', options)
      // 追加到消息列表
      messages.value.push(newMessage)
      // 刷新会话列表（更新最后消息摘要）
      await fetchConversations()
    } catch (error) {
      console.error('发送消息失败:', error)
    }
  }

  /**
   * 坐席接单
   * 将会话状态从 queued 改为 serving
   *
   * @param conversationId - 会话ID
   * @param agentId - 坐席ID
   */
  async function assignConv(conversationId: string, agentId: string): Promise<void> {
    try {
      await assignConversation(conversationId, agentId)
      await fetchConversations()
    } catch (error) {
      console.error('接单失败:', error)
    }
  }

  /**
   * 结单
   * 将会话状态改为 resolved
   *
   * @param conversationId - 会话ID
   */
  async function resolveConv(conversationId: string): Promise<void> {
    try {
      await resolveConversation(conversationId)
      await fetchConversations()
      // 如果结单的是当前会话，清空选中
      if (currentConversationId.value === conversationId) {
        currentConversationId.value = null
        messages.value = []
      }
    } catch (error) {
      console.error('结单失败:', error)
    }
  }

  /**
   * 切换置顶状态
   *
   * @param conversationId - 会话ID
   */
  async function togglePinConv(conversationId: string): Promise<void> {
    try {
      await togglePin(conversationId)
      await fetchConversations()
    } catch (error) {
      console.error('切换置顶失败:', error)
    }
  }

  /**
   * 切换代办状态
   *
   * @param conversationId - 会话ID
   */
  async function toggleTodoConv(conversationId: string): Promise<void> {
    try {
      await toggleTodo(conversationId)
      await fetchConversations()
    } catch (error) {
      console.error('切换代办失败:', error)
    }
  }

  /**
   * 转接会话
   *
   * @param conversationId - 会话ID
   * @param targetAgentId - 目标坐席ID
   */
  async function transferConv(conversationId: string, targetAgentId: string): Promise<void> {
    try {
      await transferConversation(conversationId, targetAgentId)
      await fetchConversations()
    } catch (error) {
      console.error('转接失败:', error)
    }
  }

  /**
   * 接手其他坐席的会话（抢单）
   * 接手后原坐席自动释放，会话变为当前坐席的
   *
   * @param conversationId - 会话ID
   */
  async function grabConv(conversationId: string): Promise<void> {
    try {
      await grabConversation(conversationId)
      await fetchConversations()
    } catch (error) {
      console.error('接手失败:', error)
      throw error
    }
  }

  /**
   * 摇人 — 邀请坐席加入协作
   *
   * @param conversationId - 会话ID
   * @param agentId - 被邀请的坐席ID
   */
  async function inviteToConversation(conversationId: string, agentId: string): Promise<void> {
    try {
      await inviteCollaborator(conversationId, agentId)
      await fetchConversations()
    } catch (error) {
      console.error('邀请协作失败:', error)
      throw error
    }
  }

  /**
   * 退出协作
   *
   * @param conversationId - 会话ID
   */
  async function leaveConvCollaboration(conversationId: string): Promise<void> {
    try {
      await leaveCollaboration(conversationId)
      await fetchConversations()
      // 如果退出的会话正好是当前查看的会话，清空选中
      if (currentConversationId.value === conversationId) {
        currentConversationId.value = null
        messages.value = []
      }
    } catch (error) {
      console.error('退出协作失败:', error)
      throw error
    }
  }

  // ==========================================================================
  // 邀请功能方法（P0-09~P0-11）
  // ==========================================================================

  /**
   * 邀请员工/部门加入会话
   *
   * 做什么：调用邀请API，成功后刷新会话列表
   * 为什么：邀请成功后 participants 列表会更新，需要前端同步
   *
   * @param conversationId - 会话ID
   * @param params - 邀请参数（含被邀请人列表和历史共享模式）
   */
  async function inviteParticipants(
    conversationId: string,
    params: InviteParticipantParams
  ): Promise<void> {
    try {
      await inviteParticipant(conversationId, params)
      await fetchConversations()
    } catch (error) {
      console.error('邀请参与者失败:', error)
      throw error
    }
  }

  /**
   * 移除参与者（仅主责坐席可操作）
   *
   * 做什么：调用移除API，成功后刷新会话列表
   * 为什么：参与者被移除后列表需更新
   *
   * @param conversationId - 会话ID
   * @param userId - 被移除的员工UserID
   */
  async function removeParticipantFromConv(
    conversationId: string,
    userId: string
  ): Promise<void> {
    try {
      await removeParticipantApi(conversationId, userId)
      await fetchConversations()
    } catch (error) {
      console.error('移除参与者失败:', error)
      throw error
    }
  }

  /**
   * 参与者主动退出会话
   *
   * 做什么：调用退出API，成功后刷新会话列表
   * 为什么：参与者退出后当前会话可能需要更新
   *
   * @param conversationId - 会话ID
   * @param employeeId - 退出的员工UserID
   */
  async function leaveAsParticipantConv(
    conversationId: string,
    employeeId: string
  ): Promise<void> {
    try {
      await leaveAsParticipantApi(conversationId, employeeId)
      await fetchConversations()
    } catch (error) {
      console.error('参与者退出失败:', error)
      throw error
    }
  }

  /**
   * 处理 WebSocket 推送的参与者邀请事件
   * 做什么：刷新会话列表以反映新的参与者
   */
  function handleParticipantInvited(): void {
    fetchConversations()
  }

  /**
   * 处理 WebSocket 推送的参与者加入/退出/移除事件
   * 做什么：刷新会话列表以反映参与者状态变更
   */
  function handleParticipantChanged(): void {
    fetchConversations()
  }

  // ==========================================================================
  // 轮询逻辑
  // ==========================================================================

  /**
   * 启动会话列表轮询
   * 每3秒调用一次 GET /api/conversations 刷新会话列表
   */
  function startConversationPoll(): void {
    // 先立即加载一次
    fetchConversations()
    // 每3秒轮询
    if (conversationPollTimer) clearInterval(conversationPollTimer)
    conversationPollTimer = setInterval(() => {
      fetchConversations()
    }, 3000)
  }

  /**
   * 停止会话列表轮询
   */
  function stopConversationPoll(): void {
    if (conversationPollTimer) {
      clearInterval(conversationPollTimer)
      conversationPollTimer = null
    }
  }

  /**
   * 启动消息轮询
   * 每3秒调用一次 pollMessages 刷新当前会话的新消息
   */
  function startMessagePoll(): void {
    if (messagePollTimer) clearInterval(messagePollTimer)
    messagePollTimer = setInterval(async () => {
      if (!currentConversationId.value) return
      try {
        // 获取最后一条消息的ID作为 after_message_id
        const lastMessageId = messages.value.length > 0
          ? messages.value[messages.value.length - 1].id
          : undefined
        const data = await pollMessages(currentConversationId.value, lastMessageId)
        // 将新消息追加到列表
        if (data.items && data.items.length > 0) {
          messages.value.push(...data.items)
        }
      } catch (error) {
        console.error('轮询消息失败:', error)
      }
    }, 3000)
  }

  /**
   * 停止消息轮询
   */
  function stopMessagePoll(): void {
    if (messagePollTimer) {
      clearInterval(messagePollTimer)
      messagePollTimer = null
    }
  }

  /**
   * 启动所有轮询（会话列表 + 消息）
   */
  function startAllPolling(): void {
    startConversationPoll()
    startMessagePoll()
  }

  /**
   * 停止所有轮询
   */
  function stopAllPolling(): void {
    stopConversationPoll()
    stopMessagePoll()
  }

  // ==========================================================================
  // AI Wingman 方法
  // ==========================================================================

  /**
   * 获取指定消息的 AI 草稿
   * 如果已有缓存则直接返回，否则调用 API 生成
   *
   * @param conversationId - 会话ID
   * @param messageId - 消息ID
   */
  async function fetchDraftForMessage(conversationId: string, messageId: string): Promise<void> {
    // 检查缓存
    const convDrafts = aiDrafts.value.get(conversationId)
    if (convDrafts?.has(messageId)) return

    try {
      loadingDraft.value = true
      const result = await generateDraft(conversationId)

      // 存入缓存
      if (!aiDrafts.value.has(conversationId)) {
        aiDrafts.value.set(conversationId, new Map())
      }
      aiDrafts.value.get(conversationId)!.set(messageId, result)
    } catch (error) {
      console.error('获取 AI 草稿失败:', error)
    } finally {
      loadingDraft.value = false
    }
  }

  /**
   * 获取会话摘要
   *
   * @param conversationId - 会话ID
   */
  async function fetchSummary(conversationId: string): Promise<void> {
    try {
      loadingSummary.value = true
      const result = await generateSummary(conversationId)
      currentSummary.value = result
    } catch (error) {
      console.error('获取会话摘要失败:', error)
    } finally {
      loadingSummary.value = false
    }
  }

  /**
   * 获取标签建议
   *
   * @param conversationId - 会话ID
   */
  async function fetchSuggestedTags(conversationId: string): Promise<void> {
    try {
      loadingTags.value = true
      const result = await suggestTags(conversationId)
      suggestedTags.value = result.suggested_tags
      suggestedCategory.value = result.category
      suggestedPriority.value = result.priority
    } catch (error) {
      console.error('获取标签建议失败:', error)
    } finally {
      loadingTags.value = false
    }
  }

  /**
   * 采纳 AI 草稿 — 将草稿内容填入回复输入框
   *
   * @param conversationId - 会话ID
   * @param messageId - 消息ID
   */
  function acceptDraft(conversationId: string, messageId: string): void {
    const convDrafts = aiDrafts.value.get(conversationId)
    if (!convDrafts) return
    const draft = convDrafts.get(messageId)
    if (!draft) return

    // 将草稿内容填入输入框
    pendingReplyText.value = draft.content
  }

  /**
   * 编辑 AI 草稿 — 将草稿内容填入回复输入框并聚焦
   *
   * @param conversationId - 会话ID
   * @param messageId - 消息ID
   */
  function editDraft(conversationId: string, messageId: string): void {
    const convDrafts = aiDrafts.value.get(conversationId)
    if (!convDrafts) return
    const draft = convDrafts.get(messageId)
    if (!draft) return

    // 将草稿内容填入输入框
    pendingReplyText.value = draft.content
  }

  /**
   * 忽略 AI 草稿 — 移除草稿气泡
   *
   * @param conversationId - 会话ID
   * @param messageId - 消息ID
   */
  function ignoreDraft(conversationId: string, messageId: string): void {
    const convDrafts = aiDrafts.value.get(conversationId)
    if (!convDrafts) return
    convDrafts.delete(messageId)
  }

  /**
   * 获取指定会话指定消息的草稿数据
   *
   * @param conversationId - 会话ID
   * @param messageId - 消息ID
   * @returns 草稿数据，不存在时返回 null
   */
  function getDraft(conversationId: string, messageId: string): DraftResult | null {
    const convDrafts = aiDrafts.value.get(conversationId)
    if (!convDrafts) return null
    return convDrafts.get(messageId) || null
  }

  /**
   * 清空当前会话的 Wingman 数据
   * 在切换会话时调用
   */
  function clearWingmanData(): void {
    currentSummary.value = null
    suggestedTags.value = []
    suggestedCategory.value = ''
    suggestedPriority.value = ''
  }

  // ==========================================================================
  // WebSocket 事件处理方法
  // ==========================================================================
  // 这些方法由 useWebSocket 组合式函数调用，
  // 处理后端通过 WebSocket 推送的实时事件。
  // 为什么需要这些方法：
  // - WS 推送比 3 秒轮询更实时，坐席能第一时间看到新消息
  // - 保留轮询作为 fallback，WS 断连时自动降级为轮询
  // ==========================================================================

  // --------------------------------------------------------------------------
  // WS-06 消息去重辅助函数
  // --------------------------------------------------------------------------

  /**
   * 记录已处理的消息ID（用于去重）
   *
   * 做什么：将 message_id 加入 processedMessageIds，
   *       如果已存在则先删除再重新添加（移到"最新"位置），
   *       超过 500 条时删除最旧的一条（FIFO）。
   * 为什么：防止 WS 重连或网络抖动导致重复处理同一条消息，
   *       使用 Set + 插入顺序（ES2015+ 规范）实现 FIFO 淘汰。
   * 注意：Vue ref 不深响应 Set 的 mutation，但这不影响去重功能，
   *       因为 processedMessageIds 仅作为存储使用，不在模板中渲染。
   *
   * @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
      set.delete(first)
    }
  }

  // --------------------------------------------------------------------------
  // WebSocket 消息处理函数
  // --------------------------------------------------------------------------

  /**
   * 处理 WebSocket 推送的新消息事件
   *
   * 做什么：
   * 1. 【WS-06去重】先检查 message_id 是否已处理过，已处理则跳过
   * 2. 如果当前正在查看该会话，将新消息追加到消息列表（避免重复）
   * 3. 刷新会话列表（因为 urgency/tags 可能已更新）
   *
   * 为什么避免重复：
   * - WS 推送和轮询可能同时获取到同一条消息
   * - WS 重连后后端可能重发滑动窗口内的消息
   * - 需要先做 message_id 幂等检查，再做 messages 列表检查
   *
   * @param data - WebSocket 推送的消息数据
   * @param data.conversation_id - 会话ID
   * @param data.message_id - 消息ID
   * @param data.sender_type - 发送者类型
   * @param data.sender_id - 发送者ID
   * @param data.content - 消息内容
   * @param data.urgency_score - 紧急度评分
   * @param data.tags - 标签集合
   */
  function handleNewMessage(data: {
    conversation_id: string
    message_id: string
    sender_type: string
    sender_id: string
    content: string
    urgency_score?: number
    tags?: any
  }): void {
    // ======================================================================
    // WS-06 消息去重：检查 message_id 是否已处理过
    // ======================================================================
    // 为什么：网络抖动导致 WS 重连后，后端可能重发已推送的消息
    // 如果已处理过（在 processedMessageIds 集合中），直接跳过
    if (processedMessageIds.value.has(data.message_id)) {
      console.log(`[WS去重] 跳过重复消息: ${data.message_id}`)
      return
    }

    // 记录此消息ID为"已处理"（更新到 Set 的最新位置）
    trackProcessedMessageId(data.message_id)

    // 如果当前正在查看这个会话，追加消息到消息列表
    if (currentConversationId.value === data.conversation_id) {
      // 检查消息是否已存在（避免轮询和 WS 推送重复）
      const exists = messages.value.some(m => m.id === data.message_id)
      if (!exists) {
        messages.value.push({
          id: data.message_id,
          conversation_id: data.conversation_id,
          sender_type: data.sender_type,
          sender_id: data.sender_id,
          sender_name: '',  // WS 推送不含姓名，后续轮询会补充
          content: data.content,
          msg_type: 'text',
          ai_suggestion: false,
          is_read: false,
          created_at: new Date().toISOString(),
        })
      }
    }

    // 无论如何刷新会话列表
    // 原因：新消息可能导致紧急度/标签变化，坐席需要看到最新的排序
    fetchConversations()
  }

  /**
   * 处理 WebSocket 推送的会话状态变更事件
   *
   * 做什么：刷新会话列表
   * 为什么：会话状态变更（接单/结单/转接）需要实时反映在列表中
   *
   * @param data - WebSocket 推送的会话变更数据
   * @param data.conversation_id - 会话ID
   * @param data.status - 新状态
   * @param data.assigned_agent_id - 分配的坐席ID
   */
  function handleConversationUpdated(_data: {
    conversation_id: string
    status: string
    assigned_agent_id: string | null
  }): void {
    // 刷新会话列表
    fetchConversations()
  }

  /**
   * 处理 WebSocket 推送的摇人邀请事件（定向推送）
   *
   * 做什么：弹出通知，提示被邀请坐席
   * 为什么：WS 定向推送比轮询更实时，被邀请人第一时间知道被摇了
   *
   * @param data - 邀请事件数据
   * @param data.conversation_id - 会话ID
   * @param data.inviter_agent_id - 邀请人坐席ID
   * @param data.invitee_agent_id - 被邀请坐席ID
   * @param data.employee_name - 员工姓名
   * @param data.last_message_summary - 最后消息摘要
   */
  function handleCollaboratorInvited(_data: {
    conversation_id: string
    inviter_agent_id: string
    invitee_agent_id: string
    employee_name: string
    last_message_summary: string
  }): void {
    // 被邀请人自动收到通知
    // 同时刷新会话列表以确保协作关系更新
    fetchConversations()
  }

  /**
   * 处理 WebSocket 推送的协作加入/退出事件（广播）
   * 做什么：刷新会话列表
   */
  function handleCollaboratorChanged(): void {
    fetchConversations()
  }

  // ==========================================================================
  // 输入指示器（Typing Indicator）方法
  // ==========================================================================

  /** typing 指示器自动清除定时器 */
  let typingCleanupTimer: ReturnType<typeof setInterval> | null = null

  /**
   * 处理 WebSocket 推送的 typing 事件
   *
   * 做什么：记录某人在某会话中正在输入
   * 为什么：让坐席看到员工/其他坐席正在输入的提示
   * 自动过期：5秒内没有新的 typing 事件则自动清除
   *
   * @param data - typing 事件数据 { conversation_id, sender_id, sender_name, sender_type }
   */
  function handleTypingEvent(data: {
    conversation_id: string
    sender_id: string
    sender_name: string
    sender_type: string
  }): void {
    if (!data.conversation_id) return

    // 更新 typing 记录
    typingUsers.value.set(data.conversation_id, {
      sender_id: data.sender_id,
      sender_name: data.sender_name,
      timestamp: Date.now(),
    })

    // 确保清理定时器在运行（首次触发时启动）
    if (!typingCleanupTimer) {
      typingCleanupTimer = setInterval(() => {
        const now = Date.now()
        const expired: string[] = []
        typingUsers.value.forEach((val, key) => {
          // 5秒无更新则视为停止输入
          if (now - val.timestamp > 5000) {
            expired.push(key)
          }
        })
        expired.forEach(key => typingUsers.value.delete(key))
        // 如果所有记录都已清除，停止定时器
        if (typingUsers.value.size === 0 && typingCleanupTimer) {
          clearInterval(typingCleanupTimer)
          typingCleanupTimer = null
        }
      }, 1000)
    }
  }

  /**
   * 获取指定会话的 typing 指示器文本
   *
   * @param conversationId - 会话ID
   * @returns typing 提示文本，如 "张三正在输入..."，无则返回空字符串
   */
  function getTypingText(conversationId: string): string {
    const info = typingUsers.value.get(conversationId)
    if (!info) return ''
    // 5秒过期检查
    if (Date.now() - info.timestamp > 5000) {
      typingUsers.value.delete(conversationId)
      return ''
    }
    return `${info.sender_name}正在输入...`
  }

  // ==========================================================================
  // 返回
  // ==========================================================================
  return {
    // 状态
    conversations,
    currentConversationId,
    messages,
    loadingConversations,
    loadingMessages,
    pendingReplyText,
    workspaceView,

    // AI Wingman 状态
    aiDrafts,
    currentSummary,
    suggestedTags,
    suggestedCategory,
    suggestedPriority,
    loadingDraft,
    loadingSummary,
    loadingTags,

    // 计算属性
    sortedConversations,
    handRaiseConversations,
    myServingConversations,
    otherAgentConversations,
    servingConversations,
    aiHandlingConversations,
    resolvedConversations,
    collaboratingConversations,
    currentConversation,
    myConversations,
    colleagueConversations,
    historyConversations,

    // 方法
    fetchConversations,
    selectConversation,
    fetchMessages,
    sendReply,
    assignConv,
    resolveConv,
    togglePinConv,
    toggleTodoConv,
    transferConv,
    grabConv,
    inviteToConversation,
    leaveConvCollaboration,

    // 邀请功能方法
    inviteParticipants,
    removeParticipantFromConv,
    leaveAsParticipantConv,

    // AI Wingman 方法
    fetchDraftForMessage,
    fetchSummary,
    fetchSuggestedTags,
    acceptDraft,
    editDraft,
    ignoreDraft,
    getDraft,
    clearWingmanData,

    // 轮询控制
    startConversationPoll,
    stopConversationPoll,
    startMessagePoll,
    stopMessagePoll,
    startAllPolling,
    stopAllPolling,

    // WebSocket 事件处理
    handleNewMessage,
    handleConversationUpdated,
    handleCollaboratorInvited,
    handleCollaboratorChanged,
    handleParticipantInvited,
    handleParticipantChanged,
    handleTypingEvent,
    getTypingText,

    // 输入指示器
    typingUsers,
  }
})
