<template>
  <div class="player-wrap">
    <div ref="playerRef" class="artplayer-container"></div>
  </div>
</template>

<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import Artplayer from 'artplayer'
import Hls from 'hls.js'
import type { EpisodeData } from '@/api/qishier'
import { getEpisodeProgress, saveEpisodeProgress } from '@/utils/playHistory'



type ArtWithHls = Artplayer & {
  hls?: Hls
}

const props = defineProps<{
  episode: EpisodeData | null
  autoNext: boolean
}>()

const emit = defineEmits<{
  error: [message: string]
  endedNext: []
}>()

const playerRef = ref<HTMLDivElement | null>(null)
let art: ArtWithHls | null = null
let playRetryTimer: number | null = null

function clearPlayRetryTimer() {
  if (playRetryTimer !== null) {
    window.clearTimeout(playRetryTimer)
    playRetryTimer = null
  }
}

function destroyPlayer() {
  clearPlayRetryTimer()

  if (art?.hls) {
    art.hls.destroy()
    art.hls = undefined
  }

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

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

function safeAutoPlay() {
  clearPlayRetryTimer()

  playRetryTimer = window.setTimeout(() => {
    void art?.video?.play().catch(() => {
      // 某些浏览器自动播放会被拦截，这里静默处理
    })
  }, 180)
}

function buildPlayer(episode: EpisodeData) {
  if (!playerRef.value) return

  art = new Artplayer({
    container: playerRef.value,
    url: episode.videoUrl,
    autoplay: true,
    autoSize: true,
    fullscreen: true,
    fullscreenWeb: true,
    pip: true,
    setting: true,
    playbackRate: true,
    hotkey: true,
    mutex: true,
    theme: '#f5c542',
    moreVideoAttr: {
      playsInline: true,
      preload: 'auto'
    } as any,
    customType: {
      m3u8(video: HTMLVideoElement, url: string, artInstance: Artplayer) {
        const artplayer = artInstance as ArtWithHls

        if (Hls.isSupported()) {
          const hls = new Hls({
            enableWorker: true,
            lowLatencyMode: false,
            backBufferLength: 30,
            maxBufferLength: 30,
            maxMaxBufferLength: 60,
            maxBufferHole: 1,
            highBufferWatchdogPeriod: 2,
            nudgeOffset: 0.1,
            nudgeMaxRetry: 5
          })

          artplayer.hls = hls
          hls.loadSource(url)
          hls.attachMedia(video)

          hls.on(Hls.Events.ERROR, (_event, data) => {
            console.error('HLS Error:', data)

            if (!data.fatal) {
              return
            }

            if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
              console.warn('HLS NETWORK_ERROR，尝试恢复')
              hls.startLoad()
              emit('error', '网络异常，正在尝试恢复')
              return
            }

            if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
              console.warn('HLS MEDIA_ERROR，尝试恢复')
              hls.recoverMediaError()
              emit('error', '播放异常，正在尝试恢复')
              return
            }

            emit('error', '视频播放失败')
          })
        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
          video.src = url
        } else {
          emit('error', '当前浏览器不支持 m3u8 播放')
        }
      }
    }
  }) as ArtWithHls

  art.type = 'm3u8'

  const restoreProgress = () => {
    const progress = getEpisodeProgress(episode.id)
    if (progress > 0 && art?.video) {
      try {
        art.video.currentTime = progress
      } catch {
        // ignore
      }
    }

    safeAutoPlay()
  }

  art.on('video:loadedmetadata', restoreProgress)

  art.on('video:canplay', () => {
    // 某些设备 loadedmetadata 后还没准备好，canplay 时再兜底一次
    safeAutoPlay()
  })

  art.on('video:waiting', () => {
    // 这里不报错，只作为缓冲状态
    console.warn('视频缓冲中...')
  })

  art.on('video:timeupdate', () => {
    if (art?.video) {
      saveEpisodeProgress(episode.id, art.video.currentTime)
    }
  })

  art.on('video:ended', () => {
    if (props.autoNext) {
      emit('endedNext')
    }
  })

  art.on('error', () => {
    emit('error', '视频播放失败')
  })
}

async function initPlayer() {
  destroyPlayer()

  if (!props.episode) return

  await nextTick()
  buildPlayer(props.episode)
}

watch(
    () => props.episode?.id,
    async () => {
      await initPlayer()
    }
)

watch(
    () => props.autoNext,
    () => {
      // 保持响应式依赖
    }
)

onMounted(async () => {
  await initPlayer()
})

onBeforeUnmount(() => {
  destroyPlayer()
})
</script>

<style scoped>
.player-wrap {
  width: 100%;
  background: #000;
  border-radius: 12px;
  overflow: hidden;
}

.artplayer-container {
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
}

:deep(.artplayer-app) {
  width: 100%;
  height: 100%;
}

:deep(.art-video-player) {
  background: #000;
}
</style>
