/// Sound-cue synthesis for recording state transitions (brief item B.1
/// #20). Uses the Web Audio API directly rather than shipping WAV
/// assets — keeps the Tauri binary the same size, sidesteps packaging
/// concerns, and gives the three cues a coherent sound signature we
/// can tweak without touching binary files.
///
/// The three cues are pitched as an ascending / descending pattern
/// that maps the mental model: start = go (rising C), stop = pause
/// (falling), complete = resolve (major third chord).

const START_FREQS = [523.25]; // C5
const STOP_FREQS = [392.0]; // G4
const COMPLETE_FREQS = [523.25, 659.25, 783.99]; // C5 E5 G5 major chord

const DEFAULT_VOLUME = 0.15; // 15% — well below system notification chirp volume
const TONE_DURATION_S = 0.14;
const ATTACK_S = 0.01;
const RELEASE_S = 0.06;

let cachedCtx: AudioContext | null = null;

function getAudioContext(): AudioContext | null {
  if (typeof window === "undefined") return null;
  if (cachedCtx && cachedCtx.state !== "closed") {
    return cachedCtx;
  }
  const Ctor =
    (window as typeof window & { webkitAudioContext?: typeof AudioContext })
      .AudioContext ??
    (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
  if (!Ctor) return null;
  try {
    cachedCtx = new Ctor();
    return cachedCtx;
  } catch {
    return null;
  }
}

function playTone(frequency: number, startOffsetS: number, volume: number): void {
  const ctx = getAudioContext();
  if (!ctx) return;
  // Browsers suspend AudioContext until a user gesture; Tauri's webview
  // inherits the same behaviour. Resume best-effort; if it fails silently
  // we'd rather lose a cue than throw.
  if (ctx.state === "suspended") ctx.resume().catch(() => {});

  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  osc.type = "sine";
  osc.frequency.setValueAtTime(frequency, ctx.currentTime + startOffsetS);

  const t0 = ctx.currentTime + startOffsetS;
  gain.gain.setValueAtTime(0, t0);
  gain.gain.linearRampToValueAtTime(volume, t0 + ATTACK_S);
  gain.gain.setValueAtTime(volume, t0 + TONE_DURATION_S - RELEASE_S);
  gain.gain.linearRampToValueAtTime(0, t0 + TONE_DURATION_S);

  osc.connect(gain).connect(ctx.destination);
  osc.start(t0);
  osc.stop(t0 + TONE_DURATION_S + 0.02);
}

function playChord(frequencies: number[], volume: number, stagger = 0): void {
  for (let i = 0; i < frequencies.length; i += 1) {
    playTone(frequencies[i], i * stagger, volume);
  }
}

function resolveVolume(userVolume: number | undefined, enabled: boolean): number {
  if (!enabled) return 0;
  const v = typeof userVolume === "number" ? userVolume : DEFAULT_VOLUME;
  if (!Number.isFinite(v) || v <= 0) return 0;
  // Any value >1 means a scale drift somewhere upstream (e.g. a 0–100
  // slider leaking through unscaled). Clamping to 1 would blast the
  // user at full volume; fall back to DEFAULT_VOLUME instead.
  if (v > 1) return DEFAULT_VOLUME;
  return v;
}

export function playStartCue(volume?: number, enabled = true): void {
  const v = resolveVolume(volume, enabled);
  if (v === 0) return;
  playChord(START_FREQS, v);
}

export function playStopCue(volume?: number, enabled = true): void {
  const v = resolveVolume(volume, enabled);
  if (v === 0) return;
  playChord(STOP_FREQS, v);
}

export function playCompleteCue(volume?: number, enabled = true): void {
  const v = resolveVolume(volume, enabled);
  if (v === 0) return;
  // Slightly staggered arpeggio for the "done" cue — feels resolved
  // rather than alarm-like.
  playChord(COMPLETE_FREQS, v, 0.05);
}

export const DEFAULT_SOUND_VOLUME = DEFAULT_VOLUME;
