export type SoundEvent =
  | 'timer_start'
  | 'timer_stop'
  | 'timer_pause'
  | 'timer_resume'
  | 'idle_alert'
  | 'goal_reached'
  | 'break_reminder'

export const SOUND_EVENTS: { key: SoundEvent; label: string }[] = [
  { key: 'timer_start', label: 'Timer start' },
  { key: 'timer_stop', label: 'Timer stop' },
  { key: 'timer_pause', label: 'Pause' },
  { key: 'timer_resume', label: 'Resume' },
  { key: 'idle_alert', label: 'Idle alert' },
  { key: 'goal_reached', label: 'Goal reached' },
  { key: 'break_reminder', label: 'Break reminder' },
]

export interface AudioSettings {
  enabled: boolean
  mode: 'synthesized' | 'system' | 'custom'
  volume: number
  events: Record<SoundEvent, boolean>
}

export const DEFAULT_EVENTS: Record<SoundEvent, boolean> = {
  timer_start: true,
  timer_stop: true,
  timer_pause: true,
  timer_resume: true,
  idle_alert: true,
  goal_reached: true,
  break_reminder: true,
}

const DEFAULT_SETTINGS: AudioSettings = {
  enabled: false,
  mode: 'synthesized',
  volume: 70,
  events: { ...DEFAULT_EVENTS },
}

class AudioEngine {
  private ctx: AudioContext | null = null
  private settings: AudioSettings = { ...DEFAULT_SETTINGS, events: { ...DEFAULT_SETTINGS.events } }

  private ensureContext(): AudioContext {
    if (!this.ctx) {
      this.ctx = new AudioContext()
    }
    if (this.ctx.state === 'suspended') {
      this.ctx.resume()
    }
    return this.ctx
  }

  private get gain(): number {
    return this.settings.volume / 100
  }

  updateSettings(partial: Partial<AudioSettings>) {
    if (partial.enabled !== undefined) this.settings.enabled = partial.enabled
    if (partial.mode !== undefined) this.settings.mode = partial.mode
    if (partial.volume !== undefined) this.settings.volume = partial.volume
    if (partial.events !== undefined) this.settings.events = { ...partial.events }
  }

  getSettings(): AudioSettings {
    return { ...this.settings, events: { ...this.settings.events } }
  }

  play(event: SoundEvent) {
    if (!this.settings.enabled) return
    if (!this.settings.events[event]) return
    if (this.settings.mode !== 'synthesized') return
    this.synthesize(event)
  }

  playTest(event: SoundEvent) {
    this.synthesize(event)
  }

  private synthesize(event: SoundEvent) {
    switch (event) {
      case 'timer_start':
        this.playTimerStart()
        break
      case 'timer_stop':
        this.playTimerStop()
        break
      case 'timer_pause':
        this.playTimerPause()
        break
      case 'timer_resume':
        this.playTimerResume()
        break
      case 'idle_alert':
        this.playIdleAlert()
        break
      case 'goal_reached':
        this.playGoalReached()
        break
      case 'break_reminder':
        this.playBreakReminder()
        break
    }
  }

  // Quick ascending two-note chime: C5 then E5
  private playTimerStart() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    const vol = this.gain

    // Note 1: C5 (523Hz) for 100ms
    this.playTone(ctx, t, 523, 0.100, vol, 0.010, 0.050, 3)
    // Note 2: E5 (659Hz) for 150ms
    this.playTone(ctx, t + 0.110, 659, 0.150, vol, 0.010, 0.050, 3)
  }

  // Descending resolve: G5 sliding to C5 over 250ms
  private playTimerStop() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    const vol = this.gain
    const duration = 0.250

    const osc = ctx.createOscillator()
    const gain = ctx.createGain()
    osc.type = 'sine'
    osc.frequency.setValueAtTime(784, t)
    osc.frequency.linearRampToValueAtTime(523, t + duration)

    gain.gain.setValueAtTime(0, t)
    gain.gain.linearRampToValueAtTime(vol, t + 0.010)
    gain.gain.setValueAtTime(vol, t + duration - 0.100)
    gain.gain.linearRampToValueAtTime(0, t + duration)

    osc.connect(gain)
    gain.connect(ctx.destination)
    osc.start(t)
    osc.stop(t + duration)
  }

  // Single soft tone: A4 (440Hz) for 120ms
  private playTimerPause() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    this.playTone(ctx, t, 440, 0.120, this.gain, 0.005, 0.060)
  }

  // Single bright tone: C5 (523Hz) for 120ms
  private playTimerResume() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    this.playTone(ctx, t, 523, 0.120, this.gain, 0.005, 0.060)
  }

  // Two quick pulses at A5 (880Hz), each 80ms with 60ms gap
  private playIdleAlert() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    const vol = this.gain

    this.playTone(ctx, t, 880, 0.080, vol, 0.005, 0.030)
    this.playTone(ctx, t + 0.140, 880, 0.080, vol, 0.005, 0.030)
  }

  // Ascending three-note fanfare: C5, E5, G5
  private playGoalReached() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    const vol = this.gain

    this.playTone(ctx, t, 523, 0.120, vol, 0.010, 0.040, 3)
    this.playTone(ctx, t + 0.130, 659, 0.120, vol, 0.010, 0.040, 3)
    this.playTone(ctx, t + 0.260, 784, 0.120, vol, 0.010, 0.040, 3)
  }

  // Gentle single chime at E5 (659Hz), 200ms, long release
  private playBreakReminder() {
    const ctx = this.ensureContext()
    const t = ctx.currentTime
    this.playTone(ctx, t, 659, 0.200, this.gain, 0.020, 0.100)
  }

  // Helper: play a single tone with ADSR-style envelope
  // Optional detuneCents adds a second oscillator slightly detuned for warmth
  private playTone(
    ctx: AudioContext,
    startAt: number,
    freq: number,
    duration: number,
    vol: number,
    attack: number,
    release: number,
    detuneCents?: number
  ) {
    const endAt = startAt + duration

    const gainNode = ctx.createGain()
    gainNode.gain.setValueAtTime(0, startAt)
    gainNode.gain.linearRampToValueAtTime(vol, startAt + attack)
    gainNode.gain.setValueAtTime(vol, endAt - release)
    gainNode.gain.linearRampToValueAtTime(0, endAt)
    gainNode.connect(ctx.destination)

    const osc1 = ctx.createOscillator()
    osc1.type = 'sine'
    osc1.frequency.setValueAtTime(freq, startAt)
    osc1.connect(gainNode)
    osc1.start(startAt)
    osc1.stop(endAt)

    if (detuneCents) {
      const osc2 = ctx.createOscillator()
      osc2.type = 'sine'
      osc2.frequency.setValueAtTime(freq, startAt)
      osc2.detune.setValueAtTime(detuneCents, startAt)
      osc2.connect(gainNode)
      osc2.start(startAt)
      osc2.stop(endAt)
    }
  }
}

export const audioEngine = new AudioEngine()
