type VideoEndCallback = () => void
type TimeUpdateCallback = (time: number) => void

export function getVideoMode(): 'auto' | 'local' | 'streaming' {
  const params = typeof URLSearchParams !== 'undefined' ? new URLSearchParams(location.search) : { get: () => null }
  const override = params.get('videoMode')
  if (override === 'local') return 'local'
  if (override === 'streaming') return 'streaming'
  if (typeof window !== 'undefined' && (window as any).__ELECTRON__) return 'local'
  return 'auto'
}

export class VideoManager {
  private elA: HTMLVideoElement | null = null
  private elB: HTMLVideoElement | null = null
  private activeSlot: 'A' | 'B' = 'A'
  private crossFadeMs = 300
  private onEndCallback: VideoEndCallback | null = null
  private onTimeCallbacks: Set<TimeUpdateCallback> = new Set()
  private currentSrc = ''
  private preloaded: Map<'A' | 'B', string> = new Map()
  private switching = false
  private sceneVideo: HTMLVideoElement | null = null
  streamingQuality = ''

  private get active(): HTMLVideoElement {
    return this.activeSlot === 'A' ? this.elA! : this.elB!
  }

  private get inactive(): HTMLVideoElement {
    return this.activeSlot === 'A' ? this.elB! : this.elA!
  }

  private get inactiveKey(): 'A' | 'B' {
    return this.activeSlot === 'A' ? 'B' : 'A'
  }

  attach(elA: HTMLVideoElement, elB: HTMLVideoElement) {
    this.elA = elA
    this.elB = elB
    this.sceneVideo = elA
    for (const el of [elA, elB]) {
      el.addEventListener('ended', this.handleEnded)
      el.addEventListener('timeupdate', this.handleTimeUpdate)
      el.style.position = 'absolute'
      el.style.inset = '0'
      el.style.width = '100%'
      el.style.height = '100%'
      el.style.objectFit = 'contain'
      el.style.transition = 'none'
    }
    elB.style.opacity = '0'
  }

  detach() {
    this.sceneVideo = null
    for (const el of [this.elA, this.elB]) {
      if (!el) continue
      el.removeEventListener('ended', this.handleEnded)
      el.removeEventListener('timeupdate', this.handleTimeUpdate)
      el.pause()
      el.removeAttribute('src')
    }
    this.elA = null
    this.elB = null
    this.onTimeCallbacks.clear()
  }

  playInitial(src: string, preloadUrls: string[]) {
    if (!this.elA) return
    this.sceneVideo = this.elA
    this.currentSrc = src
    this.activeSlot = 'A'
    this.preloaded.set('A', src)
    this.elA.src = src
    this.elA.style.opacity = '1'
    this.elB!.style.opacity = '0'

    this.waitReady(this.elA).then(() => {
      this.elA!.currentTime = 0
      this.elA!.play().catch(() => {})
    })

    if (preloadUrls.length > 0) {
      const next = preloadUrls[0]
      this.preloaded.set('B', next)
      this.elB!.src = next
      this.elB!.load()
    }
  }

  switchTo(src: string, preloadUrls: string[]) {
    if (!this.elA || this.switching) return

    if (src === this.currentSrc) {
      this.active.style.opacity = '1'
      this.active.currentTime = 0
      this.active.play().catch(() => {})
      return
    }

    const inKey = this.inactiveKey
    const alreadyPreloaded = this.preloaded.get(inKey)

    if (alreadyPreloaded === src) {
      this.sceneVideo = this.inactive
      this.doCrossFade(src, preloadUrls)
    } else {
      this.preloaded.set(inKey, src)
      this.inactive.src = src
      this.sceneVideo = this.inactive
      this.waitReady(this.inactive).then(() => {
        this.doCrossFade(src, preloadUrls)
      })
    }
  }

  private doCrossFade(src: string, preloadUrls: string[]) {
    const active = this.active
    const inactive = this.inactive
    const inKey = this.inactiveKey

    this.currentSrc = src
    this.switching = true

    inactive.currentTime = 0
    inactive.play().catch(() => {})

    active.style.transition = `opacity ${this.crossFadeMs}ms ease`
    inactive.style.transition = `opacity ${this.crossFadeMs}ms ease`
    active.style.opacity = '0'
    inactive.style.opacity = '1'

    setTimeout(() => {
      active.pause()
      active.style.transition = 'none'
      inactive.style.transition = 'none'
      this.activeSlot = inKey
      this.preloaded.set(inKey, src)
      this.switching = false

      if (preloadUrls.length > 0) {
        const nextInactive = this.inactive
        const nextKey = this.inactiveKey
        const candidate = preloadUrls[0]
        if (candidate !== src) {
          this.preloaded.set(nextKey, candidate)
          nextInactive.src = candidate
          nextInactive.load()
        }
      }
    }, this.crossFadeMs + 50)
  }

  getCurrentTime(): number {
    return this.active?.currentTime ?? 0
  }

  getActiveVideoElement(): HTMLVideoElement | null {
    return this.active ?? null
  }

  seekTo(time: number) {
    if (!this.active) return
    this.active.currentTime = time
  }

  setPlaybackRate(rate: number) {
    if (this.elA) this.elA.playbackRate = rate
    if (this.elB) this.elB.playbackRate = rate
  }

  getPlaybackRate(): number {
    return this.active?.playbackRate ?? 1
  }

  setMuted(muted: boolean) {
    if (this.elA) this.elA.muted = muted
    if (this.elB) this.elB.muted = muted
  }

  resolveVideoUrl(scene: { videoUrl: string; streamingUrl?: Record<string, string> }, quality?: string): string {
    const mode = getVideoMode()
    if (mode === 'local') return scene.videoUrl
    if (scene.streamingUrl) {
      const key = quality || Object.keys(scene.streamingUrl)[0]
      return scene.streamingUrl[key] || scene.videoUrl
    }
    return scene.videoUrl
  }

  switchQuality(src: string, seekTime: number) {
    const active = this.active
    this.currentSrc = src
    active.src = src
    active.load()
    this.preloaded.set(this.keyOf(active), src)
    this.waitReady(active).then(() => {
      active.currentTime = seekTime
      active.play().catch(() => {})
    })
  }

  private keyOf(el: HTMLVideoElement): 'A' | 'B' {
    return el === this.elA ? 'A' : 'B'
  }

  onEnd(cb: VideoEndCallback) {
    this.onEndCallback = cb
  }

  onTimeUpdate(cb: TimeUpdateCallback) {
    this.onTimeCallbacks.add(cb)
  }

  offTimeUpdate(cb: TimeUpdateCallback) {
    this.onTimeCallbacks.delete(cb)
  }

  private waitReady(el: HTMLVideoElement): Promise<void> {
    // readyState >= 2 (HAVE_CURRENT_DATA) 表示已有足够数据播放当前帧，无需等待
    if (el.readyState >= 2) return Promise.resolve()
    // 否则等待 canplay 事件（浏览器判断可开始播放），同时手动触发 load 确保加载流程已启动
    return new Promise((resolve) => {
      el.addEventListener('canplay', () => resolve(), { once: true })
      el.load()
    })
  }

  private handleEnded = () => {
    this.onEndCallback?.()
  }

  private handleTimeUpdate = (e: Event) => {
    const el = e.target as HTMLVideoElement
    if (!this.sceneVideo || el !== this.sceneVideo) return
    this.onTimeCallbacks.forEach((cb) => cb(el.currentTime))
  }
}
