/* ============================================================
   Collab demo — ambient audio bed
   Procedural — no external file, no network call. Web Audio API
   builds a soft, slow-moving pad that loops indefinitely while
   the video plays. Pauses when the user pauses the video.

   Why procedural? An embedded base64 MP3 would balloon the
   prototype by ~500KB and tie us to a single loop length. A pad
   built from 4 detuned oscillators + a slow LFO is ~60 lines,
   weighs nothing, and the texture is exactly the Apple-keynote
   "soft underneath" we want.
   ============================================================ */

(function () {
  /* The chord progression — a slow, suspended-feel cycle that
     never resolves. Each entry: { root in Hz, duration in seconds }.
     Sum of durations = 30, matching the video length so the bed
     loops in sync with the timeline. */
  const CHORDS = [
    { root: 130.81, dur: 7.5 },  // C3 — open
    { root: 116.54, dur: 7.5 },  // Bb2 — pivot
    { root: 110.00, dur: 7.5 },  // A2  — softer
    { root: 174.61, dur: 7.5 },  // F3  — lift into the CTA
  ];

  /* Voices: stacked at root, +5th, +octave, +major-third (-12) so the
     pad has body without becoming a strident chord. */
  const VOICES = [
    { mul: 1.0,  type: "sine",     gain: 0.18, detune:  -3 },
    { mul: 1.5,  type: "sine",     gain: 0.10, detune:   2 },
    { mul: 2.0,  type: "triangle", gain: 0.06, detune:  -1 },
    { mul: 1.25, type: "sine",     gain: 0.05, detune:   4 },
  ];

  let ctx = null;
  let master = null;        /* master GainNode — toggled by play/pause */
  let voices = [];          /* [{ osc, gain }] */
  let lfo = null;
  let started = false;
  let muted = false;
  let userPlaying = true;

  function ensureContext() {
    if (ctx) return ctx;
    const AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return null;
    ctx = new AC();
    return ctx;
  }

  function start() {
    if (started) return;
    if (!ensureContext()) return;
    started = true;

    /* Master chain: voices → lowpass → masterGain → destination */
    const lp = ctx.createBiquadFilter();
    lp.type = "lowpass";
    lp.frequency.value = 900;
    lp.Q.value = 0.7;

    master = ctx.createGain();
    master.gain.value = 0; /* fade in below */

    lp.connect(master);
    master.connect(ctx.destination);

    /* Slow LFO sweeping the filter cutoff for movement */
    lfo = ctx.createOscillator();
    lfo.type = "sine";
    lfo.frequency.value = 0.07; /* ~14s cycle */
    const lfoGain = ctx.createGain();
    lfoGain.gain.value = 350;   /* +/- 350Hz around 900 */
    lfo.connect(lfoGain);
    lfoGain.connect(lp.frequency);
    lfo.start();

    /* Build voices */
    voices = VOICES.map((v) => {
      const osc = ctx.createOscillator();
      osc.type = v.type;
      osc.detune.value = v.detune;
      const g = ctx.createGain();
      g.gain.value = v.gain;
      osc.connect(g);
      g.connect(lp);
      osc.start();
      return { osc, gain: g, conf: v };
    });

    /* Schedule the chord progression on loop */
    scheduleChords();

    /* Fade in */
    master.gain.setValueAtTime(0, ctx.currentTime);
    master.gain.linearRampToValueAtTime(muted ? 0 : 0.6, ctx.currentTime + 1.0);
  }

  function scheduleChords() {
    if (!ctx) return;
    const now = ctx.currentTime;
    const totalLoop = CHORDS.reduce((s, c) => s + c.dur, 0); /* 30s */
    /* Schedule 8 loops ahead — beyond a reasonable demo viewing window.
       Browsers handle long automation timelines fine; the cost is
       trivial vs maintaining a setInterval to re-schedule. */
    const LOOPS = 8;
    let cursor = now;
    for (let l = 0; l < LOOPS; l++) {
      for (const c of CHORDS) {
        voices.forEach(({ osc, conf }) => {
          /* Ramp pitch over 1.5s into each chord change — gives the bed
             its glassy "pad" quality. */
          osc.frequency.setValueAtTime(osc.frequency.value || c.root * conf.mul, cursor);
          osc.frequency.linearRampToValueAtTime(c.root * conf.mul, cursor + 1.5);
        });
        cursor += c.dur;
      }
    }
  }

  function setPlaying(p) {
    userPlaying = p;
    if (!started) return;
    const target = (p && !muted) ? 0.6 : 0;
    master.gain.cancelScheduledValues(ctx.currentTime);
    master.gain.linearRampToValueAtTime(target, ctx.currentTime + 0.35);
  }

  function setMuted(m) {
    muted = m;
    if (!started) return;
    const target = (userPlaying && !muted) ? 0.6 : 0;
    master.gain.cancelScheduledValues(ctx.currentTime);
    master.gain.linearRampToValueAtTime(target, ctx.currentTime + 0.25);
  }

  /* Public surface */
  window.CollabDemoAudio = {
    start,
    setPlaying,
    setMuted,
    isMuted: () => muted,
    isStarted: () => started,
    /* sfx(kind) — fire a short generated sound. kind:
         "click"  — soft UI click (filtered noise + tick)
         "whoosh" — sweep, for elements flying in
         "ding"   — short tonal cue, e.g. lock or notification
         "pop"    — tiny percussive pop for chip appearance
       All are inaudible until master is unmuted; ducked under the pad.
       Safe to call before start() — silently no-ops. */
    sfx,
  };

  function sfx(kind) {
    if (!ctx || muted) return;
    const t0 = ctx.currentTime;
    const masterSfx = ctx.createGain();
    masterSfx.gain.value = 0.18; /* lower than the pad so SFX never dominate */
    masterSfx.connect(ctx.destination);

    if (kind === "click") {
      /* Filtered short noise burst */
      const buf = ctx.createBuffer(1, 1024, ctx.sampleRate);
      const ch = buf.getChannelData(0);
      for (let i = 0; i < ch.length; i++) ch[i] = (Math.random() * 2 - 1) * 0.5;
      const noise = ctx.createBufferSource(); noise.buffer = buf;
      const bp = ctx.createBiquadFilter();
      bp.type = "bandpass"; bp.frequency.value = 2200; bp.Q.value = 1.2;
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.6, t0);
      g.gain.exponentialRampToValueAtTime(0.001, t0 + 0.08);
      noise.connect(bp); bp.connect(g); g.connect(masterSfx);
      noise.start(t0); noise.stop(t0 + 0.08);
    } else if (kind === "whoosh") {
      /* Frequency-swept noise — short rising whoosh */
      const buf = ctx.createBuffer(1, ctx.sampleRate * 0.5, ctx.sampleRate);
      const ch = buf.getChannelData(0);
      for (let i = 0; i < ch.length; i++) ch[i] = (Math.random() * 2 - 1) * 0.4;
      const noise = ctx.createBufferSource(); noise.buffer = buf;
      const bp = ctx.createBiquadFilter(); bp.type = "bandpass"; bp.Q.value = 4;
      bp.frequency.setValueAtTime(400, t0);
      bp.frequency.exponentialRampToValueAtTime(2400, t0 + 0.4);
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0, t0);
      g.gain.linearRampToValueAtTime(0.5, t0 + 0.12);
      g.gain.exponentialRampToValueAtTime(0.001, t0 + 0.45);
      noise.connect(bp); bp.connect(g); g.connect(masterSfx);
      noise.start(t0); noise.stop(t0 + 0.45);
    } else if (kind === "ding") {
      /* Two-oscillator soft bell — a fifth apart for richness */
      [880, 1320].forEach((f, i) => {
        const o = ctx.createOscillator(); o.type = "sine"; o.frequency.value = f;
        const g = ctx.createGain();
        g.gain.setValueAtTime(0.0, t0);
        g.gain.linearRampToValueAtTime(i === 0 ? 0.45 : 0.22, t0 + 0.01);
        g.gain.exponentialRampToValueAtTime(0.001, t0 + 0.7);
        o.connect(g); g.connect(masterSfx);
        o.start(t0); o.stop(t0 + 0.7);
      });
    } else if (kind === "pop") {
      /* Quick pitch-dropping blip */
      const o = ctx.createOscillator(); o.type = "triangle";
      o.frequency.setValueAtTime(620, t0);
      o.frequency.exponentialRampToValueAtTime(220, t0 + 0.12);
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0, t0);
      g.gain.linearRampToValueAtTime(0.35, t0 + 0.01);
      g.gain.exponentialRampToValueAtTime(0.001, t0 + 0.18);
      o.connect(g); g.connect(masterSfx);
      o.start(t0); o.stop(t0 + 0.18);
    }
  }
})();

/* ============================================================
   React hook + UI: ties the audio bed to the timeline's play
   state and renders a mute toggle in the corner of the canvas.
   ============================================================ */
const { useEffect: audioUseEffect, useState: audioUseState } = React;

function AmbientAudioController() {
  const { playing } = window.useTimeline();
  const [muted, setMuted] = audioUseState(false);
  const [armed, setArmed] = audioUseState(false);

  /* Browsers require a user gesture to start the AudioContext.
     We attach a one-shot listener that arms the audio on any
     interaction inside the page (click / keydown). Once armed,
     subsequent play/pause toggles are silent transitions. */
  audioUseEffect(() => {
    if (armed) return;
    const arm = () => {
      window.CollabDemoAudio.start();
      window.CollabDemoAudio.setPlaying(playing);
      setArmed(true);
      window.removeEventListener("click", arm);
      window.removeEventListener("keydown", arm);
    };
    window.addEventListener("click", arm);
    window.addEventListener("keydown", arm);
    return () => {
      window.removeEventListener("click", arm);
      window.removeEventListener("keydown", arm);
    };
  }, [armed, playing]);

  audioUseEffect(() => {
    if (!armed) return;
    window.CollabDemoAudio.setPlaying(playing);
  }, [playing, armed]);

  audioUseEffect(() => {
    if (!armed) return;
    window.CollabDemoAudio.setMuted(muted);
  }, [muted, armed]);

  return (
    <>
      {!armed && (
        <div
          style={{
            position: "absolute", left: "50%", top: 80,
            transform: "translateX(-50%)",
            display: "inline-flex", alignItems: "center", gap: 10,
            padding: "10px 18px", borderRadius: 9999,
            background: "rgba(16,24,40,0.86)",
            backdropFilter: "blur(12px)",
            WebkitBackdropFilter: "blur(12px)",
            color: "#fff",
            font: "500 14px/1 'Inter', sans-serif",
            boxShadow: "0 12px 32px rgba(0,0,0,0.32)",
            zIndex: 100,
            pointerEvents: "none",
            animation: "armPulse 2s ease-in-out infinite",
          }}
        >
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
            <path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
            <path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
          </svg>
          <span>Click anywhere to enable sound</span>
          <style>{`
            @keyframes armPulse {
              0%, 100% { transform: translateX(-50%) translateY(0); opacity: 1; }
              50%      { transform: translateX(-50%) translateY(-3px); opacity: 0.85; }
            }
          `}</style>
        </div>
      )}
      <button
        type="button"
        onClick={(e) => { e.stopPropagation(); setMuted(m => !m); }}
        title={!armed ? "Tap anywhere to enable audio" : muted ? "Unmute ambient" : "Mute ambient"}
        style={{
          position: "absolute", top: 24, right: 24,
          width: 44, height: 44, borderRadius: 9999,
          background: armed ? "rgba(16,24,40,0.85)" : "rgba(16,24,40,0.55)",
          backdropFilter: "blur(12px)",
          WebkitBackdropFilter: "blur(12px)",
          color: "#fff", border: 0, cursor: "pointer",
          display: "grid", placeItems: "center",
          boxShadow: "0 8px 24px rgba(0,0,0,0.24)",
          zIndex: 100,
          transition: "background .15s, transform .15s",
        }}
      >
        {muted || !armed ? (
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
            <line x1="23" y1="9" x2="17" y2="15"/>
            <line x1="17" y1="9" x2="23" y2="15"/>
          </svg>
        ) : (
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
            <path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
            <path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
          </svg>
        )}
      </button>
    </>
  );
}

/* ============================================================
   SFX Scheduler — fires sound effects at fixed timestamps
   matching the demo's visual beats. The Notion timeline runs
   60s, the Apple timeline runs 30s — pass `events` to override.

   Each event: { at: seconds, kind: "click"|"whoosh"|"ding"|"pop" }.
   Events fire once per loop. Uses a ref to dedupe across re-renders.
   ============================================================ */
function SfxScheduler({ events = [] }) {
  const { time, duration } = window.useTimeline();
  const playedRef = React.useRef(new Set());
  const lastLoopRef = React.useRef(0);

  React.useEffect(() => {
    /* Detect loop boundary — if time jumped backward, clear played set */
    if (time < lastLoopRef.current - 0.5) {
      playedRef.current = new Set();
    }
    lastLoopRef.current = time;

    /* Fire any event whose timestamp is within the last 200ms and
       hasn't fired yet on this loop. */
    for (const ev of events) {
      const key = `${ev.at}:${ev.kind}`;
      if (playedRef.current.has(key)) continue;
      if (time >= ev.at && time < ev.at + 0.3) {
        playedRef.current.add(key);
        try { window.CollabDemoAudio.sfx(ev.kind); } catch {}
      }
    }
  }, [time, events]);

  return null;
}

window.SfxScheduler = SfxScheduler;

window.AmbientAudioController = AmbientAudioController;
