// =============================================================================
// 企微IT智能服务台 — WebSocket 组合式函数
// =============================================================================
// 说明：封装 WebSocket 连接管理，提供：
//   1. 自动连接 + 断线重连（指数退避，最大 30 秒）
//   2. 心跳保活（每 30 秒发送 ping）
//   3. 事件分发：收到消息后根据 type 调用对应 store 方法
//   4. 降级策略：WS 断连时自动启动轮询 fallback，WS 重连后自动停止轮询
//
// 使用方式：
//   const { connect, disconnect } = useWebSocket()
//   onMounted(() => connect())
//   onUnmounted(() => disconnect())
// =============================================================================

import { useAgentStore } from '@/stores/agent'
import { useConversationStore } from '@/stores/conversation'

// --------------------------------------------------------------------------
// 常量配置
// --------------------------------------------------------------------------
/** 心跳间隔（毫秒）：每 30 秒发送一次 ping，保持连接存活 */
const HEARTBEAT_INTERVAL = 30000

/** 最大重连延迟（毫秒）：指数退避上限 30 秒 */
const MAX_RECONNECT_DELAY = 30000

/** 重连延迟基数（毫秒）：首次重连等待 1 秒 */
const RECONNECT_BASE_DELAY = 1000

/**
 * WebSocket 组合式函数
 *
 * 核心职责：
 * - 管理 WebSocket 连接的生命周期（建立、维持、断开、重连）
 * - 处理服务端推送的实时事件，分发到对应的 store
 * - 实现 WS → 轮询的自动降级和恢复
 *
 * 为什么用组合式函数（composable）而不是全局单例：
 * - 遵循 Vue3 的组合式 API 模式，与组件生命周期绑定
 * - 可以在多个组件中复用，同时保持状态隔离（如果需要）
 * - 方便在 onMounted/onUnmounted 中调用 connect/disconnect
 */
export function useWebSocket() {
  // ==========================================================================
  // 内部状态
  // ==========================================================================

  /** WebSocket 实例 */
  let ws: WebSocket | null = null

  /** 心跳定时器 ID */
  let heartbeatTimer: ReturnType<typeof setInterval> | null = null

  /** 重连定时器 ID */
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null

  /** 重连尝试次数（用于指数退避计算） */
  let reconnectAttempts = 0

  /** 是否主动断开（用户登出时设为 true，避免自动重连） */
  let intentionalDisconnect = false

  // ==========================================================================
  // 连接管理
  // ==========================================================================

  /**
   * 建立 WebSocket 连接
   *
   * 做什么：根据当前坐席ID构建 WS URL，建立连接，注册事件处理函数
   * 为什么：坐席登录后需要实时接收新消息和会话变更事件
   *
   * 连接 URL 格式：
   * - 开发环境：ws://localhost:5173/ws/{agentId}（Vite 代理转发到后端）
   * - 生产环境：wss://domain.com/ws/{agentId}（自动检测协议）
   */
  function connect(): void {
    const agentStore = useAgentStore()
    const agentId = agentStore.userId

    // 如果没有坐席ID，说明未登录，不建立连接
    if (!agentId) {
      console.warn('[WebSocket] 未登录，跳过连接')
      return
    }

    // 如果已有连接，先断开
    if (ws) {
      disconnect()
    }

    // 重置主动断开标记
    intentionalDisconnect = false

    // 构建 WebSocket URL
    // 开发环境：直接连后端 8000 端口（避免 Vite WS 代理兼容性问题）
    // 生产环境：通过同源 wss:// 连接（nginx 统一代理）
    const isDev = import.meta.env.DEV
    const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const wsHost = isDev ? 'localhost:8000' : window.location.host
    const wsUrl = `${wsProtocol}//${wsHost}/ws/${agentId}`


    console.log(`[WebSocket] 正在连接: ${wsUrl}`)
    // P0-#4 修复: 用 Sec-WebSocket-Protocol (subprotocols) 传递 token
    // 浏览器原生 WebSocket API 第2参数是 protocols (字符串数组)，不是 headers
    // 服务端从 sec-websocket-protocol 头读取 bearer.{token}
    ws = new WebSocket(wsUrl, [`bearer.${agentStore.token}`])

    // ----------------------------------------------------------------------
    // 连接成功
    // ----------------------------------------------------------------------
    ws.onopen = () => {
      console.log('[WebSocket] 连接成功')
      // 重置重连计数
      reconnectAttempts = 0
      // 启动心跳
      startHeartbeat()
      // WS 已连接，停止轮询 fallback
      // 为什么：WS 连接正常时不需要轮询，减少不必要的 HTTP 请求
      const conversationStore = useConversationStore()
      conversationStore.stopAllPolling()
    }

    // ----------------------------------------------------------------------
    // 收到消息
    // ----------------------------------------------------------------------
    ws.onmessage = (event: MessageEvent) => {
      try {
        const msg = JSON.parse(event.data)
        handleMessage(msg)
      } catch (error) {
        console.error('[WebSocket] 消息解析失败:', error)
      }
    }

    // ----------------------------------------------------------------------
    // 连接关闭
    // ----------------------------------------------------------------------
    ws.onclose = () => {
      console.log('[WebSocket] 连接关闭')
      // 停止心跳
      stopHeartbeat()
      // 清空 ws 引用
      ws = null

      // 如果不是主动断开，启动降级和重连
      if (!intentionalDisconnect) {
        // WS 断连，启动轮询 fallback
        // 为什么：WS 不可用时，仍需通过轮询获取最新数据，保证坐席能看到新消息
        const conversationStore = useConversationStore()
        conversationStore.startAllPolling()
        // 尝试重连
        scheduleReconnect()
      }
    }

    // ----------------------------------------------------------------------
    // 连接错误
    // ----------------------------------------------------------------------
    ws.onerror = (error: Event) => {
      console.error('[WebSocket] 连接错误:', error)
      // onclose 会自动触发，这里不需要额外处理
      // 只记录日志，onclose 中会启动降级和重连
    }
  }

  /**
   * 主动断开 WebSocket 连接
   *
   * 做什么：关闭 WS 连接，清理定时器，标记为主动断开
   * 为什么：坐席登出时需要主动断开，避免后台重连
   */
  function disconnect(): void {
    // 标记为主动断开，阻止自动重连
    intentionalDisconnect = true

    // 清理重连定时器
    if (reconnectTimer) {
      clearTimeout(reconnectTimer)
      reconnectTimer = null
    }

    // 清理心跳定时器
    stopHeartbeat()

    // 关闭 WebSocket 连接
    if (ws) {
      ws.close()
      ws = null
    }

    // 重置重连计数
    reconnectAttempts = 0

    console.log('[WebSocket] 已主动断开连接')
  }

  // ==========================================================================
  // 心跳保活
  // ==========================================================================

  /**
   * 启动心跳定时器
   *
   * 做什么：每 HEARTBEAT_INTERVAL 毫秒发送一次 ping 消息
   * 为什么：防止中间代理（Nginx、CDN 等）因空闲超时断开 WebSocket 连接
   */
  function startHeartbeat(): void {
    // 先清理旧定时器（避免重复）
    stopHeartbeat()

    heartbeatTimer = setInterval(() => {
      if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: 'ping' }))
        // 注意：不发 console.log，避免频繁输出
      }
    }, HEARTBEAT_INTERVAL)
  }

  /**
   * 停止心跳定时器
   */
  function stopHeartbeat(): void {
    if (heartbeatTimer) {
      clearInterval(heartbeatTimer)
      heartbeatTimer = null
    }
  }

  // ==========================================================================
  // 断线重连（指数退避）
  // ==========================================================================

  /**
   * 安排重连
   *
   * 做什么：根据指数退避算法计算延迟，安排下一次重连
   * 为什么：避免 WS 断连后所有客户端同时重连导致服务器压力过大
   *
   * 指数退避公式：delay = min(base * 2^attempts, maxDelay)
   * 第1次重连：1秒后
   * 第2次重连：2秒后
   * 第3次重连：4秒后
   * 第4次重连：8秒后
   * 第5次重连：16秒后
   * 第6次及以后：30秒后（达到上限）
   */
  function scheduleReconnect(): void {
    // 如果已主动断开，不重连
    if (intentionalDisconnect) return

    // 计算延迟（指数退避）
    const delay = Math.min(
      RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts),
      MAX_RECONNECT_DELAY
    )
    reconnectAttempts++

    console.log(
      `[WebSocket] 将在 ${delay / 1000} 秒后重连（第 ${reconnectAttempts} 次）`
    )

    // 清理旧的重连定时器
    if (reconnectTimer) {
      clearTimeout(reconnectTimer)
    }

    // 安排重连
    reconnectTimer = setTimeout(() => {
      console.log('[WebSocket] 正在重连...')
      connect()
    }, delay)
  }

  // ==========================================================================
  // 消息处理（事件分发）
  // ==========================================================================

  /**
   * 处理从 WebSocket 收到的消息
   *
   * 做什么：根据消息的 type 字段，调用对应的 store 方法处理
   * 为什么：不同类型的事件需要不同的处理逻辑，集中分发便于维护
   *
   * 消息类型：
   * - new_message: 新消息事件，由 message_router 触发
   * - conversation_updated: 会话状态变更事件，由 session_service 触发
   * - pong: 心跳响应，忽略
   *
   * @param msg - WebSocket 消息对象，包含 type 和 data 字段
   */
  function handleMessage(msg: { type: string; data?: any }): void {
    const conversationStore = useConversationStore()

    switch (msg.type) {
      case 'new_message':
        // 新消息事件：追加到消息列表 + 刷新会话列表 + 播放提示音
        if (msg.data) {
          conversationStore.handleNewMessage(msg.data)
          // 播放新消息提示音（Web Audio API，零依赖）
          playNotificationSound()
        }
        break

      case 'conversation_updated':
        // 会话状态变更事件：刷新会话列表
        if (msg.data) {
          conversationStore.handleConversationUpdated(msg.data)
        }
        break

      case 'collaborator_invited':
        // 摇人邀请（定向推送）事件：被邀请坐席收到通知 + 刷新列表
        if (msg.data) {
          conversationStore.handleCollaboratorInvited(msg.data)
          // 弹窗通知被邀请的坐席（useWebSocket 不依赖组件，通过 ElNotification 全局弹窗）
          const agentStore = useAgentStore()
          if (msg.data.invitee_agent_id === agentStore.userId) {
            import('element-plus').then(({ ElNotification }) => {
              ElNotification({
                title: '🔔 摇人邀请',
                message: `坐席 ${msg.data.inviter_agent_id} 邀请你协助处理会话「${msg.data.employee_name || '未知'}」\n${msg.data.last_message_summary || ''}`,
                type: 'info',
                duration: 0,  // 不自动关闭
                position: 'top-right',
                onClick: () => {
                  conversationStore.selectConversation(msg.data.conversation_id)
                }
              })
            })
          }
        }
        break

      case 'collaborator_joined':
      case 'collaborator_left':
        // 协作关系变更（广播）事件：刷新会话列表
        conversationStore.handleCollaboratorChanged()
        break

      // ==================================================================
      // 邀请功能事件（P0-09~P0-11）
      // ==================================================================
      case 'participant_invited':
        // 参与者被邀请：刷新会话列表（更新 participants 字段）
        conversationStore.handleCollaboratorChanged()
        break

      case 'participant_joined':
        // 参与者加入：刷新会话列表 + 系统消息
        conversationStore.handleCollaboratorChanged()
        break

      case 'participant_removed':
      case 'participant_left':
        // 参与者移除/退出：刷新会话列表
        conversationStore.handleCollaboratorChanged()
        break

      case 'pong':
        // 心跳响应，不需要处理
        break

      case 'typing':
        // 输入指示器事件：某人正在输入
        // 过滤掉自己的 typing 事件，只显示其他人的
        if (msg.data) {
          const agentStore = useAgentStore()
          if (msg.data.sender_id !== agentStore.userId) {
            conversationStore.handleTypingEvent(msg.data)
          }
        }
        break

      default:
        console.warn(`[WebSocket] 未知消息类型: ${msg.type}`)
    }
  }

  // ==========================================================================
  // 新消息提示音（Web Audio API，零依赖，无需额外文件）
  // ==========================================================================
  let audioCtx: AudioContext | null = null

  /**
   * 播放新消息提示音
   *
   * 做什么：收到新消息时播放短促提示音
   * 为什么：坐席需要即时感知新消息到达
   * 怎么做：用 Web Audio API 合成一个短促的双音提示（无需外部音频文件）
   */
  function playNotificationSound(): void {
    try {
      if (!audioCtx) {
        audioCtx = new AudioContext()
      }
      // 双音提示：两段简短的正弦波（类似「叮咚」）
      const now = audioCtx.currentTime
      // 第一声「叮」（880Hz，0.1秒）
      const osc1 = audioCtx.createOscillator()
      const gain1 = audioCtx.createGain()
      osc1.type = 'sine'
      osc1.frequency.value = 880
      gain1.gain.setValueAtTime(0.3, now)
      gain1.gain.exponentialRampToValueAtTime(0.001, now + 0.15)
      osc1.connect(gain1)
      gain1.connect(audioCtx.destination)
      osc1.start(now)
      osc1.stop(now + 0.15)
      // 第二声「咚」（660Hz，0.15秒，延迟0.1秒）
      const osc2 = audioCtx.createOscillator()
      const gain2 = audioCtx.createGain()
      osc2.type = 'sine'
      osc2.frequency.value = 660
      gain2.gain.setValueAtTime(0.3, now + 0.1)
      gain2.gain.exponentialRampToValueAtTime(0.001, now + 0.3)
      osc2.connect(gain2)
      gain2.connect(audioCtx.destination)
      osc2.start(now + 0.1)
      osc2.stop(now + 0.3)
    } catch {
      // AudioContext 不可用时静默降级（不影响消息接收）
    }
  }

  // ==========================================================================
  // 发送 typing 事件（输入指示器）
  // ==========================================================================

  /** 上次发送 typing 的时间戳（节流：每 3 秒最多发一次） */
  let lastTypingSentAt = 0

  /** typing 节流间隔（毫秒） */
  const TYPING_THROTTLE_MS = 3000

  /**
   * 发送 typing 事件到后端
   *
   * 做什么：坐席在输入框打字时，通知后端广播给其他参与者
   * 为什么：让员工/其他坐席看到「坐席正在输入...」提示
   * 怎么做：通过 WebSocket 发送 { type: "typing", conversation_id, sender_name }
   *
   * 节流机制：每 3 秒最多发送一次，避免频繁广播
   *
   * @param conversationId - 当前会话ID
   */
  function sendTyping(conversationId: string): void {
    if (!ws || ws.readyState !== WebSocket.OPEN) return

    // 节流：3 秒内不重复发送
    const now = Date.now()
    if (now - lastTypingSentAt < TYPING_THROTTLE_MS) return
    lastTypingSentAt = now

    const agentStore = useAgentStore()
    ws.send(JSON.stringify({
      type: 'typing',
      conversation_id: conversationId,
      sender_name: agentStore.agentName || '坐席',
    }))
  }

  // ==========================================================================
  // 返回
  // ==========================================================================
  return {
    /** 建立 WebSocket 连接 */
    connect,
    /** 主动断开 WebSocket 连接（登出时调用） */
    disconnect,
    /** 发送 typing 事件（输入指示器，3 秒节流） */
    sendTyping,
  }
}
