<template>
  <div>
    <c-card v-if="!isSupported"> Your browser does not support recording video from camera </c-card>

    <c-card v-else-if="!permissionGranted" text-center>
      You need to grant permission to use your camera and microphone

      <c-alert v-if="permissionCannotBePrompted" mt-4 text-left>
        Your browser has blocked permission request or does not support it. You need to grant permission manually in
        your browser settings (usually the lock icon in the address bar).
      </c-alert>

      <div v-else mt-4 flex justify-center>
        <c-button @click="requestPermissions">Grant permission</c-button>
      </div>
    </c-card>

    <c-card v-else>
      <div flex gap-2>
        <div flex-1>
          <div>Video</div>
          <n-select
            v-model:value="currentCamera"
            :options="cameras.map(({ deviceId, label }) => ({ value: deviceId, label }))"
            placeholder="Select camera"
          />
        </div>
        <div v-if="currentMicrophone && microphones.length > 0" flex-1>
          <div>Audio</div>
          <n-select
            v-model:value="currentMicrophone"
            :options="microphones.map(({ deviceId, label }) => ({ value: deviceId, label }))"
            placeholder="Select microphone"
          />
        </div>
      </div>

      <div v-if="!isMediaStreamAvailable" mt-3 flex justify-center>
        <c-button type="primary" @click="start">Start webcam</c-button>
      </div>

      <div v-else>
        <div my-2>
          <video ref="video" autoplay controls playsinline max-h-full w-full />
        </div>

        <div flex items-center justify-between gap-2>
          <c-button :disabled="!isMediaStreamAvailable" @click="takeScreenshot">
            <span mr-2> <icon-mdi-camera /></span>
            Take screenshot
          </c-button>

          <div v-if="isRecordingSupported" flex justify-center gap-2>
            <c-button v-if="recordingState === 'stopped'" @click="startRecording">
              <span mr-2> <icon-mdi-video /></span>
              Start recording
            </c-button>

            <c-button v-if="recordingState === 'recording'" @click="pauseRecording">
              <span mr-2> <icon-mdi-pause /></span>
              Pause
            </c-button>

            <c-button v-if="recordingState === 'paused'" @click="resumeRecording">
              <span mr-2> <icon-mdi-play /></span>
              Resume
            </c-button>

            <c-button v-if="recordingState !== 'stopped'" type="error" @click="stopRecording">
              <span mr-2> <icon-mdi-record /></span>
              Stop
            </c-button>
          </div>
          <div v-else italic op-60>Video recording is not supported in your browser</div>
        </div>
      </div>
    </c-card>

    <div grid grid-cols-2 mt-5 gap-2>
      <c-card v-for="({ type, value, createdAt }, index) in medias" :key="index">
        <img v-if="type === 'image'" :src="value" max-h-full w-full alt="screenshot" />

        <video v-else :src="value" controls max-h-full w-full />

        <div flex items-center justify-between>
          <div font-bold>{{ type === 'image' ? 'Screenshot' : 'Video' }}</div>

          <div flex gap-2>
            <c-button @click="downloadMedia({ type, value, createdAt })">
              <icon-mdi-download />
            </c-button>

            <c-button @click="medias = medias.filter((_ignored, i) => i !== index)">
              <icon-mdi-delete-outline />
            </c-button>
          </div>
        </div>
      </c-card>
    </div>
  </div>
</template>

<script setup lang="ts">
import _ from 'lodash';

import { useMediaRecorder } from './useMediaRecorder';

type Media = { type: 'image' | 'video'; value: string; createdAt: Date };

const {
  videoInputs: cameras,
  audioInputs: microphones,
  permissionGranted,
  isSupported,
  ensurePermissions,
} = useDevicesList({
  requestPermissions: true,
  constraints: { video: true, audio: true },
  onUpdated() {
    refreshCurrentDevices();
  },
});

const video = ref<HTMLVideoElement>();
const medias = ref<Media[]>([]);
const currentCamera = ref(cameras.value[0]?.deviceId);
const currentMicrophone = ref(microphones.value[0]?.deviceId);
const permissionCannotBePrompted = ref(false);

const {
  stream,
  start,
  enabled: isMediaStreamAvailable,
} = useUserMedia({
  constraints: computed(() => ({
    video: { deviceId: currentCamera.value },
    ...(currentMicrophone.value ? { audio: { deviceId: currentMicrophone.value } } : {}),
  })),
  autoSwitch: true,
});

const {
  isRecordingSupported,
  onRecordAvailable,
  startRecording,
  stopRecording,
  pauseRecording,
  recordingState,
  resumeRecording,
} = useMediaRecorder({
  stream,
});

onRecordAvailable((value) => {
  medias.value.unshift({ type: 'video', value, createdAt: new Date() });
});

function refreshCurrentDevices() {
  console.log('refreshCurrentDevices');

  if (_.isNil(currentCamera) || !cameras.value.find((i) => i.deviceId === currentCamera.value)) {
    currentCamera.value = cameras.value[0]?.deviceId;
  }

  if (_.isNil(microphones) || !microphones.value.find((i) => i.deviceId === currentMicrophone.value)) {
    currentMicrophone.value = microphones.value[0]?.deviceId;
  }
}

function takeScreenshot() {
  if (!video.value) return;

  const canvas = document.createElement('canvas');
  canvas.width = video.value.videoWidth;
  canvas.height = video.value.videoHeight;
  canvas.getContext('2d')?.drawImage(video.value, 0, 0);
  const image = canvas.toDataURL('image/png');

  medias.value.unshift({ type: 'image', value: image, createdAt: new Date() });
}

watchEffect(() => {
  if (video.value && stream.value) video.value.srcObject = stream.value;
});

async function requestPermissions() {
  try {
    await ensurePermissions();
  } catch (e) {
    permissionCannotBePrompted.value = true;
  }
}

function downloadMedia({ type, value, createdAt }: Media) {
  const link = document.createElement('a');
  link.href = value;
  link.download = `${type}-${createdAt.getTime()}.${type === 'image' ? 'png' : 'webm'}`;
  link.click();
}
</script>

<style lang="less" scoped></style>
