<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { buildVideoFrameUrl, fetchVideoStatus } from '@/lib/api'
import type { VideoStatus } from '@/types'

const props = defineProps<{
  video: VideoStatus | null
}>()

const STATUS_REFRESH_MS = 300

const liveVideo = ref<VideoStatus | null>(props.video)
const frameUrl = ref(buildVideoFrameUrl(0))
const displayVideo = computed(() => liveVideo.value ?? props.video)
const currentFps = computed(() => displayVideo.value?.fps ?? 30)
const canRequestFrames = computed(() => displayVideo.value?.available === true)
const modeLabel = computed(() => {
  if (!displayVideo.value) {
    return '正在获取视频状态'
  }
  if (displayVideo.value.source_mode === 'omnisocket-jpeg-live') {
    return `${displayVideo.value.fps} FPS 实时接收`
  }
  if (displayVideo.value.source_mode === 'omnisocket-waiting') {
    return '未实时获取真实值'
  }
  return `${displayVideo.value.fps} FPS`
})
const placeholderText = computed(() => {
  if (!displayVideo.value) {
    return '正在获取视频状态...'
  }
  return '未实时获取真实值'
})
const latencyLabels = computed(() => {
  const sampleWindowSize = displayVideo.value?.timing?.sample_window_size ?? 10
  const samples = displayVideo.value?.timing?.delta_samples_ms ?? []
  return Array.from({ length: sampleWindowSize }, (_, index) => samples[index] ?? null)
})
const timingHeadline = computed(() => {
  const latest = displayVideo.value?.timing?.latest_delta_ms
  if (latest == null) {
    return '等待帧尾时间'
  }
  return `最新 ${latest.toFixed(1)} ms`
})
const timingHint = computed(() => {
  const timing = displayVideo.value?.timing
  if (!timing?.available) {
    return '当前还没有从 JPEG 结尾后的尾字节里解析到时间戳，标签会在收到有效帧尾时间后自动填充。'
  }

  const unitText = timing.timestamp_unit ? `，单位按 ${timing.timestamp_unit}` : ''
  const endiannessText = timing.timestamp_endianness
    ? `，字节序按 ${timing.timestamp_endianness}`
    : ''
  return `最近 ${timing.sample_window_size} 个差值样本，面板按 ${STATUS_REFRESH_MS} ms 刷新一组${unitText}${endiannessText}。`
})

let frameTimer: number | null = null
let statusTimer: number | null = null
let frameKey = 0
let statusRequestPending = false

async function refreshStatus() {
  if (statusRequestPending) {
    return
  }

  statusRequestPending = true
  try {
    liveVideo.value = await fetchVideoStatus()
  } catch {
    // 保持当前已显示状态，避免短暂请求失败把面板内容清空。
  } finally {
    statusRequestPending = false
  }
}

function refreshFrame() {
  if (!canRequestFrames.value) {
    return
  }
  frameKey += 1
  frameUrl.value = buildVideoFrameUrl(frameKey)
}

function startFrameLoop() {
  if (frameTimer != null) {
    window.clearInterval(frameTimer)
    frameTimer = null
  }

  if (!canRequestFrames.value) {
    return
  }

  refreshFrame()
  const intervalMs = Math.max(33, Math.round(1000 / currentFps.value))
  frameTimer = window.setInterval(() => {
    refreshFrame()
  }, intervalMs)
}

function startStatusLoop() {
  if (statusTimer != null) {
    window.clearInterval(statusTimer)
    statusTimer = null
  }

  void refreshStatus()
  statusTimer = window.setInterval(() => {
    void refreshStatus()
  }, STATUS_REFRESH_MS)
}

onMounted(() => {
  startStatusLoop()
  startFrameLoop()
})

onUnmounted(() => {
  if (frameTimer != null) {
    window.clearInterval(frameTimer)
  }
  if (statusTimer != null) {
    window.clearInterval(statusTimer)
  }
})

watch(
  () => props.video,
  (nextVideo) => {
    liveVideo.value = nextVideo
  },
  { immediate: true },
)

watch([currentFps, canRequestFrames], () => {
  startFrameLoop()
})
</script>

<template>
  <section class="panel video-panel">
    <div class="panel-head">
      <div>
        <p class="eyebrow">Video</p>
        <h2>JPEG 视频流</h2>
      </div>
      <span class="badge" :class="{ bad: !displayVideo?.available }">
        {{ displayVideo?.source_mode ?? 'loading' }}
      </span>
    </div>

    <div class="video-shell">
      <img
        v-if="canRequestFrames"
        class="video-frame"
        :src="frameUrl"
        alt="Robot jpeg frame stream"
      />
      <div v-else class="video-placeholder">
        {{ placeholderText }}
      </div>
    </div>

    <div class="stats">
      <div class="stat-card">
        <span>帧源</span>
        <strong>{{ displayVideo?.frame_count ?? '--' }} 张 JPEG</strong>
      </div>
      <div class="stat-card">
        <span>当前模式</span>
        <strong>{{ modeLabel }}</strong>
      </div>
    </div>

    <div class="timing-panel">
      <div class="timing-head">
        <span>帧尾时间差</span>
        <strong>{{ timingHeadline }}</strong>
      </div>
      <div class="timing-grid">
        <span
          v-for="(sample, index) in latencyLabels"
          :key="index"
          class="timing-label"
          :class="{ empty: sample == null }"
        >
          {{ sample == null ? '--' : `${sample.toFixed(1)} ms` }}
        </span>
      </div>
      <p class="hint subtle">
        {{ timingHint }}
      </p>
    </div>

    <p class="hint">
      这里只有在后端已经收到 OmniSocket 的真实 JPEG 帧时，才会开始逐帧请求并显示画面。
      如果当前没有真实帧，页面会保持占位提示，不再回退测试视频流。
    </p>

    <p class="hint subtle">
      当前帧源状态：{{ displayVideo?.source_detail ?? '暂无' }}
    </p>
  </section>
</template>

<style scoped>
.video-panel {
  display: grid;
  gap: 16px;
}

.panel-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: start;
}

.eyebrow {
  margin: 0 0 4px;
  color: #5b7aff;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  font-weight: 700;
}

h2 {
  margin: 0;
  font-size: 24px;
}

.badge {
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(40, 199, 111, 0.16);
  color: #63e6a9;
  font-size: 12px;
  font-weight: 700;
}

.badge.bad {
  background: rgba(255, 107, 107, 0.18);
  color: #ffb4b4;
}

.video-shell {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  border: 1px solid rgba(133, 147, 169, 0.28);
  background: linear-gradient(180deg, #09111f 0%, #050812 100%);
}

.video-frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #02050d;
}

.video-placeholder {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 24px;
  color: #a8b4ce;
  text-align: center;
  line-height: 1.7;
  background:
    radial-gradient(circle at top, rgba(91, 122, 255, 0.14), transparent 42%),
    #02050d;
}

.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.stat-card {
  padding: 14px;
  border-radius: 16px;
  background: rgba(7, 14, 26, 0.78);
  border: 1px solid rgba(133, 147, 169, 0.2);
}

.stat-card span {
  display: block;
  margin-bottom: 8px;
  color: #8d99b3;
  font-size: 12px;
}

.stat-card strong {
  font-size: 18px;
}

.timing-panel {
  display: grid;
  gap: 12px;
  padding: 14px;
  border-radius: 18px;
  background: rgba(7, 14, 26, 0.88);
  border: 1px solid rgba(133, 147, 169, 0.18);
}

.timing-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  color: #cfd7e6;
}

.timing-head span {
  color: #8d99b3;
  font-size: 12px;
}

.timing-head strong {
  font-size: 16px;
}

.timing-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
}

.timing-label {
  display: grid;
  place-items: center;
  min-height: 40px;
  padding: 8px 10px;
  border-radius: 12px;
  background: rgba(91, 122, 255, 0.12);
  border: 1px solid rgba(91, 122, 255, 0.28);
  color: #dce4ff;
  font-size: 12px;
  font-weight: 700;
}

.timing-label.empty {
  background: rgba(133, 147, 169, 0.08);
  border-color: rgba(133, 147, 169, 0.18);
  color: #7e8aa5;
}

.hint {
  margin: 0;
  color: #8d99b3;
  line-height: 1.65;
}

.hint.subtle {
  font-size: 13px;
}

@media (max-width: 720px) {
  .stats {
    grid-template-columns: 1fr;
  }

  .timing-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
</style>
