<template>
  <Teleport v-if="teleportToBody" to="body">
    <div v-if="modelValue" class="vap-player vap-overlay" @touchmove.prevent>
      <button
        v-if="showClose"
        class="vap-close"
        type="button"
        aria-label="Close"
        @click="close"
      >
        &times;
      </button>

      <div ref="containerRef" class="vap-stage" :class="`vap-fit-${fit}`"></div>

      <div v-if="showStatus && loading" class="vap-status">Loading...</div>
      <div v-else-if="showStatus && errorMessage" class="vap-status vap-status-error">
        {{ errorMessage }}
      </div>
    </div>
  </Teleport>

  <div v-else-if="modelValue" class="vap-player vap-inline">
    <div ref="containerRef" class="vap-stage" :class="`vap-fit-${fit}`"></div>

    <div v-if="showStatus && loading" class="vap-status">Loading...</div>
    <div v-else-if="showStatus && errorMessage" class="vap-status vap-status-error">
      {{ errorMessage }}
    </div>
  </div>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import Vap from 'video-animation-player'
import { getVapConfigFromMp4 } from '@/utils/vapMp4Config'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'

const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false,
  },
  src: {
    type: String,
    default: '',
  },
  loop: {
    type: Boolean,
    default: false,
  },
  mute: {
    type: Boolean,
    default: false,
  },
  accurate: {
    type: Boolean,
    default: true,
  },
  autoClose: {
    type: Boolean,
    default: true,
  },
  mode: {
    type: String,
    default: 'overlay',
    validator: (value) => ['overlay', 'inline'].includes(value),
  },
  showClose: {
    type: Boolean,
    default: true,
  },
  showStatus: {
    type: Boolean,
    default: true,
  },
  fit: {
    type: String,
    default: 'contain',
    validator: (value) => ['contain', 'cover', 'fill'].includes(value),
  },
  vapData: {
    type: Object,
    default: () => ({}),
  },
})

const emit = defineEmits(['update:modelValue', 'loaded', 'ended', 'error', 'close'])

const containerRef = ref(null)
const loading = ref(false)
const errorMessage = ref('')
let player = null
let playToken = 0
let cleanupPlayerVideoGuards = null
let vapInlinePlaybackPatched = false

const teleportToBody = computed(() => props.mode === 'overlay')
const resolvedSrc = computed(() => resolveProtectedAssetUrl(props.src))

function patchVapInlinePlayback() {
  if (vapInlinePlaybackPatched) {
    return
  }

  try {
    const probePlayer = new Vap()
    const proto = Object.getPrototypeOf(probePlayer)

    if (!proto || typeof proto.initVideo !== 'function') {
      return
    }

    if (proto.__likeiInlinePlaybackPatched) {
      vapInlinePlaybackPatched = true
      return
    }

    const originalInitVideo = proto.initVideo
    proto.initVideo = function patchedInitVideo(...args) {
      const result = originalInitVideo.apply(this, args)
      enforceVideoPlaybackAttributes(this?.video, this?.options?.mute)
      return result
    }

    proto.__likeiInlinePlaybackPatched = true
    vapInlinePlaybackPatched = true

    if (typeof probePlayer.destroy === 'function') {
      probePlayer.destroy()
    }
  } catch (error) {
    console.warn('Failed to patch VAP inline playback hooks:', error)
  }
}

function enforceVideoPlaybackAttributes(video, shouldMute) {
  if (!video) {
    return
  }

  video.autoplay = false
  video.controls = false
  video.playsInline = true
  video.setAttribute('playsinline', '')
  video.setAttribute('webkit-playsinline', '')
  video.setAttribute('x5-playsinline', '')
  video.setAttribute('controlslist', 'nofullscreen noplaybackrate noremoteplayback')
  video.setAttribute('disablepictureinpicture', '')
  video.setAttribute('x-webkit-airplay', 'deny')
  video.setAttribute('preload', 'auto')

  if ('disablePictureInPicture' in video) {
    video.disablePictureInPicture = true
  }

  if ('disableRemotePlayback' in video) {
    video.disableRemotePlayback = true
  }

  // Hide the source video completely so iOS does not surface the raw mp4
  // in the native fullscreen player instead of keeping the VAP in-canvas.
  video.style.display = 'none'
  video.style.position = ''
  video.style.left = ''
  video.style.top = ''
  video.style.width = ''
  video.style.height = ''
  video.style.opacity = ''
  video.style.pointerEvents = 'none'
  video.style.zIndex = ''

  if (shouldMute) {
    video.muted = true
    video.defaultMuted = true
    video.volume = 0
    video.setAttribute('muted', '')
  } else {
    video.defaultMuted = false
    video.removeAttribute('muted')
  }
}

function installInlinePlaybackGuards(video) {
  if (!video) {
    return () => {}
  }

  const keepInlinePlayback = () => {
    enforceVideoPlaybackAttributes(video, props.mute)

    if (typeof video.webkitSetPresentationMode === 'function') {
      try {
        video.webkitSetPresentationMode('inline')
      } catch (error) {
        console.warn('Failed to force inline presentation mode:', error)
      }
    }
  }

  keepInlinePlayback()

  const guardEvents = [
    'loadedmetadata',
    'canplay',
    'play',
    'playing',
    'webkitbeginfullscreen',
    'webkitpresentationmodechanged',
  ]

  guardEvents.forEach((eventName) => {
    video.addEventListener(eventName, keepInlinePlayback)
  })

  return () => {
    guardEvents.forEach((eventName) => {
      video.removeEventListener(eventName, keepInlinePlayback)
    })
  }
}

function destroyPlayer() {
  if (cleanupPlayerVideoGuards) {
    cleanupPlayerVideoGuards()
    cleanupPlayerVideoGuards = null
  }

  if (player) {
    player.destroy()
    player = null
  }

  if (containerRef.value) {
    containerRef.value.innerHTML = ''
  }
}

function close() {
  destroyPlayer()
  emit('update:modelValue', false)
  emit('close')
}

function handleEnded() {
  emit('ended')

  if (props.autoClose && !props.loop) {
    close()
  }
}

function formatError(error) {
  if (error?.message?.includes('vapc box not found')) {
    return 'VAP config not found in this mp4'
  }

  if (error?.message?.includes('request failed')) {
    return 'Failed to load mp4'
  }

  return 'Failed to play animation'
}

async function startPlay() {
  const currentToken = (playToken += 1)
  destroyPlayer()
  errorMessage.value = ''

  if (!props.modelValue || !resolvedSrc.value) {
    return
  }

  loading.value = true

  try {
    await nextTick()
    patchVapInlinePlayback()

    if (!containerRef.value || currentToken !== playToken) {
      return
    }

    const config = await getVapConfigFromMp4(resolvedSrc.value)

    if (!containerRef.value || currentToken !== playToken) {
      return
    }

    player = new Vap().play({
      container: containerRef.value,
      src: resolvedSrc.value,
      config,
      loop: props.loop,
      mute: props.mute,
      accurate: props.accurate,
      onLoadError: (error) => {
        errorMessage.value = formatError(error)
        emit('error', error)
      },
      onDestroy: () => {
        player = null
      },
      onDestory: () => {
        player = null
      },
      ...props.vapData,
    })

    enforceVideoPlaybackAttributes(player?.video, props.mute)
    cleanupPlayerVideoGuards = installInlinePlaybackGuards(player?.video)

    player.on('ended', handleEnded)
    player.on('playing', () => {
      loading.value = false
      emit('loaded')
    })
  } catch (error) {
    if (currentToken !== playToken) {
      return
    }

    loading.value = false
    errorMessage.value = formatError(error)
    emit('error', error)
  }
}

watch(
  () => [props.modelValue, props.src],
  () => {
    if (props.modelValue) {
      startPlay()
    } else {
      destroyPlayer()
      loading.value = false
      errorMessage.value = ''
    }
  },
  { immediate: true }
)

onBeforeUnmount(() => {
  playToken += 1
  destroyPlayer()
})
</script>

<style scoped>
.vap-player {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.vap-overlay {
  position: fixed;
  inset: 0;
  z-index: 3000;
  background: rgba(0, 0, 0, 0.72);
}

.vap-inline {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.vap-stage {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.vap-stage :deep(canvas) {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.vap-fit-contain :deep(canvas) {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.vap-fit-cover :deep(canvas),
.vap-fit-fill :deep(canvas) {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
}

.vap-fit-cover :deep(canvas) {
  object-fit: cover;
}

.vap-fit-fill :deep(canvas) {
  object-fit: fill;
}

.vap-close {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 12px);
  right: 12px;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 18px;
  color: #fff;
  font-size: 28px;
  line-height: 1;
  background: rgba(0, 0, 0, 0.36);
}

.vap-status {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom) + 40px);
  z-index: 2;
  max-width: calc(100vw - 48px);
  padding: 8px 12px;
  border-radius: 6px;
  color: #fff;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.42);
}

.vap-status-error {
  background: rgba(214, 48, 49, 0.9);
}
</style>
