<script setup lang="ts">
import { withResolvers }                                                  from '@/tools';
import { getMediaStream }                                                 from '@/tools/camera.tool';
import { tryOnMounted, tryOnUnmounted, useElementSize, useEventListener } from '@vueuse/core';


let _stream: MediaStream;


const { constraints = {} } = defineProps<{ constraints?: MediaTrackConstraints, multiple?: number; }>();
const width = defineModel('width', { default: 0 });
const height = defineModel('height', { default: 0 });
const multiple = defineModel('multiple', { default: 1 });

const offsetX = ref(0);
const offsetY = ref(0);

watch([ width, height, multiple ], ([ w, h, m ]) => {
  applyConstraints({ width: w * m, height: h * m });
});


const styleValue = computed(() => {
  return width.value && height.value
         ? { width: `${ width.value }px`, height: `${ height.value }px` }
         : void 0;
});

const containerStyleValue = reactive({
  width: `${ width.value }px`,
  height: `${ height.value }px`,
});

const container = useTemplateRef<HTMLElement>('camera-container');
const videoRef = ref<HTMLVideoElement | null>(null);
const canvasRef = ref<HTMLCanvasElement | null>(null);

/* 根容器 宽高 */
const root = useElementSize(() => container.value?.parentElement);

const getVideoTrack = () => {
  return videoRef.value?.srcObject instanceof MediaStream
         ? videoRef.value.srcObject.getVideoTracks()[ 0 ]
         : new MediaStreamTrack();
};

useEventListener(videoRef, 'canplay', (event) => {
  const video = event.target as HTMLVideoElement;
  const canvas = canvasRef.value;

  const track = getVideoTrack();

  const settings = track.getSettings();
  const { width: w = 1, height: h = 1, aspectRatio = w / h } = settings;
  const reverse = aspectRatio > 1;
  const _width = reverse ? h : w;
  const _height = reverse ? w : h;
  video.setAttribute('width', `${ _width }`);
  video.setAttribute('height', `${ _height }`);

  containerStyleValue.width = `${ _width }px`;
  containerStyleValue.height = `${ _height }px`;

  offsetX.value = (
                    _width - width.value
                  ) / 2;
  offsetY.value = (
                    _height - height.value
                  ) / 2;

  video.play().then(
    () => {},
    // (_) => (error.value = _)
  );
  console.group('[log] camera:', '流媒体可播放');
  console.log(`width=${ _width }, height=${ _height }`);
  console.log(`浏览器支持: 可缩放 ${ !!(
    <any> navigator.mediaDevices.getSupportedConstraints()
  ).zoom }`);
  console.log(`摄像头支持: 可缩放 ${ !!(
    <any> track.getCapabilities()
  )[ 'zoom' ] }`);
  console.log(`当前设置缩放: ${ multiple.value }`);
  console.log(`当前实际缩放: (${ _width / width.value }, ${ _height / height.value })`);
  console.log(`图象偏移距离: (${ offsetX.value }, ${ offsetY.value })`);
  console.groupEnd();
});

tryOnMounted(() => {
  const _container = container.value!;
  canvasRef.value = container.value!.querySelector('canvas');
  videoRef.value = container.value!.querySelector('video');

  const style = getComputedStyle(_container);
  const _width = width.value || Number.parseInt(style.width);
  const _height = height.value || Number.parseInt(style.height);
  const _multiple = multiple.value ?? 1;

  canvasRef.value!.width = _width;
  canvasRef.value!.height = _height;

  applyConstraints({ ...constraints, width: _width * _multiple, height: _height * _multiple });
});

tryOnUnmounted(() => {
  destroy();
  videoRef.value = null;
  canvasRef.value = null;
});

async function applyConstraints(constraints: Partial<MediaTrackConstraints>) {
  const { width, height, ..._constraints } = constraints;
  // @ts-ignore
  if ( !!width && !!height ) {
    destroy();
    _stream = await getMediaStream(<any> { ..._constraints, width, height, facingMode: 'user' });
    videoRef.value!.srcObject = _stream;
  } else {
    await _stream.getVideoTracks()[ 0 ]?.applyConstraints(constraints);
  }
}

function destroy() {
  const tracks = _stream?.getTracks() ?? [];
  for ( const track of tracks ) track.stop();
  _stream = void 0 as any;
  if ( videoRef.value ) {
    videoRef.value.pause();
    videoRef.value.srcObject = null as any;
  }
}


const handle = async (promise: Promise<boolean>) => {
  const video = videoRef.value;
  const canvas = canvasRef.value;
  if ( !video || !canvas ) return;
  if ( video.paused ) await video.play();

  const context = canvas.getContext('2d')!;
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.drawImage(video, offsetX.value, offsetY.value, video.width, video.height, 0, 0, canvas.width, canvas.height);
  video.pause();

  promise.then(value => video.paused && value ? video.play() : void 0);
};

const get = async (type: 'base64' | 'blob', mime = 'image/png') => {
  const video = videoRef.value;
  const canvas = canvasRef.value;
  if ( !video || !canvas ) return;
  if ( !video.paused ) await handle(Promise.resolve(true));

  switch ( type ) {
    case 'blob':
      const { promise, resolve } = withResolvers<Blob>();
      canvas.toBlob(<any> resolve, mime, 0.98);
      return promise;
    case 'base64':
      return canvas.toDataURL(mime);
  }
};

defineExpose({
  handle, get,
});
</script>
<template>
  <div class="camera-container size-full" ref="camera-container" :style="containerStyleValue">
    <canvas style="display: none;"></canvas>
    <video></video>
    <slot :style="styleValue" :offsetX="offsetX" :offsetY="offsetY"></slot>
  </div>
</template>
<style scoped lang="scss">
.camera-container {
  position: absolute;
  z-index: 0;
  display: flex;
  justify-content: center;
  align-items: center;

  > * {
    position: absolute;
  }

  video {
    clip-path: url("#shade");
  }
}
</style>
