/** Minimal WebAudio synth — no audio files, all bleeps. Safe in headless
 *  browsers: every call is guarded, and the context starts on first input. */

type SfxName =
  | 'jump' | 'doubleJump' | 'land' | 'hurt' | 'pickup' | 'die' | 'win'
  | 'dash' | 'break' | 'checkpoint';

class Sfx {
  private ctx: AudioContext | null = null;
  private ok = true;

  /** call from a user-gesture (any keydown) */
  unlock(): void {
    if (!this.ok || this.ctx) return;
    try {
      this.ctx = new AudioContext();
    } catch {
      this.ok = false;
    }
  }

  play(name: SfxName): void {
    if (!this.ctx || this.ctx.state !== 'running') {
      this.ctx?.resume().catch(() => {});
      if (!this.ctx) return;
    }
    try {
      switch (name) {
        case 'jump': this.blip(300, 520, 0.09, 'square', 0.05); break;
        case 'doubleJump': this.blip(420, 720, 0.1, 'square', 0.05); break;
        case 'land': this.blip(150, 90, 0.06, 'triangle', 0.05); break;
        case 'hurt': this.blip(280, 90, 0.18, 'sawtooth', 0.07); break;
        case 'die': this.blip(220, 40, 0.5, 'sawtooth', 0.08); break;
        case 'pickup':
          this.blip(440, 440, 0.08, 'sine', 0.06);
          this.blip(660, 660, 0.1, 'sine', 0.06, 0.09);
          this.blip(880, 880, 0.2, 'sine', 0.06, 0.18);
          break;
        case 'win':
          this.blip(523, 523, 0.15, 'sine', 0.06);
          this.blip(659, 659, 0.15, 'sine', 0.06, 0.14);
          this.blip(784, 784, 0.3, 'sine', 0.06, 0.28);
          break;
        case 'dash': this.blip(680, 180, 0.13, 'sawtooth', 0.05); break;
        case 'break':
          this.blip(170, 40, 0.28, 'sawtooth', 0.08);
          this.blip(900, 300, 0.12, 'square', 0.04, 0.02);
          break;
        case 'checkpoint':
          this.blip(520, 520, 0.09, 'sine', 0.045);
          this.blip(780, 780, 0.14, 'sine', 0.045, 0.1);
          break;
      }
    } catch {
      /* audio is decoration; never crash the game */
    }
  }

  private blip(
    from: number,
    to: number,
    dur: number,
    type: OscillatorType,
    gainV: number,
    delay = 0,
  ): void {
    const ctx = this.ctx!;
    const t0 = ctx.currentTime + delay;
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.type = type;
    osc.frequency.setValueAtTime(from, t0);
    osc.frequency.exponentialRampToValueAtTime(Math.max(20, to), t0 + dur);
    gain.gain.setValueAtTime(gainV, t0);
    gain.gain.exponentialRampToValueAtTime(0.001, t0 + dur);
    osc.connect(gain).connect(ctx.destination);
    osc.start(t0);
    osc.stop(t0 + dur + 0.02);
  }
}

export const sfx = new Sfx();
