// AudioEngine.ts: Web Audio procedural BGM (8-bar 8-bit loop) + 4 SFX.
// No external audio files. Driven by CorruptionDirector via setErosion().
export type Stage = 'S0' | 'S1' | 'S2' | 'S3' | 'S4';

export class AudioEngine {
  ctx: AudioContext | null = null;
  master: GainNode | null = null;
  musicGain: GainNode | null = null;
  sfxGain: GainNode | null = null;
  lowpass: BiquadFilterNode | null = null;
  // bird layer gain
  birdGain: GainNode | null = null;

  private schedulerId: number | null = null;
  private nextNoteTime = 0;
  private step = 0;
  private playbackRate = 1.0;
  private muted = false;
  private silenceFactor = 1.0; // multiplied into master for T14 sudden silence
  private birdsMuted = false;

  init() {
    if (this.ctx) return;
    const AC = (window.AudioContext || (window as any).webkitAudioContext);
    this.ctx = new AC();
    this.master = this.ctx.createGain();
    this.master.gain.value = 0.5;
    this.master.connect(this.ctx.destination);

    this.lowpass = this.ctx.createBiquadFilter();
    this.lowpass.type = 'lowpass';
    this.lowpass.frequency.value = 20000;

    this.musicGain = this.ctx.createGain();
    this.musicGain.gain.value = 0.35;
    this.birdGain = this.ctx.createGain();
    this.birdGain.gain.value = 0.5;

    this.musicGain.connect(this.lowpass);
    this.birdGain.connect(this.lowpass);
    this.lowpass.connect(this.master);

    this.sfxGain = this.ctx.createGain();
    this.sfxGain.gain.value = 0.4;
    this.sfxGain.connect(this.master);
  }

  resume() {
    if (this.ctx && this.ctx.state === 'suspended') this.ctx.resume();
  }

  setErosion(stage: Stage) {
    const map: Record<Stage, { rate: number; lp: number; birds: boolean }> = {
      S0: { rate: 1.00, lp: 20000, birds: false },
      S1: { rate: 1.00, lp: 20000, birds: false },
      S2: { rate: 0.97, lp: 20000, birds: false },
      S3: { rate: 0.92, lp: 8000, birds: false },
      S4: { rate: 0.84, lp: 4000, birds: true },
    };
    const s = map[stage];
    this.playbackRate = s.rate;
    this.birdsMuted = s.birds;
    if (this.lowpass && this.ctx) {
      this.lowpass.frequency.setTargetAtTime(s.lp, this.ctx.currentTime, 0.3);
    }
  }

  // Gradual silence for T14 (40-60s): fade master to 0.
  setSilenceFactor(f: number) {
    this.silenceFactor = Math.max(0, Math.min(1, f));
    this.applyMaster();
  }
  private applyMaster() {
    if (!this.master || !this.ctx) return;
    const v = this.muted ? 0 : 0.5 * this.silenceFactor;
    this.master.gain.setTargetAtTime(v, this.ctx.currentTime, 0.05);
  }

  startMusic() {
    if (!this.ctx) this.init();
    if (!this.ctx) return;
    this.resume();
    if (this.schedulerId !== null) return;
    this.nextNoteTime = this.ctx.currentTime + 0.1;
    this.step = 0;
    const loop = () => {
      this.schedule();
      this.schedulerId = window.setTimeout(loop, 25);
    };
    loop();
  }

  stopMusic() {
    if (this.schedulerId !== null) { clearTimeout(this.schedulerId); this.schedulerId = null; }
  }

  // 8-bar loop in C major, gentle. 16th-note steps. Tempo ~ 96 bpm.
  // melody (square) + bass (triangle) + bird chirps (occasional).
  private melody: number[] = [
    // bar1
    72,0,76,0, 79,0,76,0, 72,0,0,0, 0,0,74,0,
    // bar2
    76,0,0,0, 79,0,81,0, 83,0,0,79, 76,0,0,0,
    // bar3
    79,0,83,0, 86,0,83,0, 79,0,0,0, 0,0,77,0,
    // bar4
    76,0,74,0, 72,0,0,0, 0,0,0,0, 0,0,0,0,
    // bar5
    74,0,77,0, 81,0,77,0, 74,0,0,0, 0,0,76,0,
    // bar6
    77,0,0,0, 81,0,83,0, 84,0,0,81, 77,0,0,0,
    // bar7
    79,0,83,0, 86,0,88,0, 86,0,83,0, 79,0,0,0,
    // bar8
    76,0,79,0, 77,0,74,0, 72,0,0,0, 0,0,0,0,
  ];
  private bass: number[] = [
    48,0,0,0, 0,0,0,0, 55,0,0,0, 0,0,0,0,
    48,0,0,0, 0,0,0,0, 55,0,0,0, 0,0,0,0,
    53,0,0,0, 0,0,0,0, 57,0,0,0, 0,0,0,0,
    48,0,0,0, 0,0,0,0, 55,0,0,0, 0,0,0,0,
    50,0,0,0, 0,0,0,0, 57,0,0,0, 0,0,0,0,
    53,0,0,0, 0,0,0,0, 57,0,0,0, 0,0,0,0,
    55,0,0,0, 0,0,0,0, 60,0,0,0, 0,0,0,0,
    48,0,0,0, 0,0,0,0, 55,0,0,0, 0,0,0,0,
  ];

  private midiToFreq(m: number): number {
    return 440 * Math.pow(2, (m - 69) / 12);
  }

  private schedule() {
    if (!this.ctx || !this.musicGain) return;
    const secPerStep = (60 / 96) / 4 / this.playbackRate; // 16th note
    while (this.nextNoteTime < this.ctx.currentTime + 0.2) {
      const idx = this.step % this.melody.length;
      const mn = this.melody[idx];
      const bn = this.bass[idx];
      if (mn > 0) this.playTone(this.midiToFreq(mn), this.nextNoteTime, secPerStep * 1.5, 'square', 0.18, this.musicGain);
      if (bn > 0) this.playTone(this.midiToFreq(bn), this.nextNoteTime, secPerStep * 3.5, 'triangle', 0.22, this.musicGain);
      // bird chirp occasionally on downbeats (step % 16 == 0)
      if (!this.birdsMuted && idx % 16 === 0 && Math.random() < 0.5 && this.birdGain) {
        this.playBird(this.nextNoteTime + Math.random() * 0.3);
      }
      this.nextNoteTime += secPerStep;
      this.step++;
    }
  }

  private playTone(freq: number, when: number, dur: number, type: OscillatorType, vol: number, dest: AudioNode) {
    if (!this.ctx) return;
    const osc = this.ctx.createOscillator();
    const g = this.ctx.createGain();
    osc.type = type;
    osc.frequency.value = freq;
    g.gain.setValueAtTime(0, when);
    g.gain.linearRampToValueAtTime(vol, when + 0.01);
    g.gain.exponentialRampToValueAtTime(0.0001, when + dur);
    osc.connect(g); g.connect(dest);
    osc.start(when); osc.stop(when + dur + 0.02);
  }

  private playBird(when: number) {
    if (!this.ctx || !this.birdGain) return;
    const base = 80 + Math.random() * 6;
    // two quick notes
    this.playTone(this.midiToFreq(base + 12), when, 0.08, 'square', 0.06, this.birdGain);
    this.playTone(this.midiToFreq(base + 16), when + 0.09, 0.1, 'square', 0.06, this.birdGain);
  }

  // SFX (only 4 per §6.6)
  sfxBlip() {
    if (!this.ctx || !this.sfxGain) return;
    this.playTone(880, this.ctx.currentTime, 0.05, 'square', 0.3, this.sfxGain);
  }
  sfxDone() {
    if (!this.ctx || !this.sfxGain) return;
    const t = this.ctx.currentTime;
    this.playTone(660, t, 0.08, 'square', 0.3, this.sfxGain);
    this.playTone(880, t + 0.08, 0.08, 'square', 0.3, this.sfxGain);
    this.playTone(1320, t + 0.16, 0.16, 'square', 0.3, this.sfxGain);
  }
  sfxBell() {
    if (!this.ctx || !this.sfxGain) return;
    const t = this.ctx.currentTime;
    // bell-like: sine partials
    for (const f of [440, 660, 880]) this.playTone(f, t, 0.8, 'sine', 0.2, this.sfxGain);
  }
  sfxHeartbeat() {
    if (!this.ctx || !this.sfxGain) return;
    const t = this.ctx.currentTime;
    this.playTone(1000, t, 0.06, 'sine', 0.4, this.sfxGain);
  }
}

export const audio = new AudioEngine();
