Source: services/socketService.js

/**
 * @file services/socketService.js
 * @description 负责管理 Socket.IO 客户端连接、事件监听和状态同步。
 * @module SocketService
 */

import { io } from 'socket.io-client'
import {
  useUserStore,
  useChatStore,
  useFriendStore,
  useGroupStore
} from '@/stores'
import emitter from './eventBus' // 导入全局事件总线

/**
 * @type {Socket|null}
 * @description Socket.IO 客户端实例。
 * @private
 */
let socket = null

/**
 * @function connectSocket
 * @description 连接 Socket.IO 服务器。
 * 如果 socket 已连接或用户没有 token,则不执行连接。
 * 设置连接、断开、错误和各种实时消息事件监听器。
 * @returns {Socket|null} 连接成功的 Socket 实例,如果无法连接则返回 null。
 */
export const connectSocket = () => {
  const userStore = useUserStore()
  const chatStore = useChatStore()

  // 如果 socket 已经存在且已连接,直接返回
  if (socket && socket.connected) {
    return socket
  }

  // 如果用户没有 token,则无法连接
  if (!userStore.token) {
    console.error('Socket.IO: 该用户无 token')
    return null
  }

  // 获取 Socket URL
  const VITE_SOCKET_URL =
    import.meta.env.VITE_SOCKET_URL || 'http://localhost:3000'

  // 创建 Socket 实例,并附带认证 token
  socket = io(VITE_SOCKET_URL, {
    auth: {
      token: userStore.token
    }
  })

  /**
   * @event connect
   * @description 监听 Socket 连接成功事件。
   */
  socket.on('connect', () => {
    console.log('Socket 连接成功')
  })

  /**
   * @event disconnect
   * @description 监听 Socket 断开连接事件。
   * 如果是服务器断开,尝试重连。
   * @param {string} reason - 断开连接的原因。
   */
  socket.on('disconnect', (reason) => {
    if (reason === 'io server disconnect') {
      socket.connect() // 尝试重连
    }
    console.warn(`Socket: 连接断开,原因: ${reason}`)
  })

  /**
   * @event connect_error
   * @description 监听 Socket 连接错误事件。
   * 如果是认证失败,则执行用户登出并提示重新登录。
   * @param {Error} err - 连接错误对象。
   */
  socket.on('connect_error', (err) => {
    console.error(`Socket: 连接错误, 原因:${err.message}`)
    if (err.message.includes('认证失败')) {
      userStore.logout()
      ElMessage.success('请重新登陆')
      router.push('/auth')
    }
  })

  /**
   * @event new_message
   * @description 监听新的实时消息事件。
   * 处理普通消息和提及消息,并更新聊天 Store 和发送通知。
   * @param {object} newMessage - 新消息数据。
   * @param {Array<string>} newMessage.mentions - 被提及用户的ID列表。
   * @param {string} newMessage.conversationId - 消息所属会话ID。
   * @param {object} newMessage.groupInfo - 如果是群组消息,包含群组信息。
   * @param {object} newMessage.sender - 消息发送者信息。
   * @param {string} newMessage.content - 消息内容。
   */
  socket.on('new_message', (newMessage) => {
    // 如果没有提及任何人,直接处理为普通新消息
    if (newMessage.mentions.length === 0) {
      chatStore.handleNewRealTimeMessage(newMessage)
    } else {
      // 如果消息包含提及,并且当前不在该会话中,则发送桌面通知
      if (newMessage.conversationId !== chatStore.activeConversation?._id) {
        emitter.emit('show-notification', {
          type: 'mention',
          title: `在 #${newMessage.groupInfo.name} 中被提及`,
          message: `${newMessage.sender.username}: ${newMessage.content}`
        })
      }
      // 即使在当前会话,也要处理新消息,因为提及消息也可能是普通消息
      chatStore.handleNewRealTimeMessage(newMessage)
    }
  })

  /**
   * @event new_friend_request
   * @description 监听新的好友请求事件。
   * 更新好友 Store 中的请求列表。
   * @param {object} requestData - 新的好友请求数据。
   */
  socket.on('new_friend_request', (requestData) => {
    const friendStore = useFriendStore()
    friendStore.handleNewRequest(requestData)
  })

  /**
   * @event new_group_invitation
   * @description 监听新的群组邀请事件。
   * 更新群组 Store 中的邀请列表。
   * @param {object} invitationData - 新的群组邀请数据。
   */
  socket.on('new_group_invitation', (invitationData) => {
    const groupStore = useGroupStore()
    groupStore.handleNewRequest(invitationData)
  })

  return socket
}

/**
 * @function disconnectSocket
 * @description 断开 Socket.IO 连接。
 * @returns {void}
 */
export const disconnectSocket = () => {
  if (socket) {
    socket.disconnect()
    socket = null
  }
}

/**
 * @function getSocket
 * @description 获取当前活动的 Socket.IO 实例。
 * @returns {Socket|null} 当前的 Socket 实例,如果未连接则可能为 null。
 */
export const getSocket = () => {
  return socket
}