<script setup lang="ts">
import StreamVideo from './StreamVideo.vue'
import { RoomWsClient } from './room-ws-client'
import type { Transport } from 'mediasoup-client/lib/types'

const roomId = ref('classroom')
const displayName = ref('学员')
const wsHost = ref('127.0.0.1')
const wsPort = ref(30443)
const status = ref('未连接')
const teacherStream = ref<MediaStream | null>(null)
const localPreview = ref<MediaStream | null>(null)

let client: RoomWsClient | null = null
let unsub: (() => void) | null = null
let sendTransport: Transport | null = null
let recvTransport: Transport | null = null
let teacherPeerId: string | null = null
const consumed = new Set<string>()
const pendingTeacherProducerIds: string[] = []

function wsUrl() {
  return `ws://${wsHost.value}:${wsPort.value}`
}

function resetAfterRoomGone() {
  teacherStream.value = null
  recvTransport = null
  sendTransport = null
  teacherPeerId = null
  consumed.clear()
  pendingTeacherProducerIds.length = 0
  localPreview.value?.getTracks().forEach(t => t.stop())
  localPreview.value = null
  unsub?.()
  unsub = null
  client?.close()
  client = null
}

onMounted(() => {
  try {
    const q = new URLSearchParams(window.location.search)
    const hp = q.get('port')
    const hh = q.get('host')
    if (hh?.trim()) wsHost.value = hh.trim()
    if (hp) {
      const n = Number(hp)
      if (Number.isFinite(n) && n > 0) wsPort.value = n
    }
  } catch {
    /* ignore */
  }
})

async function consumeTeacher(recv: Transport, producerId: string) {
  if (consumed.has(producerId)) return
  if (!client) return
  try {
    const consumer = await client.consume(recv, producerId)
    consumed.add(producerId)
    teacherStream.value = new MediaStream([consumer.track])
    consumer.on('producerclose', () => {
      consumed.delete(producerId)
      teacherStream.value = null
    })
  } catch (e) {
    status.value = `拉取教师画面失败：${e instanceof Error ? e.message : String(e)}`
  }
}

async function joinClass() {
  teacherStream.value = null
  localPreview.value?.getTracks().forEach(t => t.stop())
  localPreview.value = null
  consumed.clear()
  pendingTeacherProducerIds.length = 0
  unsub?.()
  client?.close()
  client = null
  sendTransport = null
  recvTransport = null
  teacherPeerId = null

  status.value = '连接信令…'
  client = new RoomWsClient(wsUrl())
  client.onDisconnect(() => {
    status.value = '与教室断开，请重新点击「加入课堂」'
    resetAfterRoomGone()
  })

  try {
    await client.connect()

    unsub = client.subscribe(msg => {
      if (msg.type === 'newProducer' && msg.data) {
        const d = msg.data as {
          peerId: string
          producerId: string
          appData?: { role?: string }
        }
        if (d.appData?.role === 'teacher') {
          teacherPeerId = d.peerId
          if (recvTransport) void consumeTeacher(recvTransport, d.producerId)
          else if (!pendingTeacherProducerIds.includes(d.producerId)) {
            pendingTeacherProducerIds.push(d.producerId)
          }
        }
      }
      if (msg.type === 'peerLeft' && msg.data) {
        const d = msg.data as { peerId: string; role?: string }
        const teacherLeft =
          d.role === 'teacher' || (teacherPeerId !== null && d.peerId === teacherPeerId)
        if (teacherLeft) {
          status.value = '未连接（教师端已断开，请重新点击「加入课堂」）'
          resetAfterRoomGone()
        }
      }
      if (msg.type === 'peerJoined' && msg.data) {
        const d = msg.data as {
          peerId: string
          role?: string
          self?: boolean
        }
        if (!d.self && d.role === 'teacher') {
          teacherPeerId = d.peerId
        }
      }
      if (msg.type === 'producerClosed' && msg.data) {
        const d = msg.data as { producerId: string }
        consumed.delete(d.producerId)
        const i = pendingTeacherProducerIds.indexOf(d.producerId)
        if (i !== -1) pendingTeacherProducerIds.splice(i, 1)
        teacherStream.value = null
        status.value = '教师已停止共享屏幕；恢复后请重新加入或等待教师再次推流'
      }
    })

    const joined = await client.join(roomId.value, 'student', displayName.value)
    await client.loadDevice(joined.rtpCapabilities)

    recvTransport = await client.createRecvTransport()
    for (const pid of pendingTeacherProducerIds.splice(0)) {
      await consumeTeacher(recvTransport, pid)
    }
    for (const ep of joined.existingProducers) {
      if (ep.appData?.role === 'teacher') {
        teacherPeerId = ep.peerId
        await consumeTeacher(recvTransport, ep.producerId)
      }
    }

    status.value =
      '已加入课堂。请点击「共享我的屏幕」将画面推送给教师（手机浏览器上必须点一次该按钮）。'
  } catch (e) {
    unsub?.()
    unsub = null
    client?.close()
    client = null
    recvTransport = null
    sendTransport = null
    status.value = `加入失败：${e instanceof Error ? e.message : String(e)}`
  }
}

async function pushScreenToTeacher(): Promise<boolean> {
  if (!client) {
    status.value = '请先加入课堂'
    return false
  }
  if (sendTransport) {
    status.value = '已在向教师推送屏幕'
    return true
  }
  if (!window.isSecureContext) {
    status.value =
      '当前页面不是安全上下文（例如用 http://局域网IP 打开）。屏幕共享会被浏览器禁用，请改用 HTTPS 部署，或使用 https://localhost / 本机调试。'
    return false
  }
  const gdm = navigator.mediaDevices?.getDisplayMedia
  if (typeof gdm !== 'function') {
    status.value =
      '当前浏览器不支持屏幕共享（无 getDisplayMedia）。请换用 Chrome 等浏览器，或确认系统版本。'
    return false
  }
  status.value = '正在采集屏幕以推送给教师…'
  let stream: MediaStream
  try {
    stream = await gdm.call(navigator.mediaDevices, {
      video: true,
      audio: false,
    })
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e)
    status.value = `未开始共享（${msg}）。请直接点击「共享我的屏幕」（不要依赖加入课堂后自动共享）；若通过 http 访问局域网地址，需改为 HTTPS。`
    return false
  }
  localPreview.value = stream
  stream.getVideoTracks()[0].addEventListener('ended', () => {
    sendTransport?.close()
    sendTransport = null
    localPreview.value?.getTracks().forEach(t => t.stop())
    localPreview.value = null
    status.value = '本机屏幕共享已结束；仍与课堂保持连接，可再次点击「共享我的屏幕」推送'
  })
  sendTransport = await client.createSendTransport()
  await sendTransport.produce({
    track: stream.getVideoTracks()[0],
    appData: { source: 'screen' },
  })
  status.value = '已连接，正在向教师推送本机屏幕'
  return true
}

async function shareMyScreen() {
  await pushScreenToTeacher()
}

async function leave() {
  resetAfterRoomGone()
  status.value = '已离开'
}

onUnmounted(() => {
  void leave()
})
</script>

<template>
  <div class="live-student px-w">
    <div class="app">
      <header class="bar">
        <h1>学员端</h1>
        <div class="row">
          <label>房间 <input v-model="roomId" /></label>
          <label>显示名 <input v-model="displayName" /></label>
          <label>WS 主机 <input v-model="wsHost" placeholder="教师机 IP" /></label>
          <label>
            端口
            <input
              v-model.number="wsPort"
              type="number"
              title="须与教师端界面上的端口一致（教师端可能为随机端口）"
            />
          </label>
          <button type="button" class="primary" @click="joinClass">加入课堂</button>
          <button type="button" @click="shareMyScreen">共享我的屏幕</button>
          <button type="button" @click="leave">离开</button>
        </div>
        <p class="status">{{ status }}</p>
      </header>
      <section class="main">
        <h2>教师屏幕</h2>
        <StreamVideo v-if="teacherStream" :stream="teacherStream" />
        <p v-else class="muted">等待教师推流…</p>
      </section>
      <section class="pip">
        <h2>我的屏幕（预览）</h2>
        <StreamVideo v-if="localPreview" :stream="localPreview" />
        <p v-else class="muted">未共享</p>
      </section>
    </div>
  </div>
</template>

<style scoped>
.live-student {
  min-height: 100vh;
}
.app {
  font-family: system-ui, sans-serif;
  padding: 12px;
  color: #e8e8ef;
  background: #0f1419;
  min-height: 100vh;
}
.bar h1 {
  margin: 0 0 8px;
  font-size: 1.25rem;
}
.row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.row input {
  width: 130px;
}
button {
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid #444;
  background: #252b36;
  color: inherit;
  cursor: pointer;
}
button.primary {
  background: #2f9e6d;
  border-color: #2f9e6d;
}
.status {
  margin: 8px 0 0;
  opacity: 0.85;
  font-size: 0.9rem;
}
.main,
.pip {
  margin-top: 16px;
}
.main h2,
.pip h2 {
  font-size: 1rem;
  margin-bottom: 8px;
}
.muted {
  opacity: 0.6;
}
.pip .vid {
  max-height: 200px;
}
</style>
