/**
 * 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);
}

/**
 * Ambient loop: sparse pentatonic koto plucks over a slow taiko heartbeat,
 * scheduled on setInterval (so it breathes even when rAF throttles) with a
 * seeded LCG — the tune wanders but never uses Math.random. Quiet on purpose.
 */
let musicTimer: number | null = null;
export const music = {
  start(): void {
    if (musicTimer !== null || !ac()) return;
    const scale = [147, 175, 196, 220, 262, 294, 349]; // D minor pentatonic-ish
    let s = 42, step = 0;
    const rnd = () => {
      s = (s * 1103515245 + 12345) & 0x7fffffff;
      return s / 0x7fffffff;
    };
    musicTimer = window.setInterval(() => {
      step++;
      if (rnd() < 0.72) {
        const f = scale[Math.floor(rnd() * scale.length)];
        tone(f, 0.9, 'triangle', 0.045, f * 0.985); // soft pluck with a tiny droop
      }
      if (step % 8 === 0) tone(73, 1.2, 'sine', 0.07); // the heartbeat
    }, 620);
  },
  stop(): void {
    if (musicTimer !== null) {
      clearInterval(musicTimer);
      musicTimer = null;
    }
  },
};

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);
  },
  /** The blade landing a real cut — a firm little chop-thunk. */
  chop(): void {
    burst(0.09, 0.5, 2400, 500);
    tone(180, 0.12, 'square', 0.08, 90);
  },
  /** 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);
  },
};
