<template>
  <div class="realsense-viewer">
    <el-row type="flex" class="height-full">
      <el-col :span="4" class="control-panel" style="border-right: 10px solid rgb(40, 50, 60);">
        <div class="controls">
          <el-form :model="form">
            <el-form-item label="深度图像">
              <el-switch v-model="form.stereoModule"></el-switch>
            </el-form-item>
            <el-form-item label="彩色图像">
              <el-switch v-model="form.rgbCamera"></el-switch>
            </el-form-item>
          </el-form>
        </div>
      </el-col>
      <el-col :span="20" class="video-panel" v-if="form.stereoModule || form.rgbCamera">
        <div class="stream-container" :class="{ dual: form.stereoModule && form.rgbCamera }">
          <div v-show="form.stereoModule" class="stream-item">
            <div class="stream-wrapper">
              <video ref="depthVideo" autoplay muted playsinline></video>
              <span class="stream-state">{{ statusText(streamStatus.getDepthImageStream) }}</span>
            </div>
          </div>
          <div v-show="form.rgbCamera" class="stream-item">
            <div class="stream-wrapper">
              <video ref="colorVideo" autoplay muted playsinline></video>
              <span class="stream-state">{{ statusText(streamStatus.getRGBImageStream) }}</span>
            </div>
          </div>
        </div>
      </el-col>
      <el-col v-else :span="20" style="align-self: center;">
        <el-empty description="请选择模式" />
      </el-col>
    </el-row>
  </div>
</template>

<script setup>
import { inject, reactive, toRefs, watch, onUnmounted, ref, nextTick } from 'vue';
import { getRegister } from "@/api/device/register";
import { getRgbStreamUrl } from "@/api/device/camera";
import { useRoute } from "vue-router";

const props = defineProps({
  initialDeviceId: {
    type: String,
    default: null,
  },
  initialTerminalId: {
    type: String,
    default: null,
  },
});

const socket = inject('ws');
const route = useRoute();
const data = reactive({
  form: {
    stereoModule: false,
    rgbCamera: false,
  },
  terminalId: null,
  cameraId: null,
  type: 'camera',
  callbacks: {},
});
const { form } = toRefs(data);
const depthVideo = ref(null);
const colorVideo = ref(null);
const streamStatus = reactive({
  getRGBImageStream: 'idle',
  getDepthImageStream: 'idle',
});
const mediaPlayers = new Map();
const retryTimers = new Map();
let rgbStreamGeneration = 0;
let rgbStreamRetryTimer = null;
let rgbStreamAbortController = null;
const MAX_QUEUE_BYTES = 2 * 1024 * 1024;
const STARTUP_BUFFER_SECONDS = 0.35;
const TARGET_LIVE_LATENCY_SECONDS = 0.45;
const MAX_PLAYED_BUFFER_SECONDS = 2;
const PLAYBACK_SYNC_INTERVAL_MS = 200;

const statusText = (status) => ({
  connecting: '连接中',
  live: '实时',
  retrying: '重连中',
  error: '播放异常',
}[status] || '未连接');

function getVideoRef(method) {
  return method === 'getRGBImageStream' ? colorVideo : depthVideo;
}

async function startDirectRgbStream() {
  if (!data.terminalId || !data.cameraId || !colorVideo.value) return;
  rgbStreamAbortController?.abort();
  rgbStreamAbortController = null;
  const generation = ++rgbStreamGeneration;
  clearTimeout(rgbStreamRetryTimer);
  rgbStreamRetryTimer = null;
  streamStatus.getRGBImageStream = 'connecting';
  try {
    const response = await getRgbStreamUrl(data.terminalId, data.cameraId);
    if (generation !== rgbStreamGeneration || !data.form.rgbCamera) return;
    const relativeUrl = response.data;
    const baseUrl = import.meta.env.VITE_APP_BASE_API.replace(/\/$/, '');
    const video = colorVideo.value;
    if (!video) return;
    const player = createMediaPlayer(
      'getRGBImageStream',
      video,
      'video/mp4; codecs="avc1.42C01E"'
    );
    rgbStreamAbortController = new AbortController();
    consumeDirectRgbStream(
      `${baseUrl}${relativeUrl}`,
      generation,
      player,
      rgbStreamAbortController.signal
    );
  } catch (error) {
    if (generation === rgbStreamGeneration && data.form.rgbCamera) {
      console.error('Failed to open the direct RGB video stream:', error);
      scheduleDirectRgbRestart();
    }
  }
}

async function consumeDirectRgbStream(url, generation, player, signal) {
  try {
    const response = await fetch(url, { signal, cache: 'no-store' });
    if (!response.ok || !response.body) {
      throw new Error(`RGB video stream request failed: HTTP ${response.status}`);
    }
    const reader = response.body.getReader();
    while (generation === rgbStreamGeneration && data.form.rgbCamera) {
      const { done, value } = await reader.read();
      if (done) break;
      if (!value?.byteLength || player.destroyed) continue;
      const chunk = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
      player.queue.push(chunk);
      player.queueBytes += chunk.byteLength;
      if (player.queueBytes > MAX_QUEUE_BYTES) {
        throw new Error(`RGB video stream queue exceeded ${MAX_QUEUE_BYTES} bytes`);
      }
      pumpMediaQueue(player);
    }
    if (!signal.aborted && generation === rgbStreamGeneration && data.form.rgbCamera) {
      scheduleDirectRgbRestart();
    }
  } catch (error) {
    if (error?.name !== 'AbortError' && generation === rgbStreamGeneration && data.form.rgbCamera) {
      console.error('Direct RGB video stream interrupted:', error);
      scheduleDirectRgbRestart();
    }
  }
}

function stopDirectRgbStream() {
  rgbStreamGeneration++;
  clearTimeout(rgbStreamRetryTimer);
  rgbStreamRetryTimer = null;
  rgbStreamAbortController?.abort();
  rgbStreamAbortController = null;
  destroyMediaPlayer('getRGBImageStream');
  streamStatus.getRGBImageStream = 'idle';
}

function scheduleDirectRgbRestart() {
  if (rgbStreamRetryTimer || !data.form.rgbCamera) return;
  rgbStreamAbortController?.abort();
  rgbStreamAbortController = null;
  destroyMediaPlayer('getRGBImageStream');
  streamStatus.getRGBImageStream = 'retrying';
  rgbStreamRetryTimer = setTimeout(() => {
    rgbStreamRetryTimer = null;
    startDirectRgbStream();
  }, 1000);
}

function createMediaPlayer(method, video, mimeType) {
  if (!window.MediaSource || !MediaSource.isTypeSupported(mimeType)) {
    streamStatus[method] = 'error';
    throw new Error(`浏览器不支持媒体格式: ${mimeType}`);
  }

  destroyMediaPlayer(method);
  const mediaSource = new MediaSource();
  const objectUrl = URL.createObjectURL(mediaSource);
  const state = {
    method,
    video,
    mimeType,
    mediaSource,
    sourceBuffer: null,
    objectUrl,
    queue: [],
    queueBytes: 0,
    lastSequence: 0,
    started: false,
    syncTimer: null,
    destroyed: false,
  };
  mediaPlayers.set(method, state);
  video.src = objectUrl;

  mediaSource.addEventListener('sourceopen', () => {
    if (state.destroyed || state.sourceBuffer) return;
    try {
      const sourceBuffer = mediaSource.addSourceBuffer(mimeType);
      state.sourceBuffer = sourceBuffer;
      sourceBuffer.addEventListener('updateend', () => pumpMediaQueue(state));
      sourceBuffer.addEventListener('error', () => restartMediaStream(method));
      state.syncTimer = window.setInterval(() => maintainLivePlayback(state), PLAYBACK_SYNC_INTERVAL_MS);
      pumpMediaQueue(state);
    } catch (error) {
      console.error('创建视频缓冲区失败:', error);
      restartMediaStream(method);
    }
  }, { once: true });
  return state;
}

function appendMediaPacket(method, packet) {
  const video = getVideoRef(method).value;
  if (!video) return;
  let state = mediaPlayers.get(method);
  if (!state || state.mimeType !== packet.mimeType || state.video !== video) {
    state = createMediaPlayer(method, video, packet.mimeType);
  }
  if (state.lastSequence && packet.sequence !== state.lastSequence + 1) {
    console.warn(`视频片段序号中断: ${state.lastSequence} -> ${packet.sequence}`);
    scheduleRestart(method);
    return;
  }
  state.lastSequence = packet.sequence;
  state.queue.push(packet.data);
  state.queueBytes += packet.data.byteLength;
  if (state.queueBytes > MAX_QUEUE_BYTES) {
    console.warn(`视频缓冲积压超过${MAX_QUEUE_BYTES / 1024 / 1024}MB，重新建立实时流`);
    scheduleRestart(method);
    return;
  }
  pumpMediaQueue(state);
}

function pumpMediaQueue(state) {
  const sourceBuffer = state.sourceBuffer;
  if (state.destroyed || !sourceBuffer || sourceBuffer.updating) return;
  try {
    maintainLivePlayback(state);
    const buffered = sourceBuffer.buffered;
    if (buffered.length > 0 && state.started) {
      const oldestBufferedTime = buffered.start(0);
      const removeEnd = state.video.currentTime - 1;
      if (removeEnd - oldestBufferedTime > MAX_PLAYED_BUFFER_SECONDS) {
        sourceBuffer.remove(oldestBufferedTime, removeEnd);
        return;
      }
    }
    if (state.queue.length === 0) return;
    const chunk = state.queue.shift();
    state.queueBytes -= chunk.byteLength;
    sourceBuffer.appendBuffer(chunk);
    streamStatus[state.method] = 'live';
  } catch (error) {
    console.error('追加视频片段失败:', error);
    restartMediaStream(state.method);
  }
}

function restartMediaStream(method) {
  if (method === 'getRGBImageStream') {
    scheduleDirectRgbRestart();
  } else {
    scheduleRestart(method);
  }
}

function maintainLivePlayback(state) {
  const { sourceBuffer, video } = state;
  if (state.destroyed || !sourceBuffer || sourceBuffer.updating || sourceBuffer.buffered.length === 0) return;

  const buffered = sourceBuffer.buffered;
  const rangeIndex = buffered.length - 1;
  const liveStart = buffered.start(rangeIndex);
  const liveEnd = buffered.end(rangeIndex);
  const bufferedDuration = liveEnd - liveStart;

  if (!state.started) {
    if (bufferedDuration < STARTUP_BUFFER_SECONDS) return;
    video.currentTime = Math.max(liveStart, liveEnd - TARGET_LIVE_LATENCY_SECONDS);
    video.playbackRate = 1;
    state.started = true;
    video.play().catch(() => {});
    return;
  }

  if (video.paused) {
    video.play().catch(() => {});
  }
  if (video.seeking) return;

  const latency = liveEnd - video.currentTime;
  let playbackRate = 1;
  if (latency > 1.4) {
    playbackRate = 1.2;
  } else if (latency > 1) {
    playbackRate = 1.12;
  } else if (latency > 0.7) {
    playbackRate = 1.05;
  } else if (latency < 0.28) {
    playbackRate = 0.98;
  }
  if (Math.abs(video.playbackRate - playbackRate) > 0.001) {
    video.playbackRate = playbackRate;
  }
}

function destroyMediaPlayer(method) {
  const state = mediaPlayers.get(method);
  if (!state) return;
  state.destroyed = true;
  state.queue = [];
  state.queueBytes = 0;
  if (state.syncTimer) {
    clearInterval(state.syncTimer);
    state.syncTimer = null;
  }
  const video = state.video;
  if (video) {
    video.playbackRate = 1;
    video.pause();
    video.removeAttribute('src');
    video.load();
  }
  URL.revokeObjectURL(state.objectUrl);
  mediaPlayers.delete(method);
}

function channelFor(method) {
  return `edgeCameraServiceImpl/${method}/${data.terminalId}/${data.cameraId}`;
}

async function setSubscription(enabled, method, force = false) {
  if (!socket || !data.terminalId || !data.cameraId) return;
  await nextTick();
  const channel = channelFor(method);

  if (enabled) {
    if (data.callbacks[channel] && !force) return;
    streamStatus[method] = 'connecting';
    if (!data.callbacks[channel]) {
      const callback = (payload) => {
        if (payload?.media) {
          appendMediaPacket(method, payload);
        } else if (payload === '500') {
          scheduleRestart(method);
        }
      };
      // 先注册前端回调，再通知后端启动，避免丢失初始化片段。
      socket.on(channel, callback);
      data.callbacks[channel] = callback;
    }
  } else {
    clearTimeout(retryTimers.get(method));
    retryTimers.delete(method);
  }

  socket.send({
    type: 'channel_subscription',
    action: enabled ? 'subscribe' : 'unsubscribe',
    channel,
  });
  if (!enabled) {
    if (data.callbacks[channel]) {
      socket.off(channel, data.callbacks[channel]);
      delete data.callbacks[channel];
    }
    destroyMediaPlayer(method);
    streamStatus[method] = 'idle';
  }
}

function scheduleRestart(method) {
  if (retryTimers.has(method) || !data.form[method === 'getRGBImageStream' ? 'rgbCamera' : 'stereoModule']) return;
  streamStatus[method] = 'retrying';
  destroyMediaPlayer(method);
  const timer = setTimeout(async () => {
    retryTimers.delete(method);
    await setSubscription(false, method);
    await new Promise(resolve => setTimeout(resolve, 500));
    if (data.form[method === 'getRGBImageStream' ? 'rgbCamera' : 'stereoModule']) {
      await setSubscription(true, method);
    }
  }, 1000);
  retryTimers.set(method, timer);
}

function handleSocketOpen() {
  if (data.form.stereoModule && data.terminalId && data.cameraId) {
    destroyMediaPlayer('getDepthImageStream');
    setSubscription(true, 'getDepthImageStream', true);
  }
}

function handleSocketClose() {
  destroyMediaPlayer('getDepthImageStream');
  if (data.form.stereoModule) {
    streamStatus.getDepthImageStream = 'retrying';
  }
}

socket?.on('open', handleSocketOpen);
socket?.on('close', handleSocketClose);

watch(() => data.form.stereoModule, async (newVal) => {
  if (!data.terminalId || !data.cameraId) return;
  await setSubscription(newVal, 'getDepthImageStream');
});

watch(() => data.form.rgbCamera, async (newVal) => {
  if (!data.terminalId || !data.cameraId) return;
  if (newVal) {
    await nextTick();
    await startDirectRgbStream();
  } else {
    stopDirectRgbStream();
  }
});

watch(
  () => props.initialDeviceId || route.path.split('/')[3],
  async (deviceId) => {
    if (!deviceId) return;
    try {
      const response = await getRegister(deviceId);
      data.cameraId = response.data.deviceCode;
      data.terminalId = props.initialTerminalId || response.data.idDeDeviceTerminalConfig;
      if (data.form.stereoModule) await setSubscription(true, 'getDepthImageStream');
      if (data.form.rgbCamera) await startDirectRgbStream();
    } catch (error) {
      console.error('获取摄像头注册信息失败:', error);
    }
  },
  { immediate: true }
);

onUnmounted(() => {
  socket?.off('open', handleSocketOpen);
  socket?.off('close', handleSocketClose);
  stopDirectRgbStream();
  ['getDepthImageStream'].forEach(method => {
    clearTimeout(retryTimers.get(method));
    if (data.terminalId && data.cameraId) {
      const channel = channelFor(method);
      socket?.send({ type: 'channel_subscription', action: 'unsubscribe', channel });
      if (data.callbacks[channel]) socket.off(channel, data.callbacks[channel]);
    }
    destroyMediaPlayer(method);
  });
  data.callbacks = {};
});
</script>

<style scoped lang="scss">
.realsense-viewer {
  display: flex;
  flex-direction: column;
  flex: 1;
  background-color: #000;
  .height-full {
    height: 100%;
  }
  .control-panel {
    padding: 10px;
    .controls {
      padding: 10px;
      background-color: rgb(50, 50, 50);
      border-radius: 10px;
      .el-form {
        display: flex;
        flex-direction: column;
      }
    }
  }
  .video-panel {
    display: flex;
    flex-direction: column;
    flex-grow: 1;
    overflow: hidden;
    .stream-container {
      display: flex;
      flex-direction: column;
      flex-grow: 1;
      min-height: 0;
      &.dual {
        .stream-item {
          min-height: 50%;
        }
      }
      .stream-item {
        flex: 1;
        min-height: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        .stream-wrapper {
          width: 100%;
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
          overflow: hidden;
          position: relative;
          video {
            width: 100%;
            height: 100%;
            object-fit: contain;
            background: #000;
          }
          .stream-state {
            position: absolute;
            top: 12px;
            right: 12px;
            padding: 4px 9px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 4px;
            background: rgba(0, 0, 0, 0.58);
            color: rgba(255, 255, 255, 0.9);
            font-size: 12px;
            line-height: 18px;
            pointer-events: none;
          }
        }
      }
    }
  }
}
</style>
