/** Tiny WebAudio blips — no assets, fails silently if audio is unavailable. */
class Sfx {
  private ctx: AudioContext | null = null;

  /** Call from a user gesture to unlock audio. */
  unlock(): void {
    try {
      this.ctx ??= new AudioContext();
      if (this.ctx.state === 'suspended') void this.ctx.resume();
    } catch {
      this.ctx = null;
    }
  }

  private blip(freq: number, dur: number, type: OscillatorType = 'square', vol = 0.05, delay = 0): void {
    const ctx = this.ctx;
    if (!ctx || ctx.state !== 'running') return;
    try {
      const t0 = ctx.currentTime + delay;
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.type = type;
      osc.frequency.value = freq;
      gain.gain.setValueAtTime(vol, t0);
      gain.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
      osc.connect(gain).connect(ctx.destination);
      osc.start(t0);
      osc.stop(t0 + dur + 0.02);
    } catch {
      // ignore
    }
  }

  tap(): void {
    this.blip(620, 0.07);
  }

  place(): void {
    this.blip(440, 0.06);
    this.blip(660, 0.08, 'square', 0.05, 0.05);
  }

  wrong(): void {
    this.blip(160, 0.18, 'sawtooth', 0.06);
  }

  perfect(): void {
    this.blip(660, 0.08, 'triangle', 0.07);
    this.blip(880, 0.08, 'triangle', 0.07, 0.07);
    this.blip(1320, 0.14, 'triangle', 0.07, 0.14);
  }

  good(): void {
    this.blip(660, 0.09, 'triangle', 0.06);
    this.blip(880, 0.12, 'triangle', 0.06, 0.08);
  }

  over(): void {
    this.blip(330, 0.12, 'sawtooth', 0.05);
  }

  coin(): void {
    this.blip(990, 0.07, 'sine', 0.08);
    this.blip(1480, 0.16, 'sine', 0.08, 0.06);
  }

  angry(): void {
    this.blip(220, 0.14, 'sawtooth', 0.06);
    this.blip(150, 0.2, 'sawtooth', 0.06, 0.1);
  }

  serve(): void {
    this.blip(520, 0.07, 'triangle', 0.06);
    this.blip(780, 0.1, 'triangle', 0.06, 0.06);
  }
}

export const sfx = new Sfx();
