// audio.jsx — Full sound system. Initializes audio context on first user gesture.
// Provides: click, open, close, chime, aiPulse, gear hum (start/stop), loading music + ambient pad.

const AudioCtl = (() => {
  let ctx = null;
  let masterGain = null;
  let muted = false;          // start unmuted — but context can't play until first gesture
  let inited = false;
  let onChange = null;

  let hum = null;             // gear hum oscillator pack
  let humG = null;
  let pad = null;             // ambient pad timer
  let aiPulse = null;         // AI thinking interval

  const ensure = () => {
    if (!ctx) {
      try { ctx = new (window.AudioContext || window.webkitAudioContext)(); }
      catch (e) { return null; }
      masterGain = ctx.createGain();
      masterGain.gain.value = muted ? 0 : 0.85;
      masterGain.connect(ctx.destination);
    }
    if (ctx.state === "suspended") { try { ctx.resume(); } catch (e) {} }
    return ctx;
  };

  // first user gesture handler — call from anywhere; idempotent
  const initOnGesture = () => {
    if (inited) return;
    inited = true;
    ensure();
    if (onChange) onChange(muted, inited);
  };

  // ---------- SFX helpers ----------
  const blip = (freq, dur, type = "sine", vol = 0.06, attack = 0.005) => {
    if (muted || !inited) return;
    const c = ensure(); if (!c) return;
    const o = c.createOscillator(); const g = c.createGain();
    o.type = type; o.frequency.value = freq;
    g.gain.value = 0;
    g.gain.linearRampToValueAtTime(vol, c.currentTime + attack);
    g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur);
    o.connect(g); g.connect(masterGain);
    o.start(); o.stop(c.currentTime + dur + 0.05);
  };

  // Noise burst for whoosh
  const whoosh = (rev = false) => {
    if (muted || !inited) return;
    const c = ensure(); if (!c) return;
    const dur = 0.35;
    const bufSize = c.sampleRate * dur;
    const buf = c.createBuffer(1, bufSize, c.sampleRate);
    const data = buf.getChannelData(0);
    for (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1) * 0.6;
    const src = c.createBufferSource(); src.buffer = buf;
    const f = c.createBiquadFilter(); f.type = "bandpass"; f.Q.value = 0.8;
    const g = c.createGain(); g.gain.value = 0;
    if (rev) {
      f.frequency.setValueAtTime(2400, c.currentTime);
      f.frequency.exponentialRampToValueAtTime(400, c.currentTime + dur);
      g.gain.linearRampToValueAtTime(0.08, c.currentTime + 0.03);
      g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur);
    } else {
      f.frequency.setValueAtTime(400, c.currentTime);
      f.frequency.exponentialRampToValueAtTime(3000, c.currentTime + dur);
      g.gain.linearRampToValueAtTime(0.09, c.currentTime + 0.08);
      g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur);
    }
    src.connect(f); f.connect(g); g.connect(masterGain);
    src.start(); src.stop(c.currentTime + dur + 0.05);
  };

  const click = () => blip(2200, 0.035, "square", 0.018);
  const open = () => whoosh(false);
  const close = () => whoosh(true);
  const chime = () => {
    if (muted || !inited) return;
    const c = ensure(); if (!c) return;
    // Two-note chime: high gold tone
    blip(1320, 0.18, "sine", 0.05);
    setTimeout(() => blip(1760, 0.24, "sine", 0.04), 90);
  };

  // ---------- Gear hum ----------
  const startHum = () => {
    if (muted || !inited || hum) return;
    const c = ensure(); if (!c) return;
    hum = c.createOscillator();
    humG = c.createGain();
    hum.type = "sawtooth";
    hum.frequency.value = 95;
    humG.gain.value = 0;
    humG.gain.linearRampToValueAtTime(0.015, c.currentTime + 0.4);
    const lfo = c.createOscillator(); const lfoG = c.createGain();
    lfo.frequency.value = 4.5; lfoG.gain.value = 6;
    lfo.connect(lfoG); lfoG.connect(hum.frequency);
    const f = c.createBiquadFilter(); f.type = "bandpass"; f.frequency.value = 220; f.Q.value = 1.4;
    hum.connect(f); f.connect(humG); humG.connect(masterGain);
    lfo.start(); hum.start();
    hum._lfo = lfo;
  };
  const stopHum = () => {
    if (!hum || !ctx) { hum = null; return; }
    const node = hum, gain = humG;
    try {
      gain.gain.cancelScheduledValues(ctx.currentTime);
      gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.25);
    } catch (e) {}
    setTimeout(() => { try { node.stop(); node._lfo && node._lfo.stop(); } catch (e) {} }, 320);
    hum = null; humG = null;
  };

  // ---------- AI thinking pulse ----------
  const startAiPulse = () => {
    if (muted || !inited || aiPulse) return;
    const tick = () => blip(280, 0.18, "sine", 0.025);
    tick();
    aiPulse = setInterval(tick, 600);
  };
  const stopAiPulse = () => {
    if (aiPulse) { clearInterval(aiPulse); aiPulse = null; }
  };

  // ---------- Ambient pad / loading music ----------
  // Soft piano-like chord progression (sine + triangle), 4 chords looping.
  const CHORDS = [
    [196.00, 246.94, 293.66, 392.00],   // G major (G, B, D, G')
    [174.61, 220.00, 261.63, 349.23],   // F major
    [164.81, 196.00, 246.94, 329.63],   // E minor
    [146.83, 185.00, 220.00, 293.66],   // D minor
  ];
  const playChord = (notes, dur = 4) => {
    if (muted || !inited || !ctx) return;
    const c = ctx;
    notes.forEach((freq, i) => {
      // piano-ish: short attack + slow decay using two oscillators (fundamental + octave)
      const o1 = c.createOscillator(); o1.type = "sine"; o1.frequency.value = freq;
      const o2 = c.createOscillator(); o2.type = "triangle"; o2.frequency.value = freq * 2;
      const g = c.createGain();
      const peak = 0.022 - i * 0.003;
      g.gain.value = 0;
      g.gain.linearRampToValueAtTime(peak, c.currentTime + 0.05);  // quick attack like piano hammer
      g.gain.linearRampToValueAtTime(peak * 0.4, c.currentTime + dur * 0.4);
      g.gain.linearRampToValueAtTime(0, c.currentTime + dur);
      const g2 = c.createGain(); g2.gain.value = 0.18; // octave quieter
      o2.connect(g2); g2.connect(g);
      o1.connect(g);
      // soft lowpass
      const f = c.createBiquadFilter(); f.type = "lowpass"; f.frequency.value = 1800;
      g.connect(f); f.connect(masterGain);
      o1.start(); o2.start();
      o1.stop(c.currentTime + dur + 0.2);
      o2.stop(c.currentTime + dur + 0.2);
    });
    // soft strings: a slow sine pad on the root
    const s = c.createOscillator(); s.type = "sine"; s.frequency.value = notes[0] * 2;
    const sg = c.createGain(); sg.gain.value = 0;
    sg.gain.linearRampToValueAtTime(0.012, c.currentTime + 1.5);
    sg.gain.linearRampToValueAtTime(0, c.currentTime + dur);
    s.connect(sg); sg.connect(masterGain);
    s.start(); s.stop(c.currentTime + dur + 0.2);
  };

  const startPad = () => {
    if (pad || muted || !inited) return;
    ensure(); if (!ctx) return;
    let idx = 0;
    const step = () => {
      if (muted) { stopPad(); return; }
      playChord(CHORDS[idx % CHORDS.length], 4.2);
      idx++;
    };
    step();
    pad = setInterval(step, 4200);
  };
  const stopPad = (fade = true) => {
    if (pad) { clearInterval(pad); pad = null; }
    if (fade && masterGain && ctx) {
      try {
        masterGain.gain.cancelScheduledValues(ctx.currentTime);
        masterGain.gain.setValueAtTime(masterGain.gain.value, ctx.currentTime);
        masterGain.gain.linearRampToValueAtTime(muted ? 0 : 0.85, ctx.currentTime + 0.5);
      } catch (e) {}
    }
  };

  // master fade out music (called when dashboard appears)
  const fadeMusic = (sec = 1.4) => {
    if (!ctx || !masterGain) { stopPad(false); return; }
    // duck during fade, then stop
    const startVol = masterGain.gain.value;
    try {
      masterGain.gain.cancelScheduledValues(ctx.currentTime);
      masterGain.gain.setValueAtTime(startVol, ctx.currentTime);
      masterGain.gain.linearRampToValueAtTime(0.0001, ctx.currentTime + sec);
    } catch (e) {}
    setTimeout(() => {
      stopPad(false);
      if (masterGain && ctx) {
        try { masterGain.gain.setValueAtTime(muted ? 0 : 0.85, ctx.currentTime); } catch (e) {}
      }
    }, sec * 1000 + 100);
  };

  const setMuted = (m) => {
    muted = m;
    if (masterGain && ctx) {
      try {
        masterGain.gain.cancelScheduledValues(ctx.currentTime);
        masterGain.gain.linearRampToValueAtTime(muted ? 0 : 0.85, ctx.currentTime + 0.15);
      } catch (e) {}
    }
    if (muted) { stopHum(); stopAiPulse(); stopPad(false); }
    if (onChange) onChange(muted, inited);
  };
  const toggle = () => { setMuted(!muted); return muted; };
  const isMuted = () => muted;
  const isInited = () => inited;
  const onMuteChange = (fn) => { onChange = fn; };

  return {
    initOnGesture, ensure,
    click, open, close, chime,
    startHum, stopHum,
    startAiPulse, stopAiPulse,
    startPad, stopPad, fadeMusic,
    toggle, setMuted, isMuted, isInited, onMuteChange
  };
})();

window.AudioCtl = AudioCtl;
