<template>
  <div class="media-recorder" :class="`media-recorder--${kind}`">
    <div class="media-recorder__frame">
      <div v-if="previewUrl" class="media-recorder__preview">
        <video
          v-if="kind === 'video'"
          ref="previewEl"
          :src="previewUrl"
          controls
          playsinline
          class="media-recorder__media"
        />
        <audio
          v-else
          ref="previewEl"
          :src="previewUrl"
          controls
          class="media-recorder__media media-recorder__media--audio"
        />
      </div>
      <div v-else-if="isRecording" class="media-recorder__live">
        <video
          v-if="kind === 'video'"
          ref="liveEl"
          autoplay
          muted
          playsinline
          class="media-recorder__media"
        />
        <div v-else class="media-recorder__pulse">
          <SvgIcon name="paper-plane-right" :size="32" color="var(--color-primary)" />
        </div>
        <div class="media-recorder__timer">{{ formattedTime }}</div>
      </div>
      <div v-else class="media-recorder__empty">
        <SvgIcon name="upload" :size="48" color="var(--color-thd-gray)" />
        <p class="media-recorder__hint">{{ hintText }}</p>
      </div>
    </div>

    <div class="media-recorder__actions">
      <button
        v-if="!isRecording && !previewUrl"
        type="button"
        class="media-recorder__btn media-recorder__btn--start"
        :disabled="loading"
        @click="onStart"
      >
        <span v-if="loading">در حال بارگذاری...</span>
        <span v-else>شروع ضبط</span>
      </button>
      <button
        v-if="isRecording"
        type="button"
        class="media-recorder__btn media-recorder__btn--stop"
        @click="onStop"
      >
        پایان ضبط
      </button>
      <button
        v-if="previewUrl && !loading"
        type="button"
        class="media-recorder__btn media-recorder__btn--reset"
        @click="onReset"
      >
        ضبط دوباره
      </button>
    </div>

    <p v-if="error" class="media-recorder__error">{{ error }}</p>
  </div>

  <PermissionModal v-if="isModal('RegisterPermissionModal')" />
</template>

<script setup>
import { toast } from 'vue3-toastify'
import useModal from '@/composables/useModal'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import { objectToFormData } from '@/utils/object-to-formdata'
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
import { useUploadMediaMutation } from '@/services/query/auth'
import PermissionModal from '@/features/auth/components/studentRegister/PermissionModal.vue'

const props = defineProps({
  modelValue: { type: Object, default: null },
  kind: {
    type: String,
    default: 'audio',
    validator: (v) => ['audio', 'video'].includes(v),
  },
  maxSeconds: { type: Number, default: 120 },
  context: { type: String, default: 'verification' },
  subType: { type: String, default: '' },
  purpose: { type: String, default: 'video' },
  error: { type: String, default: '' },
})

const emit = defineEmits(['update:modelValue', 'uploaded'])

const { openModal, isModal } = useModal()

const previewUrl = ref(props.modelValue?.url || null)
const isRecording = ref(false)
const elapsedMs = ref(0)
const loading = ref(false)
const liveEl = ref(null)
const previewEl = ref(null)

let mediaRecorder = null
let mediaStream = null
let chunks = []
let timerHandle = null
let timerStart = 0
let recorderType = null

// The backend only accepts these extensions. Browsers can't record mp3/wav, but
// most can record into an mp4 container (→ .m4a audio / .mp4 video), so prefer a
// supported type whose extension the backend allows.
const ACCEPTED_TYPES = {
  audio: [
    { mime: 'audio/mp4', ext: 'm4a' },
    { mime: 'audio/mpeg', ext: 'mp3' },
    { mime: 'audio/wav', ext: 'wav' },
  ],
  video: [
    { mime: 'video/mp4', ext: 'mp4' },
    { mime: 'video/webm', ext: 'webm' },
  ],
}

const pickRecorderType = (kind) => {
  const candidates = ACCEPTED_TYPES[kind] || []
  const supported = candidates.find((c) => window.MediaRecorder?.isTypeSupported?.(c.mime))
  if (supported) return supported
  // Last-resort fallbacks so recording still works even if extensions differ.
  return kind === 'video'
    ? { mime: 'video/webm', ext: 'webm' }
    : { mime: 'audio/webm', ext: 'webm' }
}

const hintText = computed(() =>
  props.kind === 'video' ? 'ویدیوی خود را ضبط کنید' : 'صدای خود را ضبط کنید'
)

const formattedTime = computed(() => {
  const s = Math.floor(elapsedMs.value / 1000)
  const mm = String(Math.floor(s / 60)).padStart(2, '0')
  const ss = String(s % 60).padStart(2, '0')
  return `${mm}:${ss}`
})

const uploadMutation = useUploadMediaMutation()

const stopTracks = () => {
  if (mediaStream) {
    mediaStream.getTracks().forEach((t) => t.stop())
    mediaStream = null
  }
}

const clearTimer = () => {
  if (timerHandle) {
    clearInterval(timerHandle)
    timerHandle = null
  }
}

const onStart = async () => {
  try {
    const constraints = props.kind === 'video' ? { audio: true, video: true } : { audio: true }
    mediaStream = await navigator.mediaDevices.getUserMedia(constraints)
    chunks = []
    recorderType = pickRecorderType(props.kind)
    mediaRecorder = new window.MediaRecorder(mediaStream, { mimeType: recorderType.mime })
    mediaRecorder.ondataavailable = (e) => {
      if (e.data?.size > 0) chunks.push(e.data)
    }
    mediaRecorder.onstop = () => handleStop()
    isRecording.value = true

    // The live video is conditionally rendered by isRecording, so wait until
    // Vue mounts it before attaching the camera stream.
    await nextTick()
    if (props.kind === 'video' && liveEl.value) {
      liveEl.value.srcObject = mediaStream
    }

    mediaRecorder.start()
    elapsedMs.value = 0
    timerStart = Date.now()
    timerHandle = setInterval(() => {
      elapsedMs.value = Date.now() - timerStart
      if (elapsedMs.value >= props.maxSeconds * 1000) onStop()
    }, 250)
  } catch {
    openModal('RegisterPermissionModal', { kind: props.kind })
  }
}

const onStop = () => {
  if (mediaRecorder && mediaRecorder.state !== 'inactive') {
    mediaRecorder.stop()
  }
  clearTimer()
}

const handleStop = async () => {
  isRecording.value = false
  stopTracks()
  const mime = recorderType?.mime || mediaRecorder?.mimeType || 'audio/webm'
  const ext = recorderType?.ext || 'webm'
  const blob = new Blob(chunks, { type: mime })
  const file = new File([blob], `recording.${ext}`, { type: mime, lastModified: Date.now() })

  if (previewUrl.value && previewUrl.value.startsWith('blob:')) {
    URL.revokeObjectURL(previewUrl.value)
  }
  previewUrl.value = URL.createObjectURL(blob)

  loading.value = true
  try {
    const fd = objectToFormData({
      file,
      subType: props.subType || props.kind,
      context: props.context,
      ...(props.purpose ? { purpose: props.purpose } : {}),
    })
    const response = await uploadMutation.mutateAsync(fd)
    const payload = response?.data ?? response
    const value = {
      url: previewUrl.value,
      uploadId: payload?.uploadId || payload?.id,
      ...payload,
    }
    emit('update:modelValue', value)
    emit('uploaded', value)
  } catch {
    toast.error('بارگذاری فایل با خطا مواجه شد')
  } finally {
    loading.value = false
  }
}

const onReset = () => {
  if (previewUrl.value && previewUrl.value.startsWith('blob:')) {
    URL.revokeObjectURL(previewUrl.value)
  }
  previewUrl.value = null
  emit('update:modelValue', null)
}

onBeforeUnmount(() => {
  stopTracks()
  clearTimer()
  if (previewUrl.value && previewUrl.value.startsWith('blob:')) {
    URL.revokeObjectURL(previewUrl.value)
  }
})
</script>

<style lang="scss" scoped>
.media-recorder {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  width: 100%;

  &__frame {
    position: relative;
    aspect-ratio: 3 / 4;
    border-radius: 1rem;
    background: #f3f4f6;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  &--audio &__frame {
    aspect-ratio: 1;
  }

  &__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    color: var(--color-thd-gray);
  }

  &__hint {
    font-family: var(--font-family-fa);
    font-size: 0.75rem;
    margin: 0;
  }

  &__live,
  &__preview {
    width: 100%;
    height: 100%;
    position: relative;
  }

  &__media {
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #000;
  }

  &__media--audio {
    height: 2.5rem;
    background: transparent;
    position: absolute;
    inset-block-end: 0.75rem;
    inset-inline: 0.5rem;
    width: calc(100% - 1rem);
  }

  &__pulse {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    animation: pulse 1.5s ease-in-out infinite;
  }

  &__timer {
    position: absolute;
    inset-block-start: 0.5rem;
    inset-inline-start: 0.5rem;
    background: rgba(0, 0, 0, 60%);
    color: #fff;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    font-family: var(--font-family-en);
    font-size: 0.75rem;
  }

  &__actions {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
  }

  &__btn {
    border: none;
    border-radius: 9999px;
    padding: 0.5rem 1rem;
    font-family: var(--font-family-fa);
    font-size: 0.875rem;
    cursor: pointer;
    transition: opacity 0.15s ease;

    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    &--start {
      background: var(--color-primary);
      color: #fff;
    }

    &--stop {
      background: var(--color-error);
      color: #fff;
    }

    &--reset {
      background: transparent;
      color: var(--color-sec-gray);
      border: 1px solid var(--color-thd-gray);
    }
  }

  &__error {
    font-family: var(--font-family-fa);
    font-size: 0.75rem;
    color: var(--color-error);
    margin: 0;
  }
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.4;
  }
}
</style>
