/**
 * In-app notification chime via Web Audio (no asset file required).
 *
 * Browsers require a user gesture before AudioContext can output sound. We
 * unlock on pointer/key activity and resume again whenever the tab becomes
 * visible, so later message/call tones are not stuck suspended.
 */

let ctx: AudioContext | null = null;
let unlockBound = false;

export function getNotifyAudioContext(): AudioContext | null {
  if (typeof window === "undefined") return null;
  const AC = window.AudioContext || (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
  if (!AC) return null;
  if (!ctx) ctx = new AC();
  return ctx;
}

function resumeContext(audio: AudioContext): Promise<void> {
  if (audio.state !== "suspended") return Promise.resolve();
  return audio.resume().catch(() => undefined);
}

/** Call from a user gesture so browsers allow later autoplay. Safe to call often. */
export function unlockNotifySound() {
  const audio = getNotifyAudioContext();
  if (!audio) return;
  void resumeContext(audio);
}

/**
 * Keep the shared AudioContext runnable for the life of the page.
 * Idempotent — RealtimeProvider calls this once on mount.
 */
export function bindNotifySoundUnlock() {
  if (typeof window === "undefined" || unlockBound) return;
  unlockBound = true;
  const unlock = () => unlockNotifySound();
  window.addEventListener("pointerdown", unlock);
  window.addEventListener("keydown", unlock);
  window.addEventListener("touchstart", unlock, { passive: true });
  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "visible") unlockNotifySound();
  });
}

export type NotifySoundKind = "default" | "cheerful" | "short";

export function playNotifySound(kind: NotifySoundKind = "default") {
  try {
    const audio = getNotifyAudioContext();
    if (!audio) return;
    void resumeContext(audio).then(() => {
      if (audio.state === "suspended") return;
      if (kind === "cheerful") playCheerfulTone(audio);
      else if (kind === "short") playShortTone(audio);
      else playMessageTone(audio);
    });
  } catch {
    // Ignore autoplay / AudioContext failures.
  }
}

function playPings(audio: AudioContext, pings: Array<{ at: number; freq: number; dur: number; volume?: number }>) {
  const now = audio.currentTime;
  for (const ping of pings) {
    const t = now + ping.at;
    const gain = audio.createGain();
    gain.connect(audio.destination);
    gain.gain.setValueAtTime(0.0001, t);
    gain.gain.exponentialRampToValueAtTime(ping.volume ?? 0.18, t + 0.015);
    gain.gain.exponentialRampToValueAtTime(0.0001, t + ping.dur);

    const osc = audio.createOscillator();
    osc.type = "sine";
    osc.frequency.setValueAtTime(ping.freq, t);
    osc.connect(gain);
    osc.start(t);
    osc.stop(t + ping.dur + 0.02);
  }
}

/** Distinct SMS-style double ping for incoming messages. */
function playMessageTone(audio: AudioContext) {
  playPings(audio, [
    { at: 0, freq: 1200, dur: 0.12 },
    { at: 0.14, freq: 1500, dur: 0.14 },
  ]);
}

/** Rising major triad — a short "smiley" for a new task landing on you. */
function playCheerfulTone(audio: AudioContext) {
  playPings(audio, [
    { at: 0, freq: 523, dur: 0.11, volume: 0.16 },
    { at: 0.1, freq: 659, dur: 0.11, volume: 0.16 },
    { at: 0.2, freq: 784, dur: 0.16, volume: 0.18 },
  ]);
}

/** Two short pings — shorter than the new-assignment chime, still audible. */
function playShortTone(audio: AudioContext) {
  playPings(audio, [
    { at: 0, freq: 1480, dur: 0.09, volume: 0.2 },
    { at: 0.11, freq: 1760, dur: 0.1, volume: 0.2 },
  ]);
}
