// ee-audio.jsx — modern, classy, copyright-free music bed (Web Audio API).
// Filtered sawtooth chord stabs (warm deep-house tone) over a lush major-7th
// progression, with a soft four-on-the-floor kick, gentle hats, and a plucked
// sub bass. Nothing sustains, so there is no drone/hum. Synced to the timeline:
// plays only while the video plays, fades out in the final seconds, starts muted
// (browsers block autoplay sound) behind a click-to-unmute pill.
// Loaded as text/babel after ee-kit.jsx and animations.jsx.

function useMusicBed({ playing, time, duration, enabled, fadeOutAt }) {
  const ref = React.useRef(null);

  const ensure = React.useCallback(() => {
    if (ref.current) return ref.current;
    const AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return null;
    const ctx = new AC();

    const master = ctx.createGain();
    master.gain.value = 0; // start silent; ramp up
    const glue = ctx.createBiquadFilter();      // soft top to keep it smooth
    glue.type = 'lowpass'; glue.frequency.value = 4200; glue.Q.value = 0.2;
    const sub = ctx.createBiquadFilter();        // trim subsonic only
    sub.type = 'highpass'; sub.frequency.value = 32;
    master.connect(glue).connect(sub).connect(ctx.destination);

    // short noise buffer reused for hats
    const noiseBuf = ctx.createBuffer(1, ctx.sampleRate * 0.2, ctx.sampleRate);
    const nd = noiseBuf.getChannelData(0);
    for (let i = 0; i < nd.length; i++) nd[i] = Math.random() * 2 - 1;

    // Lush, inviting progression: Cmaj7 · Em7 · Am7 · Fmaj7 (one bar each).
    const chords = [
      { root: 65.41,  tones: [261.63, 329.63, 392.00, 493.88] }, // Cmaj7
      { root: 82.41,  tones: [246.94, 329.63, 392.00, 493.88] }, // Em7
      { root: 55.00,  tones: [261.63, 329.63, 392.00, 440.00] }, // Am7
      { root: 87.31,  tones: [261.63, 329.63, 349.23, 440.00] }, // Fmaj7
    ];

    // Filtered saw chord stab — the warm, modern deep-house texture.
    const stab = (freqs, when, vel = 1) => {
      const amp = ctx.createGain();
      amp.gain.setValueAtTime(0, when);
      amp.gain.linearRampToValueAtTime(0.05 * vel, when + 0.014);
      amp.gain.exponentialRampToValueAtTime(0.0005, when + 0.62);
      const flt = ctx.createBiquadFilter();
      flt.type = 'lowpass'; flt.Q.value = 5;
      flt.frequency.setValueAtTime(2400, when);          // opens bright…
      flt.frequency.exponentialRampToValueAtTime(520, when + 0.5); // …then closes
      flt.connect(amp).connect(master);
      freqs.forEach((f) => {
        [-5, 5].forEach((cents) => {                     // detuned pair for width
          const o = ctx.createOscillator();
          o.type = 'sawtooth';
          o.frequency.value = f;
          o.detune.value = cents;
          o.connect(flt);
          o.start(when); o.stop(when + 0.7);
        });
      });
    };

    // Plucked sub bass (saw through its own lowpass).
    const bass = (freq, when, vel = 1) => {
      const o = ctx.createOscillator();
      o.type = 'sawtooth'; o.frequency.value = freq;
      const f = ctx.createBiquadFilter();
      f.type = 'lowpass'; f.frequency.value = 320; f.Q.value = 1.2;
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0001, when);
      g.gain.linearRampToValueAtTime(0.16 * vel, when + 0.016);
      g.gain.exponentialRampToValueAtTime(0.0006, when + 0.46);
      o.connect(f).connect(g).connect(master);
      o.start(when); o.stop(when + 0.5);
    };

    // Soft kick: sine with a fast pitch drop.
    const kick = (when, vel = 1) => {
      const o = ctx.createOscillator();
      o.type = 'sine';
      o.frequency.setValueAtTime(135, when);
      o.frequency.exponentialRampToValueAtTime(46, when + 0.11);
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0001, when);
      g.gain.linearRampToValueAtTime(0.28 * vel, when + 0.01);
      g.gain.exponentialRampToValueAtTime(0.0005, when + 0.26);
      o.connect(g).connect(master);
      o.start(when); o.stop(when + 0.3);
    };

    // Gentle closed hat: short filtered noise.
    const hat = (when, vel = 1) => {
      const n = ctx.createBufferSource(); n.buffer = noiseBuf;
      const f = ctx.createBiquadFilter(); f.type = 'highpass'; f.frequency.value = 7500;
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0001, when);
      g.gain.linearRampToValueAtTime(0.035 * vel, when + 0.004);
      g.gain.exponentialRampToValueAtTime(0.0004, when + 0.05);
      n.connect(f).connect(g).connect(master);
      n.start(when); n.stop(when + 0.07);
    };

    // Lookahead scheduler off the audio clock.
    const tempo = 110;
    const stepDur = 60 / tempo / 2;   // eighth notes, 8 per bar
    const sched = { step: 0, nextTime: 0 };
    const lookahead = 0.13;
    const node = { ctx, master, active: false };

    const tick = () => {
      if (!node.active) return;
      while (sched.nextTime < ctx.currentTime + lookahead) {
        const t = Math.max(sched.nextTime, ctx.currentTime + 0.02);
        const bar = Math.floor(sched.step / 8);
        const s = sched.step % 8;
        const chord = chords[bar % chords.length];
        // four-on-the-floor kick on the quarter notes
        if (s % 2 === 0) kick(t, s === 0 ? 1 : 0.92);
        // offbeat hats
        if (s % 2 === 1) hat(t, s === 5 ? 0.8 : 1);
        // syncopated chord stabs (the "and" of beats) for a modern push-pull
        if (s === 0 || s === 3 || s === 6) stab(chord.tones, t, s === 0 ? 1 : 0.82);
        // bass: root on the one, octave lift on the "and" of beat 2
        if (s === 0) bass(chord.root, t, 1);
        else if (s === 3) bass(chord.root * 2, t, 0.7);
        sched.nextTime += stepDur;
        sched.step++;
      }
    };
    node.timer = setInterval(tick, 25);
    node.resetClock = () => { sched.nextTime = ctx.currentTime + 0.06; };

    ref.current = node;
    return ref.current;
  }, []);

  // Drive gain from playing / mute / fade-out window.
  React.useEffect(() => {
    const node = ref.current;
    if (!node) return;
    const { ctx, master } = node;
    const now = ctx.currentTime;
    const live = enabled && playing;
    if (live && !node.active && node.resetClock) node.resetClock();
    node.active = live;
    let target = 0;
    if (live) {
      const remaining = duration - time;
      const fadeWin = duration - fadeOutAt;
      target = remaining <= fadeWin ? 0.6 * clamp(remaining / fadeWin, 0, 1) : 0.6;
    }
    master.gain.cancelScheduledValues(now);
    master.gain.setTargetAtTime(target, now, live ? 0.25 : 0.3);
    if (live && ctx.state === 'suspended') ctx.resume();
  }, [playing, time, duration, enabled, fadeOutAt]);

  return { ensure };
}

// Click-to-unmute pill, bottom-right, above the playback bar.
function AudioControl() {
  const { playing, time, duration } = useTimeline();
  const [on, setOn] = React.useState(false);
  const { ensure } = useMusicBed({ playing, time, duration, enabled: on, fadeOutAt: duration - 2 });

  const toggle = () => {
    const node = ensure();
    if (node && node.ctx.state === 'suspended') node.ctx.resume();
    setOn(v => !v);
  };

  return (
    <button onClick={toggle} title="Toggle music"
      style={{
        position: 'absolute', right: 24, bottom: 92, zIndex: 50,
        display: 'flex', alignItems: 'center', gap: 10,
        background: on ? EE.mint : EE.surface,
        color: EE.teal,
        border: `1px solid ${on ? EE.mint : EE.border}`,
        borderRadius: 999, padding: '10px 16px 10px 12px', cursor: 'pointer',
        fontFamily: EE.body, fontWeight: 600, fontSize: 14, letterSpacing: '0.01em',
        boxShadow: on ? '0 6px 18px rgba(101,236,172,0.4)' : '0 4px 12px rgba(4,69,76,0.1)',
        transition: 'background 200ms cubic-bezier(0.2,0.7,0.2,1), box-shadow 200ms',
      }}>
      <SoundIcon on={on} />
      {on ? 'Music on' : 'Play music'}
    </button>
  );
}

function SoundIcon({ on }) {
  return (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={EE.teal}
      strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M5 9v6h4l5 4V5L9 9z" />
      {on ? (
        <g>
          <path d="M16.5 8.5a5 5 0 0 1 0 7" />
          <path d="M19 6a8 8 0 0 1 0 12" />
        </g>
      ) : (
        <path d="M22 9l-5 6M17 9l5 6" />
      )}
    </svg>
  );
}

Object.assign(window, { AudioControl });
