/**
 * Call ringtone / ringback via Web Audio (no asset files).
 *
 * - Incoming: repeating two-tone phone-style ring
 * - Outbound ringback: softer repeating cadence while waiting for answer
 *
 * Uses the shared notify AudioContext so a prior click in the app unlocks
 * ringtone playback under browser autoplay rules.
 */

import { getNotifyAudioContext, unlockNotifySound } from "./notify-sound";

let incomingTimer: number | null = null;
let outboundTimer: number | null = null;
let stopIncomingBurst: (() => void) | null = null;
let stopOutboundBurst: (() => void) | null = null;

async function ensureRunning(): Promise<AudioContext | null> {
  unlockNotifySound();
  const audio = getNotifyAudioContext();
  if (!audio) return null;
  if (audio.state === "suspended") {
    try {
      await audio.resume();
    } catch {
      return null;
    }
  }
  return audio;
}

function playBurst(
  audio: AudioContext,
  opts: { freqs: number[]; duration: number; gap: number; volume: number; count: number },
): () => void {
  const oscillators: OscillatorNode[] = [];
  const now = audio.currentTime;
  let t = now;

  for (let i = 0; i < opts.count; i++) {
    for (const freq of opts.freqs) {
      const gain = audio.createGain();
      gain.connect(audio.destination);
      gain.gain.setValueAtTime(0.0001, t);
      gain.gain.exponentialRampToValueAtTime(opts.volume, t + 0.02);
      gain.gain.setValueAtTime(opts.volume, t + opts.duration - 0.04);
      gain.gain.exponentialRampToValueAtTime(0.0001, t + opts.duration);

      const osc = audio.createOscillator();
      osc.type = "sine";
      osc.frequency.setValueAtTime(freq, t);
      osc.connect(gain);
      osc.start(t);
      osc.stop(t + opts.duration + 0.02);
      oscillators.push(osc);
    }
    t += opts.duration + opts.gap;
  }

  return () => {
    for (const osc of oscillators) {
      try {
        osc.stop();
      } catch {
        // already stopped
      }
    }
  };
}

/** Start looping ringtone for an incoming call. Safe to call repeatedly. */
export function startIncomingRingtone() {
  if (incomingTimer != null) return;
  // Claim the slot before the async resume so overlapping starts don't double-ring.
  incomingTimer = -1;
  void (async () => {
    const audio = await ensureRunning();
    if (!audio) {
      incomingTimer = null;
      return;
    }
    if (incomingTimer !== -1) return;

    const ringOnce = () => {
      stopIncomingBurst?.();
      // Classic double-ring: two short bursts, then a pause before the next cycle.
      stopIncomingBurst = playBurst(audio, {
        freqs: [440, 480],
        duration: 0.4,
        gap: 0.2,
        volume: 0.14,
        count: 2,
      });
    };

    ringOnce();
    incomingTimer = window.setInterval(ringOnce, 2800);
  })();
}

export function stopIncomingRingtone() {
  if (incomingTimer != null && incomingTimer !== -1) {
    window.clearInterval(incomingTimer);
  }
  incomingTimer = null;
  stopIncomingBurst?.();
  stopIncomingBurst = null;
}

/** Soft ringback while you are dialing and waiting for an answer. */
export function startOutboundRingback() {
  if (outboundTimer != null) return;
  outboundTimer = -1;
  void (async () => {
    const audio = await ensureRunning();
    if (!audio) {
      outboundTimer = null;
      return;
    }
    if (outboundTimer !== -1) return;

    const ringOnce = () => {
      stopOutboundBurst?.();
      stopOutboundBurst = playBurst(audio, {
        freqs: [425],
        duration: 1.0,
        gap: 0,
        volume: 0.08,
        count: 1,
      });
    };

    ringOnce();
    outboundTimer = window.setInterval(ringOnce, 4000);
  })();
}

export function stopOutboundRingback() {
  if (outboundTimer != null && outboundTimer !== -1) {
    window.clearInterval(outboundTimer);
  }
  outboundTimer = null;
  stopOutboundBurst?.();
  stopOutboundBurst = null;
}

export function stopAllCallSounds() {
  stopIncomingRingtone();
  stopOutboundRingback();
}
