<template>
  <div class="live-broadcast">
    <el-card>
      <template #header>
        <span>官网视频直播（WebRTC）</span>
      </template>
      <p class="status">{{ status }}</p>
      <p v-if="session" class="viewer-row">
        <el-tag type="info" effect="plain">当前观看人数：{{ viewerCount }}</el-tag>
      </p>
      <div class="form-block">
        <div class="field-row">
          <span class="field-label">画面来源</span>
          <el-radio-group v-model="captureMode" :disabled="!token || switchingCapture">
            <el-radio-button value="camera">仅摄像头</el-radio-button>
            <el-radio-button value="screen_only">仅共享屏幕</el-radio-button>
            <el-radio-button value="screen_pip">共享屏幕 + 摄像头</el-radio-button>
          </el-radio-group>
        </div>
        <div class="field-row">
          <span class="field-label">摄像头</span>
          <el-select
            v-model="selectedCameraId"
            placeholder="默认摄像头"
            clearable
            filterable
            style="width: 100%; max-width: 360px"
            :disabled="!token || switchingCapture"
          >
            <el-option label="系统默认" value="" />
            <el-option
              v-for="d in videoInputs"
              :key="d.deviceId"
              :label="d.label || `摄像头 ${d.deviceId.slice(0, 8)}…`"
              :value="d.deviceId"
            />
          </el-select>
          <el-button :disabled="!token" @click="refreshVideoDevices">刷新列表</el-button>
        </div>
        <p v-if="session" class="hint-live">
          直播中可切换三种模式；「屏幕+摄像头」下可拖动小窗，观众画面与预览一致（画布 16:9
          铺满）。共享整屏时尽量勿把本管理页选进画面，以免套娃。
        </p>
        <div v-if="session" class="field-row">
          <el-button
            type="primary"
            plain
            :disabled="!token || switchingCapture"
            :loading="switchingCapture"
            @click="applyCaptureSwitch"
          >
            应用切换（不切断直播）
          </el-button>
        </div>
      </div>
      <div class="actions">
        <el-button v-if="!session" type="primary" :disabled="!token" @click="start">开始直播</el-button>
        <el-button v-else type="danger" @click="stop">结束直播</el-button>
      </div>
      <div ref="previewWrapRef" class="preview-wrap">
        <video
          v-show="previewLayout !== 'screen_pip'"
          ref="previewMainRef"
          class="preview-main"
          playsinline
          muted
          autoplay
        ></video>
        <video
          v-show="previewLayout === 'screen_pip'"
          ref="previewScreenRef"
          class="preview-main preview-main--fill"
          playsinline
          muted
          autoplay
        ></video>
        <video
          v-show="previewLayout === 'screen_pip'"
          ref="previewCamRef"
          class="preview-pip-drag"
          :style="pipStyle"
          playsinline
          muted
          autoplay
          title="拖动调整小窗位置（观众端同步）"
          @pointerdown.prevent="onPipPointerDown"
        ></video>
      </div>
    </el-card>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { useAuthStore } from '../../stores/auth'
import { startPublishing } from '../../utils/liveWebRTC'

function liveStatusUrl() {
  const base = (import.meta.env.VITE_API_BASE || '').replace(/\/$/, '')
  return base ? `${base}/api/web/live/status` : '/api/web/live/status'
}

const authStore = useAuthStore()
const token = computed(() => authStore.getToken() || '')
const previewWrapRef = ref(null)
const previewMainRef = ref(null)
const previewScreenRef = ref(null)
const previewCamRef = ref(null)
const status = ref('就绪')
const session = ref(null)
const viewerCount = ref(0)
let viewerPollTimer = null
const captureMode = ref('camera')
const selectedCameraId = ref('')
const videoInputs = ref([])
const switchingCapture = ref(false)
/** @type {import('vue').Ref<'camera'|'screen_only'|'screen_pip'>} */
const previewLayout = ref('camera')

/** 与推流画布 1280×720 一致的归一化小窗矩形（左上 + 宽高，0~1） */
const pipNorm = ref(defaultPipNorm())

function defaultPipNorm() {
  const nw = 0.24
  const nh = 0.24
  return {
    nx: 1 - nw - 10 / 1280,
    ny: 1 - nh - 10 / 720,
    nw,
    nh
  }
}

const pipStyle = computed(() => ({
  left: `${pipNorm.value.nx * 100}%`,
  top: `${pipNorm.value.ny * 100}%`,
  width: `${pipNorm.value.nw * 100}%`,
  height: `${pipNorm.value.nh * 100}%`
}))

let pipDragging = false
let pipDragStart = { cx: 0, cy: 0, nx: 0, ny: 0 }

function clamp01(v, lo, hi) {
  return Math.min(hi, Math.max(lo, v))
}

function onPipPointerDown(e) {
  if (previewLayout.value !== 'screen_pip') return
  pipDragging = true
  pipDragStart.cx = e.clientX
  pipDragStart.cy = e.clientY
  pipDragStart.nx = pipNorm.value.nx
  pipDragStart.ny = pipNorm.value.ny
  try {
    e.target.setPointerCapture(e.pointerId)
  } catch (_) {}
  window.addEventListener('pointermove', onPipPointerMove)
  window.addEventListener('pointerup', onPipPointerUp, { once: true })
  window.addEventListener('pointercancel', onPipPointerUp, { once: true })
}

function onPipPointerMove(e) {
  if (!pipDragging || !previewWrapRef.value) return
  const rect = previewWrapRef.value.getBoundingClientRect()
  if (rect.width < 1 || rect.height < 1) return
  const dx = (e.clientX - pipDragStart.cx) / rect.width
  const dy = (e.clientY - pipDragStart.cy) / rect.height
  const { nw, nh } = pipNorm.value
  pipNorm.value = {
    ...pipNorm.value,
    nx: clamp01(pipDragStart.nx + dx, 0, 1 - nw),
    ny: clamp01(pipDragStart.ny + dy, 0, 1 - nh)
  }
}

function onPipPointerUp() {
  pipDragging = false
  window.removeEventListener('pointermove', onPipPointerMove)
}

async function refreshVideoDevices() {
  try {
    if (!navigator.mediaDevices?.enumerateDevices) return
    const list = await navigator.mediaDevices.enumerateDevices()
    videoInputs.value = list.filter((d) => d.kind === 'videoinput')
  } catch (_) {}
}

function applyPreview(payload) {
  const { layout, main, screen, cam } = payload || {}
  previewLayout.value = layout || 'camera'
  if (previewLayout.value === 'screen_pip') {
    if (previewMainRef.value) previewMainRef.value.srcObject = null
    if (previewScreenRef.value) previewScreenRef.value.srcObject = screen || null
    if (previewCamRef.value) previewCamRef.value.srcObject = cam || null
  } else {
    if (previewScreenRef.value) previewScreenRef.value.srcObject = null
    if (previewCamRef.value) previewCamRef.value.srcObject = null
    if (previewMainRef.value) previewMainRef.value.srcObject = main || null
  }
}

function clearPreview() {
  previewLayout.value = 'camera'
  pipNorm.value = defaultPipNorm()
  if (previewMainRef.value) previewMainRef.value.srcObject = null
  if (previewScreenRef.value) previewScreenRef.value.srcObject = null
  if (previewCamRef.value) previewCamRef.value.srcObject = null
}

async function fetchViewerCount() {
  try {
    const r = await fetch(liveStatusUrl(), { cache: 'no-store' })
    if (!r.ok) return
    const j = await r.json()
    if (typeof j.viewers === 'number') viewerCount.value = j.viewers
  } catch (_) {}
}

function startViewerPoll() {
  stopViewerPoll()
  fetchViewerCount()
  viewerPollTimer = window.setInterval(fetchViewerCount, 2500)
}

function stopViewerPoll() {
  if (viewerPollTimer != null) {
    clearInterval(viewerPollTimer)
    viewerPollTimer = null
  }
}

watch(session, (s) => {
  if (s) {
    startViewerPoll()
  } else {
    stopViewerPoll()
    viewerCount.value = 0
  }
})

async function applyCaptureSwitch() {
  if (!session.value?.switchMode) return
  switchingCapture.value = true
  try {
    await session.value.switchMode(captureMode.value, selectedCameraId.value || '')
  } finally {
    switchingCapture.value = false
  }
}

function start() {
  if (!token.value) {
    status.value = '请先登录'
    return
  }
  status.value = '正在连接…'
  const { stop, switchMode } = startPublishing({
    token: token.value,
    captureMode: captureMode.value,
    videoDeviceId: selectedCameraId.value || '',
    onStatus: (s) => {
      status.value = s
    },
    onLocalStream: applyPreview,
    onActiveModeChange: (m) => {
      captureMode.value = m
    },
    getPipRect: () => ({ ...pipNorm.value })
  })
  session.value = { stop, switchMode }
}

function stop() {
  session.value?.stop()
  session.value = null
  clearPreview()
  status.value = '已停止'
}

function onBeforeUnload() {
  session.value?.stop()
}

onMounted(() => {
  document.title = '视频直播开播 - 管理后台'
  window.addEventListener('beforeunload', onBeforeUnload)
  refreshVideoDevices()
})

onUnmounted(() => {
  stopViewerPoll()
  window.removeEventListener('beforeunload', onBeforeUnload)
  window.removeEventListener('pointermove', onPipPointerMove)
})

onBeforeRouteLeave(() => {
  stop()
})
</script>

<style scoped>
.live-broadcast {
  max-width: min(1200px, 100%);
}
.status {
  color: #409eff;
  margin-bottom: 14px;
  min-height: 1.5em;
}
.viewer-row {
  margin: -6px 0 14px;
}
.form-block {
  margin-bottom: 16px;
}
.field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}
.field-label {
  font-size: 14px;
  color: #606266;
  min-width: 72px;
}
.hint-live {
  margin: 0 0 10px;
  font-size: 13px;
  line-height: 1.5;
  color: #909399;
  max-width: 720px;
}
.actions {
  margin-bottom: 16px;
}
.preview-wrap {
  position: relative;
  width: 100%;
  max-width: 1100px;
}
.preview-main {
  display: block;
  width: 100%;
  min-height: 320px;
  max-height: min(85vh, 900px);
  border-radius: 8px;
  background: #000;
  object-fit: contain;
  aspect-ratio: 16 / 9;
}
.preview-main--fill {
  object-fit: fill;
}
.preview-pip-drag {
  position: absolute;
  box-sizing: border-box;
  border-radius: 8px;
  border: 3px solid #409eff;
  background: #000;
  object-fit: cover;
  cursor: grab;
  touch-action: none;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
  z-index: 2;
}
.preview-pip-drag:active {
  cursor: grabbing;
}
</style>
