/**
 * SFX, 100% synthesized Web Audio — no paid assets, nothing to load. Lazy
 * AudioContext (browsers block it until a user gesture; call sfx.enable() from a
 * click). Every call is a no-op if audio isn't available (headless harness),
 * so gameplay never depends on it.
 */

let ctx: AudioContext | null = null;
let noise: AudioBuffer | null = null;

function ac(): AudioContext | null {
  try {
    ctx ??= new AudioContext();
    if (ctx.state === 'suspended') void ctx.resume();
    return ctx;
  } catch {
    return null;
  }
}

/** One second of white noise, built once, reused for whooshes and splats. */
function noiseBuffer(c: AudioContext): AudioBuffer {
  if (noise) return noise;
  const buf = c.createBuffer(1, c.sampleRate, c.sampleRate);
  const d = buf.getChannelData(0);
  // deterministic-ish LCG so it isn't Math.random (keeps sim files clean of it)
  let s = 1234567;
  for (let i = 0; i < d.length; i++) {
    s = (s * 1103515245 + 12345) & 0x7fffffff;
    d[i] = (s / 0x3fffffff) - 1;
  }
  noise = buf;
  return buf;
}

function tone(freq: number, dur: number, type: OscillatorType, gain: number, sweepTo?: number): void {
  const c = ac();
  if (!c) return;
  const o = c.createOscillator(), g = c.createGain();
  o.type = type;
  o.frequency.setValueAtTime(freq, c.currentTime);
  if (sweepTo) o.frequency.exponentialRampToValueAtTime(sweepTo, c.currentTime + dur);
  g.gain.setValueAtTime(gain, c.currentTime);
  g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur);
  o.connect(g).connect(c.destination);
  o.start();
  o.stop(c.currentTime + dur);
}

function burst(dur: number, gain: number, filterHz: number, sweepTo?: number): void {
  const c = ac();
  if (!c) return;
  const src = c.createBufferSource();
  src.buffer = noiseBuffer(c);
  const f = c.createBiquadFilter();
  f.type = 'lowpass';
  f.frequency.setValueAtTime(filterHz, c.currentTime);
  if (sweepTo) f.frequency.exponentialRampToValueAtTime(sweepTo, c.currentTime + dur);
  const g = c.createGain();
  g.gain.setValueAtTime(gain, c.currentTime);
  g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur);
  src.connect(f).connect(g).connect(c.destination);
  src.start();
  src.stop(c.currentTime + dur);
}

export const sfx = {
  /** Call from a user gesture to unblock audio. */
  enable(): void {
    ac();
  },
  /** The strike — a deep temple gong. */
  gong(): void {
    tone(68, 1.4, 'sine', 0.4);
    tone(136, 1.1, 'sine', 0.12);
  },
  /** The katana — a bright noise whoosh sweeping down. */
  slash(): void {
    burst(0.32, 0.5, 9000, 400);
    tone(1400, 0.18, 'sawtooth', 0.14, 300);
  },
  /** Wet food hitting the board — a short damp thud. */
  splat(): void {
    burst(0.16, 0.45, 900, 200);
  },
  /** Title / serve sting — a plucked shamisen twang. */
  shamisen(): void {
    tone(330, 0.5, 'sawtooth', 0.16, 220);
    tone(660, 0.35, 'triangle', 0.06, 500);
  },
};
