import { type GameEffect } from '../core/gameResult'
import { DEFAULT_GAME_AUDIO_CONFIG, type AudioCueKey, type GameAudioConfig } from './audioConfig'

export class SoundDirector {
  enabled: boolean
  config: GameAudioConfig
  contexts: Partial<Record<AudioCueKey, WechatMiniprogram.InnerAudioContext>>
  loopTimers: Partial<Record<AudioCueKey, number>>
  backgroundLoopTimer: number
  activeGuidanceCue: AudioCueKey | null
  backgroundManager: WechatMiniprogram.BackgroundAudioManager | null
  appAudioMode: 'foreground' | 'background'

  constructor(config: GameAudioConfig = DEFAULT_GAME_AUDIO_CONFIG) {
    this.enabled = true
    this.config = config
    this.contexts = {}
    this.loopTimers = {}
    this.backgroundLoopTimer = 0
    this.activeGuidanceCue = null
    this.backgroundManager = null
    this.appAudioMode = 'foreground'
  }

  configure(config: GameAudioConfig): void {
    this.config = config
    this.resetContexts()
  }

  setEnabled(enabled: boolean): void {
    this.enabled = enabled
  }

  resetContexts(): void {
    const timerKeys = Object.keys(this.loopTimers) as AudioCueKey[]
    for (const key of timerKeys) {
      const timer = this.loopTimers[key]
      if (timer) {
        clearTimeout(timer)
      }
    }
    this.loopTimers = {}
    this.clearBackgroundLoopTimer()

    const keys = Object.keys(this.contexts) as AudioCueKey[]
    for (const key of keys) {
      const context = this.contexts[key]
      if (!context) {
        continue
      }
      context.stop()
      context.destroy()
    }
    this.contexts = {}
    this.activeGuidanceCue = null
    this.stopBackgroundGuidance()
  }

  destroy(): void {
    this.resetContexts()
  }

  handleEffects(effects: GameEffect[]): void {
    if (!this.enabled || !this.config.enabled || !effects.length) {
      return
    }

    const hasFinishCompletion = effects.some((effect) => effect.type === 'control_completed' && effect.controlKind === 'finish')
    if (hasFinishCompletion) {
      this.stopGuidanceLoop()
      this.play('control_completed:finish')
      return
    }

    for (const effect of effects) {
      if (effect.type === 'session_started') {
        this.play('session_started')
        continue
      }

      if (effect.type === 'session_cancelled') {
        this.stopGuidanceLoop()
        this.play('control_completed:finish')
        continue
      }

      if (effect.type === 'punch_feedback' && effect.tone === 'warning') {
        this.play('punch_feedback:warning')
        continue
      }

      if (effect.type === 'guidance_state_changed') {
        if (effect.guidanceState === 'distant') {
          this.startGuidanceLoop('guidance:distant')
          continue
        }
        if (effect.guidanceState === 'approaching') {
          this.startGuidanceLoop('guidance:approaching')
          continue
        }
        if (effect.guidanceState === 'ready') {
          this.startGuidanceLoop('guidance:ready')
          continue
        }
        this.stopGuidanceLoop()
        continue
      }

      if (effect.type === 'control_completed') {
        this.stopGuidanceLoop()
        if (effect.controlKind === 'start') {
          this.play('control_completed:start')
          continue
        }

        if (effect.controlKind === 'finish') {
          this.play('control_completed:finish')
          continue
        }

        this.play('control_completed:control')
        continue
      }

      if (effect.type === 'session_finished') {
        this.stopGuidanceLoop()
        if (!hasFinishCompletion) {
          this.play('control_completed:finish')
        }
      }
    }
  }

  setAppAudioMode(mode: 'foreground' | 'background'): void {
    if (this.appAudioMode === mode) {
      return
    }

    this.appAudioMode = mode
    const activeGuidanceCue = this.activeGuidanceCue
    if (!activeGuidanceCue) {
      this.stopBackgroundGuidance()
      return
    }

    if (mode === 'background') {
      this.stopForegroundCue(activeGuidanceCue)
      this.startBackgroundGuidance(activeGuidanceCue)
      return
    }

    this.stopBackgroundGuidance()
    this.playForeground(activeGuidanceCue)
  }

  play(key: AudioCueKey): void {
    if (this.appAudioMode === 'background') {
      const cue = this.config.cues[key]
      if (!cue || cue.backgroundMode !== 'guidance' || !this.isGuidanceCue(key)) {
        return
      }

      this.startBackgroundGuidance(key)
      return
    }

    this.playForeground(key)
  }

  playForeground(key: AudioCueKey): void {
    const cue = this.config.cues[key]
    if (!cue || !cue.src) {
      return
    }

    this.clearLoopTimer(key)
    const context = this.getContext(key)
    context.stop()
    if (typeof context.seek === 'function') {
      context.seek(0)
    }
    context.volume = Math.max(0, Math.min(1, this.config.masterVolume * cue.volume))
    context.play()
  }


  startGuidanceLoop(key: AudioCueKey): void {
    if (this.activeGuidanceCue === key) {
      return
    }

    this.stopGuidanceLoop()
    this.activeGuidanceCue = key
    if (this.appAudioMode === 'background') {
      this.startBackgroundGuidance(key)
      return
    }

    this.playForeground(key)
  }

  stopGuidanceLoop(): void {
    if (!this.activeGuidanceCue) {
      this.stopBackgroundGuidance()
      return
    }

    this.clearLoopTimer(this.activeGuidanceCue)
    const context = this.contexts[this.activeGuidanceCue]
    if (context) {
      context.stop()
      if (typeof context.seek === 'function') {
        context.seek(0)
      }
    }
    this.stopBackgroundGuidance()
    this.activeGuidanceCue = null
  }


  clearLoopTimer(key: AudioCueKey): void {
    const timer = this.loopTimers[key]
    if (timer) {
      clearTimeout(timer)
      delete this.loopTimers[key]
    }
  }

  handleCueEnded(key: AudioCueKey): void {
    const cue = this.config.cues[key]
    if (!cue.loop || this.activeGuidanceCue !== key || !this.enabled || !this.config.enabled) {
      return
    }

    this.clearLoopTimer(key)
    this.loopTimers[key] = setTimeout(() => {
      delete this.loopTimers[key]
      if (this.activeGuidanceCue === key && this.enabled && this.config.enabled) {
        this.play(key)
      }
    }, cue.loopGapMs) as unknown as number
  }

  handleBackgroundCueEnded(): void {
    const key = this.activeGuidanceCue
    if (!key || !this.enabled || !this.config.enabled || this.appAudioMode !== 'background') {
      return
    }

    const cue = this.config.cues[key]
    if (!cue || !cue.loop) {
      return
    }

    this.clearBackgroundLoopTimer()
    this.backgroundLoopTimer = setTimeout(() => {
      this.backgroundLoopTimer = 0
      if (this.activeGuidanceCue === key && this.appAudioMode === 'background' && this.enabled && this.config.enabled) {
        this.playBackgroundCue(key)
      }
    }, cue.loopGapMs) as unknown as number
  }

  clearBackgroundLoopTimer(): void {
    if (this.backgroundLoopTimer) {
      clearTimeout(this.backgroundLoopTimer)
      this.backgroundLoopTimer = 0
    }
  }

  stopForegroundCue(key: AudioCueKey): void {
    this.clearLoopTimer(key)
    const context = this.contexts[key]
    if (!context) {
      return
    }
    context.stop()
    if (typeof context.seek === 'function') {
      context.seek(0)
    }
  }

  isGuidanceCue(key: AudioCueKey): boolean {
    return key === 'guidance:searching'
      || key === 'guidance:distant'
      || key === 'guidance:approaching'
      || key === 'guidance:ready'
  }

  startBackgroundGuidance(key: AudioCueKey): void {
    if (!this.enabled || !this.config.enabled || !this.config.backgroundAudioEnabled) {
      return
    }

    const cue = this.config.cues[key]
    if (!cue || cue.backgroundMode !== 'guidance' || !cue.src) {
      return
    }

    this.playBackgroundCue(key)
  }

  playBackgroundCue(key: AudioCueKey): void {
    const cue = this.config.cues[key]
    if (!cue || !cue.src) {
      return
    }

    const manager = this.getBackgroundManager()
    this.clearBackgroundLoopTimer()
    manager.stop()
    manager.title = 'ColorMapRun 引导音'
    manager.epname = 'ColorMapRun'
    manager.singer = 'ColorMapRun'
    manager.coverImgUrl = ''
    manager.src = cue.src
    manager.play()
  }

  stopBackgroundGuidance(): void {
    this.clearBackgroundLoopTimer()
    if (!this.backgroundManager) {
      return
    }

    this.backgroundManager.stop()
  }

  getBackgroundManager(): WechatMiniprogram.BackgroundAudioManager {
    if (this.backgroundManager) {
      return this.backgroundManager
    }

    const manager = wx.getBackgroundAudioManager()
    if (typeof manager.onEnded === 'function') {
      manager.onEnded(() => {
        this.handleBackgroundCueEnded()
      })
    }
    this.backgroundManager = manager
    return manager
  }

  getContext(key: AudioCueKey): WechatMiniprogram.InnerAudioContext {
    const existing = this.contexts[key]
    if (existing) {
      return existing
    }

    const cue = this.config.cues[key]
    const context = wx.createInnerAudioContext()
    context.src = cue.src
    context.autoplay = false
    context.loop = false
    context.obeyMuteSwitch = this.config.obeyMuteSwitch
    if (typeof context.onEnded === 'function') {
      context.onEnded(() => {
        this.handleCueEnded(key)
      })
    }
    context.volume = Math.max(0, Math.min(1, this.config.masterVolume * cue.volume))
    this.contexts[key] = context
    return context
  }
}
