/** Tiny synthesized SFX kit — no assets, pure Web Audio. */
export class Sfx {
  private ctx: AudioContext | null = null;
  private master: GainNode | null = null;
  private noiseBuf: AudioBuffer | null = null;

  resume(): void {
    if (!this.ctx) {
      this.ctx = new AudioContext();
      this.master = this.ctx.createGain();
      this.master.gain.value = 0.3;
      this.master.connect(this.ctx.destination);
    }
    void this.ctx.resume();
  }

  private noise(ctx: AudioContext): AudioBuffer {
    if (!this.noiseBuf) {
      const buf = ctx.createBuffer(1, Math.floor(ctx.sampleRate * 0.5), ctx.sampleRate);
      const d = buf.getChannelData(0);
      for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;
      this.noiseBuf = buf;
    }
    return this.noiseBuf;
  }

  private env(dur: number, vol: number, when: number): GainNode | null {
    if (!this.ctx || !this.master) return null;
    const g = this.ctx.createGain();
    const t = this.ctx.currentTime + when;
    g.gain.setValueAtTime(vol, t);
    g.gain.exponentialRampToValueAtTime(0.001, t + dur);
    g.connect(this.master);
    return g;
  }

  private burst(dur: number, vol: number, type: BiquadFilterType, f0: number, f1: number, when = 0): void {
    if (!this.ctx) return;
    const ctx = this.ctx;
    const g = this.env(dur, vol, when);
    if (!g) return;
    const src = ctx.createBufferSource();
    src.buffer = this.noise(ctx);
    const filt = ctx.createBiquadFilter();
    filt.type = type;
    const t = ctx.currentTime + when;
    filt.frequency.setValueAtTime(f0, t);
    filt.frequency.exponentialRampToValueAtTime(Math.max(40, f1), t + dur);
    filt.Q.value = 1.1;
    src.connect(filt);
    filt.connect(g);
    src.start(t);
    src.stop(t + dur + 0.02);
  }

  private tone(type: OscillatorType, f0: number, f1: number, dur: number, vol: number, when = 0): void {
    if (!this.ctx) return;
    const g = this.env(dur, vol, when);
    if (!g) return;
    const o = this.ctx.createOscillator();
    o.type = type;
    const t = this.ctx.currentTime + when;
    o.frequency.setValueAtTime(f0, t);
    o.frequency.exponentialRampToValueAtTime(Math.max(20, f1), t + dur);
    o.connect(g);
    o.start(t);
    o.stop(t + dur + 0.02);
  }

  swing(): void { this.burst(0.13, 0.45, 'bandpass', 750, 2800); }
  spin(): void { this.burst(0.34, 0.55, 'bandpass', 450, 3200); }
  chargeReady(): void { this.tone('sine', 660, 1320, 0.13, 0.22); }
  grass(): void { this.burst(0.09, 0.38, 'highpass', 2200, 4500); }
  pot(): void {
    this.burst(0.16, 0.65, 'lowpass', 3200, 420);
    for (let i = 0; i < 3; i++) {
      this.tone('square', 850 + Math.random() * 700, 280, 0.05, 0.1, i * 0.028);
    }
  }
  squish(): void {
    this.tone('sine', 290, 70, 0.13, 0.5);
    this.burst(0.08, 0.22, 'lowpass', 1300, 320);
  }
  pop(): void {
    this.tone('sine', 170, 720, 0.13, 0.5);
    this.burst(0.13, 0.28, 'highpass', 1500, 5200);
  }
  gem(): void {
    this.tone('sine', 988, 988, 0.07, 0.28);
    this.tone('sine', 1480, 1480, 0.2, 0.26, 0.07);
  }
  heart(): void {
    this.tone('sine', 659, 659, 0.09, 0.28);
    this.tone('sine', 880, 880, 0.22, 0.26, 0.09);
  }
  hurt(): void { this.tone('square', 165, 75, 0.2, 0.32); }
  thud(): void { this.burst(0.07, 0.16, 'lowpass', 480, 140); }
}
