<template>
  <div class="page">
    <div class="page-content">
      <div class="top-bar">
        <div class="room-info">
          <icon-mdi-video class="room-icon"></icon-mdi-video>
          <div class="room-details">
            <span class="room-name">{{ courseRoomName }}</span>
            <span class="room-id">监控端 · 房间 ID: {{ courseRoomId }}</span>
          </div>
        </div>
        <div class="network-status" :class="networkQualityClass">
          <icon-mdi-wifi class="network-icon"></icon-mdi-wifi>
          <span class="network-text">{{ networkStatusText }}</span>
        </div>
      </div>
      <div class="monitor-container">
        <el-empty v-if="classStatus !== 'in_class'" :description="classStatus === 'not_started' ? '等待老师上课...' : '老师已下课，等待重新上课...'"></el-empty>
        <div v-else-if="studentUsers.length === 0" class="empty-students">
          <el-empty description="暂无学生画面"></el-empty>
        </div>
        <div
          v-else
          class="grid"
          :style="{
            '--grid-cols': String(gridCols),
            '--grid-rows': String(gridRows),
          }"
        >
          <div v-for="u in studentUsers" :key="String(u.uid)" class="grid-item">
            <div :id="`player-${u.uid}`" class="player-wrapper"></div>
            <div class="video-label">
              <span>{{ u.userName || `用户 ${u.uid}` }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import type { IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng';
  import IconMdiWifi from '~icons/mdi/wifi';
  import IconMdiVideo from '~icons/mdi/video';
  import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue';
  import { useRoute, useRouter } from 'vue-router';
  import { ElMessageBox, ElMessage as Toast } from 'element-plus';
  import type { OnJoinRoomData, ServerToClientEvents } from './types';
  import { useAgoraMeeting } from './useAgoraMeeting';
  import { destroyMeetingSocket, useMeetingSocket, useWebSocket } from './websocket';

  const route = useRoute();
  const router = useRouter();
  const userStore = useUserStore();

  /** 课程房间名称 */
  const courseRoomName = computed(() => String(route.query.name || ''));
  /** 用户长 ID */
  const longUserId = computed(() => userStore.userInfo?.id || 0);
  /** 用户名称 */
  const userName = computed(() => userStore.userInfo?.name || '用户');
  /** 作业 ID */
  const homeworkId = computed(() => Number(route.query.homeworkId || 0));
  /** 课程房间ID */
  const courseRoomId = computed(() => `n_${homeworkId.value}`);

  /** 课堂状态：not_started表示未开始 / in_class表示上课中 / finished表示已结束 */
  const classStatus = ref<'finished' | 'in_class' | 'not_started'>('not_started');
  /** 用户的短id */
  const shortUid = ref<number>(0);
  /** 房间号 */
  const roomId = ref<string>('');
  /** 老师短 UID */
  const teacherUid = ref<number>(0);

  /** 监控端是否发布本地音视频（默认不发布） */
  const isPublishLocalTracks = ref<boolean>(false);
  /** 是否禁用本地音视频轨道 */
  const disableLocalTracks = computed(() => !isPublishLocalTracks.value);

  const meeting = useAgoraMeeting(
    courseRoomId,
    userName,
    shortUid,
    {
      onTokenWillExpire: () => {
        Toast.warning('Token 即将过期，正在自动刷新...');
      },
      onTokenExpired: () => {
        Toast.error('Token 已过期，请重新加入会议');
      },
      onConnectionStateChange: (state: string) => {
        if (state === 'DISCONNECTED') {
          Toast.warning('网络连接已断开');
        } else if (state === 'RECONNECTING') {
          Toast.info('正在重新连接...');
        }
      },
      onError: (error: Error) => {
        Toast.error(error.message || '操作失败');
      },
    },
    { disableLocalTracks }
  );

  /** 加入方式 */
  const joinMode = computed(() => 'monitor' as const);
  /** 房间结束时间（毫秒时间戳） */
  const endTimestamp = computed(() => {
    const t = Number(route.query.endTimestamp || 0);
    return Number.isFinite(t) && t > 0 ? t : null;
  });
  /** 房间结束时间（毫秒时间戳） */
  const roomEndTimestamp = ref<number | null>(null);
  /** 是否正在弹出续期窗口 */
  const isRenewPrompting = ref<boolean>(false);

  watchEffect(() => {
    if (roomEndTimestamp.value === null && endTimestamp.value) {
      roomEndTimestamp.value = endTimestamp.value;
    }
  });

  const socket = useWebSocket();
  const meetingSocket = useMeetingSocket(courseRoomId, longUserId, userName, homeworkId, joinMode, endTimestamp);

  const { networkQuality, selectedUserUid, leaveChannel, cleanup } = meeting;

  /** 网络质量样式类名 */
  const networkQualityClass = computed(() => networkQuality.value);

  /** 网络状态文本描述 */
  const networkStatusText = computed(() => {
    const qualityMap: Record<string, string> = {
      good: '网络良好',
      fair: '网络一般',
      poor: '网络较差',
    };
    return qualityMap[networkQuality.value] || '网络未知';
  });

  /**
   * 监控端学生用户列表（只展示学生，不包含老师）
   */
  const studentUsers = computed(() => {
    const users = meeting.remoteUsers.value as (IAgoraRTCRemoteUser & { userName?: string })[];
    return users.filter((u) => (teacherUid.value ? u.uid !== teacherUid.value : true));
  });

  /**
   * 根据学生人数计算宫格列数
   * - 1 个学生：全屏 (1 列)
   * - 2~4 个学生：2 列
   * - 5~9 个学生：3 列
   * - 10 个以上：4 列
   */
  const gridCols = computed(() => {
    const count = studentUsers.value.length;
    if (count <= 1) {
      return 1; // 1 个学生，全屏显示
    }
    if (count <= 4) {
      return 2; // 2~4 个学生，2 列
    }
    if (count <= 9) {
      return 3; // 5~9 个学生，3 列
    }
    return 4; // 10 个以上，4 列
  });

  /**
   * 根据学生人数与列数计算排数
   */
  const gridRows = computed(() => {
    const count = studentUsers.value.length;
    const cols = gridCols.value || 1;
    return Math.max(1, Math.ceil(count / cols));
  });

  type MeetingWsPacket = Parameters<ServerToClientEvents['message']>[0];

  /**
   * 离开监控端
   */
  async function handleLeave(): Promise<void> {
    try {
      socket.off('message', onSocketMessage);
      try {
        meetingSocket.leaveRoom(socket);
      } catch {}
      destroyMeetingSocket();
      await cleanup();
      router.back();
    } catch (error) {
      console.error('离开监控端失败:', error);
    }
  }

  /**
   * WebSocket 消息处理
   * @param packet - 消息包
   */
  function onSocketMessage(packet: MeetingWsPacket) {
    switch (packet.type) {
      case 'sev_join_room': {
        const data = packet.data;
        if (data?.roomId && !roomId.value) {
          roomId.value = data.roomId;
        }
        if (data?.classStatus) {
          classStatus.value = data.classStatus;
        }
        if (typeof data?.teacherUid === 'number') {
          teacherUid.value = data.teacherUid;
        }
        if (typeof data?.endTimestamp === 'number') {
          roomEndTimestamp.value = data.endTimestamp;
        }
        break;
      }
      case 'sev_room_end_timestamp_updated': {
        roomEndTimestamp.value = packet.data.endTimestamp;
        Toast.success('课程已续期');
        break;
      }
      case 'sev_room_will_expire': {
        if (isRenewPrompting.value) {
          break;
        }
        isRenewPrompting.value = true;
        const baseEndTimestamp = roomEndTimestamp.value || packet.data.endTimestamp;
        const minutesLeft = Math.max(1, Math.ceil(packet.data.remainingMs / 60000));
        ElMessageBox.prompt(`课程将在约 ${minutesLeft} 分钟后结束，是否续期？`, '续期', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          inputValue: '10',
          inputPattern: /^\d+$/,
          inputErrorMessage: '请输入分钟数',
        })
          .then(({ value }) => {
            const minutes = Number(value);
            if (!Number.isFinite(minutes) || minutes <= 0) {
              Toast.error('续期分钟数不正确');
              return;
            }
            const nextEndTimestamp = baseEndTimestamp + minutes * 60 * 1000;
            meetingSocket.renewRoomEndTimestamp(socket, packet.data.fromRoomId, nextEndTimestamp);
          })
          .catch(() => {})
          .finally(() => {
            isRenewPrompting.value = false;
          });
        break;
      }
      case 'sev_set_main_video': {
        selectedUserUid.value = packet.data.targetUid;
        break;
      }
      case 'sev_class_started': {
        classStatus.value = 'in_class';
        meetingSocket
          .joinRoom(socket)
          .then(async (roomData: OnJoinRoomData) => {
            roomId.value = roomData.roomId;
            shortUid.value = roomData.shortUid;
            teacherUid.value = roomData.teacherUid;
            await meeting.initAndJoin(roomData);
          })
          .catch((e: unknown) => {
            if (e instanceof Error && e.message) {
              Toast.error(e.message);
              return;
            }
            Toast.error('加入失败');
          });
        break;
      }
      case 'sev_class_ended': {
        classStatus.value = 'finished';
        leaveChannel().catch(() => {});
        break;
      }
      case 'sev_kick_user':
      case 'sev_device_conflict': {
        Toast.warning('监控端已断开，请重新进入');
        handleLeave();
        break;
      }
      default:
        break;
    }
  }

  socket.on('message', onSocketMessage);

  /**
   * 页面挂载后加入房间
   */
  onMounted(async () => {
    try {
      const roomData = await meetingSocket.joinRoom(socket);
      roomId.value = roomData.roomId;
      shortUid.value = roomData.shortUid;
      teacherUid.value = roomData.teacherUid;
      classStatus.value = roomData?.classStatus || 'not_started';
      if (classStatus.value === 'in_class') {
        await meeting.initAndJoin(roomData);
      }
    } catch (error) {
      console.error('加入监控端失败:', error);
      const errorMessage = error instanceof Error ? error.message : '加入监控端失败';
      Toast.error(errorMessage);
      router.back();
    }
  });

  /**
   * 页面卸载清理
   */
  onUnmounted(async () => {
    socket.off('message', onSocketMessage);
    await cleanup();
    try {
      meetingSocket.leaveRoom(socket);
    } catch {}
    destroyMeetingSocket();
  });
</script>

<style lang="scss" scoped>
  .page {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100vh;
    padding: 15px;
    overflow: hidden;

    .page-content {
      position: relative;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      width: 100%;
      height: 100%;
      overflow: hidden;
      background: rgba(255, 255, 255, 0.4);
      border: 1px solid rgba(255, 255, 255, 0.2);
      border-radius: 20px;
      box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
    }
  }

  .top-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 16px 24px;
  }

  .room-info {
    display: flex;
    gap: 12px;
    align-items: center;
  }

  .room-icon {
    width: 24px;
    height: 24px;
    color: #667eea;
  }

  .room-details {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }

  .room-name {
    font-size: 16px;
    font-weight: 600;
    color: #000000;
  }

  .room-id {
    font-size: 12px;
    color: #666666;
  }

  .network-status {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 8px 16px;
    background: rgba(255, 255, 255, 0.1);
    border-radius: 20px;

    &.good {
      color: #10b981;
      background: rgba(16, 185, 129, 0.1);
    }

    &.fair {
      color: #f59e0b;
      background: rgba(245, 158, 11, 0.1);
    }

    &.poor {
      color: #ef4444;
      background: rgba(239, 68, 68, 0.1);
    }
  }

  .network-icon {
    width: 18px;
    height: 18px;
  }

  .network-text {
    font-size: 13px;
    font-weight: 500;
  }

  .monitor-container {
    position: relative;
    flex: 1;
    width: 100%;
    padding: 16px;
    overflow: hidden;
  }

  .empty-students {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
  }

  .grid {
    display: grid;
    grid-template-rows: repeat(var(--grid-rows), 1fr);
    grid-template-columns: repeat(var(--grid-cols), 1fr);
    gap: 12px;
    width: 100%;
    height: 100%;
  }

  .grid-item {
    position: relative;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.85);
    border-radius: 16px;
    aspect-ratio: 16 / 9; // 设置 16:9 比例
  }

  .player-wrapper {
    width: 100%;
    height: 100%;
  }

  .player-wrapper :deep(video) {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .video-label {
    position: absolute;
    bottom: 10px;
    left: 10px;
    padding: 6px 10px;
    font-size: 12px;
    color: #ffffff;
    user-select: none;
    background: rgba(0, 0, 0, 0.55);
    border-radius: 10px;
  }
</style>
