/* eslint-disable @typescript-eslint/sort-type-constituents */
/**
 * meeting WebSocket 模块的“类型总出口”
 *
 * 目标：
 * - 把 socket.io Server/Namespace/Socket 的泛型参数一次性定义清楚
 * - 让 meeting.websocket.ts 不再依赖 any/as any 来访问 socket.data.user 或事件名
 * - 让 fetchSockets() 返回的 RemoteSocket 拥有正确的 data 类型
 */
import type { Namespace, RemoteSocket, Server, Socket } from 'socket.io';

export interface MeetingWsUser {
  /** 用户长 ID（来自 Token 中的 nameidentifier claim） */
  userId: string;
  /** 角色：0 学生，非 0 代表创建者/管理员（和当前业务一致） */
  role: number;
  /** 声网短 UID（用于加入 RTC 频道） */
  shortUid: number;
  /** 平台标识（从 Custom-Platform 解析得来） */
  platform: string;
  /** 用户名称（用于展示和黑名单） */
  userName: string;
}

export interface MeetingSocketData {
  /**
   * 通过 MeetingAuthGuard 注入到 socket.data 中的用户信息
   * - 该字段会被包含在 fetchSockets() 返回结果的 data 里
   * - 因此这是“跨节点查找用户 / 过滤用户”的关键字段
   */
  user?: MeetingWsUser;

  /**
   * 当前 socket 加入的业务房间 ID（Socket.IO 房间名）
   * - 由 handleJoinRoom 写入
   * - 由 handleLeaveRoom/handleDisconnect 用于判断“房间是否已空”
   */
  roomId?: string;

  /**
   * 课程房间 ID（前端路由中的 id）
   * - 主要用于清理 courseRoomId -> roomId 的映射 key
   */
  courseRoomId?: string;

  /**
   * 加入方式（课程/监控/学生）
   * - 由 handleJoinRoom 写入
   * - 用于区分教师端窗口断开时是否需要触发下课逻辑
   */
  joinMode?: JoinMode;
}

export interface MeetingJoinRoomData {
  /** 课程房间 ID（前端路由参数） */
  courseRoomId: string;
  /** 用户长 ID（数据库中的真实用户 ID，用于分配 shortUid） */
  longUserId: number;
  /** 展示用用户名 */
  userName: string;
  /** 是否创建者（可选；当前后端实际仍以 socket.data.user.role 判断权限） */
  isHost?: boolean;
}

export interface MeetingKickUserData {
  /** 目标用户声网 shortUid */
  targetUid: number;
  /** 房间 ID（服务端实际 Socket.IO 房间名） */
  roomId: string;
}

export interface MeetingLeaveRoomData {
  /** 课程房间 ID（用于业务侧记录/日志；Socket.IO 会自动离开房间） */
  courseRoomId: string;
}

export interface MeetingMuteUserData {
  /** 目标用户声网 shortUid */
  targetUid: number;
  /** 房间 ID（服务端实际 Socket.IO 房间名） */
  roomId: string;
}

/**
 * 投屏数据（上行：客户端 -> 服务端）
 */
export interface MeetingScreenShareData {
  /** 房间 ID（服务端实际 Socket.IO 房间名） */
  roomId: string;
  screenUid: number;
}

/**
 * 投屏通知数据（下行：服务端 -> 客户端）
 */
export interface MeetingScreenShareNotifyData {
  /** 来源房间 */
  fromRoomId: string;
  /** 目标用户声网 shortUid */
  targetUid: number;
  /** 投屏用户声网 shortUid */
  screenUid: number;
}

export interface MeetingClientToServerEvents {
  /** 客户端加入会议房间（join + 分配 shortUid + 返回 tokenInfo + 恢复禁用状态） */
  client_join_room: (data: MeetingJoinRoomData) => void;
  /** 客户端离开会议房间（业务事件；Socket.IO 会自动处理房间成员移除） */
  client_leave_room: (data: MeetingLeaveRoomData) => void;
  /** 创建者踢人 */
  client_kick_user: (data: MeetingKickUserData) => void;
  /** 创建者禁麦 */
  client_mute_audio: (data: MeetingMuteUserData) => void;
  /** 创建者解除禁麦 */
  client_unmute_audio: (data: MeetingMuteUserData) => void;
  /** 创建者禁视频 */
  client_mute_video: (data: MeetingMuteUserData) => void;
  /** 创建者解除禁视频 */
  client_unmute_video: (data: MeetingMuteUserData) => void;
  /** 创建者设置某个用户为全员主屏 */
  client_set_main_video: (data: MeetingMuteUserData) => void;
  /** 创建者开始投屏 */
  client_start_screen_share: (data: MeetingScreenShareData) => void;
  /** 创建者停止投屏 */
  client_stop_screen_share: (data: MeetingScreenShareData) => void;
  /** 续期房间结束时间（仅创建者/老师） */
  client_renew_room_end_timestamp: (data: { roomId: string; endTimestamp: number }) => void;
}

export interface MeetingWsMessagePacket<T extends string = string, D = unknown> {
  /** 消息类型（sev_* / error 等） */
  type: T;
  /** 业务数据 */
  data: D;
}

/**
 * Token 响应数据 DTO
 */
export interface TokenResponseDto {
  /** 声网appid */
  appid: string;
  /** 声网token */
  rtcToken: string;
  /** 过期时间 */
  expiresAt: number;
}

export interface MeetingJoinRoomSuccessData {
  /** 服务端实际房间 ID（用于后续控制指令中的 roomId） */
  roomId: string;
  /** 当前用户声网短 UID */
  shortUid: number;
  /** 声网 token 信息（当前项目中由 meetingService.generateToken 生成） */
  tokenInfo: TokenResponseDto;
  /** 恢复状态：是否被禁麦 */
  isAudioMuted: boolean;
  /** 恢复状态：是否被禁视频 */
  isVideoMuted: boolean;
  /** 课堂状态：not_started（未开始）/ in_class（上课中）/ finished（已下课） */
  classStatus: 'finished' | 'in_class' | 'not_started';
  /** 投屏状态：正在投屏的用户短 UID，undefined 表示无人投屏 */
  screenShareUid?: number;
  screenShareOwnerUid?: number;
  /** 上台的短uid，默认为老师的uid */
  speakerUid: number;
  /** 老师的短uid */
  teacherUid: number;
  /** 房间结束时间（毫秒时间戳） */
  endTimestamp?: number;
}

export interface MeetingKickedData {
  /** 来源房间（可选） */
  fromRoomId?: string;
  /** 提示原因（可选） */
  reason?: string;
  /** 被踢出的用户短 UID（用于前端同步状态） */
  targetUid?: number;
}

export interface MeetingControlData {
  /** 来源房间（用于前端提示/一致性校验） */
  fromRoomId: string;
  /** 被控制的用户短 UID（用于前端同步状态） */
  targetUid?: number;
}

export interface MeetingSetMainVideoData {
  /** 来源房间 */
  fromRoomId: string;
  /** 被设置为主屏的用户短 UID */
  targetUid: number;
}

export interface MeetingClassStateData {
  /** 来源房间 */
  fromRoomId: string;
}

export interface MeetingRoomWillExpireData {
  /** 来源房间 */
  fromRoomId: string;
  /** 房间结束时间（毫秒时间戳） */
  endTimestamp: number;
  /** 剩余毫秒数 */
  remainingMs: number;
}

export interface MeetingRoomEndTimestampUpdatedData {
  /** 来源房间 */
  fromRoomId: string;
  /** 房间结束时间（毫秒时间戳） */
  endTimestamp: number;
}

export type MeetingDownlinkPacket =
  /** 统一错误包（WsExceptionFilter/业务侧主动 emit 的错误） */
  | ErrorMessage
  /** 下课通知 */
  | MeetingWsMessagePacket<'sev_class_ended', MeetingClassStateData>
  /** 上课通知 */
  | MeetingWsMessagePacket<'sev_class_started', MeetingClassStateData>
  /** 设备冲突通知（多设备登录被踢出） */
  | MeetingWsMessagePacket<'sev_device_conflict', MeetingKickedData>
  /** 加入房间成功响应 */
  | MeetingWsMessagePacket<'sev_join_room', MeetingJoinRoomSuccessData>
  /** 被踢出通知 */
  | MeetingWsMessagePacket<'sev_kick_user', MeetingKickedData>
  /** 禁麦通知 */
  | MeetingWsMessagePacket<'sev_mute_audio', MeetingControlData>
  /** 禁视频通知 */
  | MeetingWsMessagePacket<'sev_mute_video', MeetingControlData>
  /** 设置主屏通知 */
  | MeetingWsMessagePacket<'sev_set_main_video', MeetingSetMainVideoData>
  /** 开始投屏通知 */
  | MeetingWsMessagePacket<'sev_start_screen_share', MeetingScreenShareNotifyData>
  /** 停止投屏通知 */
  | MeetingWsMessagePacket<'sev_stop_screen_share', MeetingScreenShareNotifyData>
  /** 解除禁麦通知 */
  | MeetingWsMessagePacket<'sev_unmute_audio', MeetingControlData>
  /** 解除禁视频通知 */
  | MeetingWsMessagePacket<'sev_unmute_video', MeetingControlData>
  /** 房间即将到期提醒（仅老师端） */
  | MeetingWsMessagePacket<'sev_room_will_expire', MeetingRoomWillExpireData>
  /** 房间结束时间更新（续期后广播） */
  | MeetingWsMessagePacket<'sev_room_end_timestamp_updated', MeetingRoomEndTimestampUpdatedData>;

export interface MeetingServerToClientEvents {
  /** 约定：服务端统一通过 message 事件下发业务包（packet.type 决定语义） */
  message: (packet: MeetingDownlinkPacket) => void;
}

/**
 * 客户端 -> 服务端（上发消息）
 */
export type ClientToServerMessageType =
  | 'client_end_class' // 结束课程（仅创建者）
  | 'client_join_room' // 加入会议房间
  | 'client_kick_user' // 踢出指定用户（仅创建者）
  | 'client_leave_room' // 离开会议房间
  | 'client_mute_audio' // 禁麦指定用户（仅创建者）
  | 'client_mute_video' // 禁视频指定用户（仅创建者）
  | 'client_set_main_video' // 设置主屏用户
  | 'client_start_class' // 开始课程（仅创建者）
  | 'client_start_screen_share' // 开始投屏
  | 'client_stop_screen_share' // 停止投屏
  | 'client_unmute_audio' // 解除禁麦（仅创建者）
  | 'client_unmute_video' // 解除禁视频（仅创建者）
  | 'client_renew_room_end_timestamp'; // 续期房间结束时间（仅创建者）

/**
 * 服务端 -> 客户端（下发消息）
 */
export type ServerToClientMessageType =
  | 'sev_class_ended' // 下课通知
  | 'sev_class_started' // 上课通知
  | 'sev_device_conflict' // 设备冲突通知（多设备登录被踢出）
  | 'sev_join_room' // 加入房间成功响应
  | 'sev_kick_user' // 被踢出通知（创建者踢人）
  | 'sev_mute_audio' // 禁麦通知（转发给目标用户）
  | 'sev_mute_video' // 禁视频通知（转发给目标用户）
  | 'sev_set_main_video' // 设置主屏通知
  | 'sev_start_screen_share' // 开始投屏通知（广播给所有人）
  | 'sev_stop_screen_share' // 停止投屏通知（广播给所有人）
  | 'sev_unmute_audio' // 解除禁麦通知（转发给目标用户）
  | 'sev_unmute_video' // 解除禁视频通知（转发给目标用户）
  | 'sev_room_will_expire' // 房间即将到期提醒（仅老师端）
  | 'sev_room_end_timestamp_updated'; // 房间结束时间更新（续期后广播）

export interface ErrorMessage {
  // 固定为 error，便于前端统一处理
  type: 'error';
  // 错误信息载体
  data: { reason: string; code?: number };
}

export interface SucceedMessage {
  // 下发消息类型（sev_*）
  type: ServerToClientMessageType;
  // 最小化返回字段：来源房间 + 可选原因 + 可选目标用户
  data: {
    reason?: string;
    fromRoomId: string;
    targetUid?: number;
  };
}

/**
 * 加入房间数据
 */
export type JoinMode = 'classroom' | 'monitor' | 'student';

export interface JoinRoomData {
  // 课程房间 ID（前端传入）
  courseRoomId: string;
  /** 作业 ID */
  homeworkId: number;
  // 用户名（仅用于日志/展示）
  userName: string;
  /** 加入方式（课程/监控/学生） */
  joinMode: JoinMode;
  /** 房间结束时间（毫秒时间戳），仅老师端首次进入时会写入 */
  endTimestamp?: number;
}

/**
 * 踢人数据
 */
export interface KickUserData {
  // 目标用户声网 shortUid
  targetUid: number;
  // 房间 ID（Socket.IO 房间名）
  roomId: string;
}

export interface MuteUserData {
  // 目标用户声网 shortUid
  targetUid: number;
  // 房间 ID（Socket.IO 房间名）
  roomId: string;
}

/**
 * 用户状态（用于恢复被禁麦/禁视频状态，以及管理多设备连接）
 */
export interface UserPermissionState {
  /** 是否被禁麦 */
  isAudioMuted: boolean;
  /** 是否被禁视频 */
  isVideoMuted: boolean;
  /** 用户的所有 Socket 连接 ID（支持多设备同时在线） */
  socketIds: string[];
  /** 用户的 Socket 连接详情（用于区分加入方式） */
  socketEntries?: Array<{ socketId: string; joinMode: JoinMode }>;
}

/**
 * 黑名单用户信息
 */
export interface BlacklistUser {
  /** 短 UID */
  shortUid: number;
  /** 用户名称 */
  userName: string;
}
/**
 * 强类型化的 Server / Namespace / Socket / RemoteSocket
 *
 * 对应 socket.io v4 的泛型定义：
 * Server<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
 * - ListenEvents：客户端 -> 服务端（socket.on / @SubscribeMessage）
 * - EmitEvents：服务端 -> 客户端（socket.emit）
 * - ServerSideEvents：服务器之间 serverSideEmit（可选）
 * - SocketData：socket.data 中可持久化/可被 fetchSockets 取回的数据
 */
export type MeetingServer = Server<MeetingClientToServerEvents, MeetingServerToClientEvents, any, MeetingSocketData>;
export type MeetingNamespace = Namespace<MeetingClientToServerEvents, MeetingServerToClientEvents, any, MeetingSocketData>;
export type MeetingSocket = Socket<MeetingClientToServerEvents, MeetingServerToClientEvents, any, MeetingSocketData>;
export type MeetingRemoteSocket = RemoteSocket<MeetingServerToClientEvents, MeetingSocketData>;
