// audio.jsx — Bell sound toggle + best possible auto start on scroll/gesture

const MASTER_LEVEL = 5.5;

function AudioToggle() {
  const [on, setOn] = React.useState(false);
  const [userMuted, setUserMuted] = React.useState(false);

  const ctxRef = React.useRef(null);
  const masterRef = React.useRef(null);
  const stopFnRef = React.useRef(null);
  const presetRef = React.useRef(null);

  const ensureCtx = () => {
    if (ctxRef.current) return ctxRef.current;

    const AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return null;

    const ctx = new AC();

    const master = ctx.createGain();
    master.gain.value = 0;

    const preGain = ctx.createGain();
    preGain.gain.value = 1.6;

    const comp = ctx.createDynamicsCompressor();
    comp.threshold.value = -22;
    comp.knee.value = 18;
    comp.ratio.value = 10;
    comp.attack.value = 0.005;
    comp.release.value = 0.2;

    const limiter = ctx.createDynamicsCompressor();
    limiter.threshold.value = -2;
    limiter.knee.value = 0;
    limiter.ratio.value = 20;
    limiter.attack.value = 0.001;
    limiter.release.value = 0.1;

    master.connect(preGain).connect(comp).connect(limiter).connect(ctx.destination);

    ctxRef.current = ctx;
    masterRef.current = master;

    return ctx;
  };

  const buildPreset = (id) => {
    const ctx = ensureCtx();
    if (!ctx) return;

    if (stopFnRef.current) stopFnRef.current();

    presetRef.current = id;

    if (id === "pulse") {
      stopFnRef.current = buildPulse(ctx, masterRef.current);
    }
  };

  const fadeTo = (target, secs = 1.2) => {
    const ctx = ctxRef.current;
    const master = masterRef.current;

    if (!ctx || !master) return;

    master.gain.cancelScheduledValues(ctx.currentTime);
    master.gain.setValueAtTime(master.gain.value, ctx.currentTime);
    master.gain.linearRampToValueAtTime(
      Math.max(0.0001, target),
      ctx.currentTime + secs
    );
  };

  const startSound = async () => {
    if (userMuted) return false;

    const ctx = ensureCtx();
    if (!ctx) return false;

    try {
      if (ctx.state !== "running") {
        await ctx.resume();
      }

      if (ctx.state !== "running") {
        setOn(false);
        return false;
      }

      if (!presetRef.current) {
        buildPreset("pulse");
      }

      fadeTo(MASTER_LEVEL, 1.4);
      setOn(true);
      return true;
    } catch (err) {
      console.log("Audio blocked:", err);
      setOn(false);
      return false;
    }
  };

  React.useEffect(() => {
    if (on || userMuted) return;

    let fired = false;

    const tryStart = async () => {
      if (fired) return;

      fired = true;
      const success = await startSound();

      if (!success) {
        fired = false;
      }
    };

    window.addEventListener("scroll", tryStart, { passive: true });
    window.addEventListener("wheel", tryStart, { passive: true });
    window.addEventListener("touchstart", tryStart, { passive: true });
    window.addEventListener("pointerdown", tryStart);
    window.addEventListener("click", tryStart);
    window.addEventListener("keydown", tryStart);

    return () => {
      window.removeEventListener("scroll", tryStart);
      window.removeEventListener("wheel", tryStart);
      window.removeEventListener("touchstart", tryStart);
      window.removeEventListener("pointerdown", tryStart);
      window.removeEventListener("click", tryStart);
      window.removeEventListener("keydown", tryStart);
    };
  }, [on, userMuted]);

  React.useEffect(() => {
    const onVisibility = () => {
      const ctx = ctxRef.current;
      if (!ctx) return;

      if (document.hidden) {
        ctx.suspend();
      } else if (on) {
        ctx.resume();
      }
    };

    document.addEventListener("visibilitychange", onVisibility);
    return () => document.removeEventListener("visibilitychange", onVisibility);
  }, [on]);

  const toggleMute = async () => {
    if (on) {
      fadeTo(0.0001, 0.6);
      setOn(false);
      setUserMuted(true);
    } else {
      setUserMuted(false);
      await startSound();
    }
  };

  return (
    <button
      className={"audio-toggle" + (on ? " on" : "")}
      onClick={toggleMute}
      aria-pressed={on}
      title={on ? "Sound on · click to mute" : "Sound off · click to play"}
    >
      <span className="audio-bell" aria-hidden="true">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
          <path d="M12 3a4 4 0 0 0-4 4v2.2c0 .7-.2 1.4-.6 2l-1 1.7c-.4.7.1 1.6.9 1.6h9.4c.8 0 1.3-.9.9-1.6l-1-1.7c-.4-.6-.6-1.3-.6-2V7a4 4 0 0 0-4-4Z" />
          <path d="M10 18a2 2 0 0 0 4 0" />
        </svg>
      </span>
    </button>
  );
}

// ─── PRESETS ─────────────────────────────────────────────

function buildPulse(ctx, master) {
  const nodes = [];

  [110, 164.8, 220].forEach((f) => {
    const osc = ctx.createOscillator();
    osc.type = "sine";
    osc.frequency.value = f;

    const g = ctx.createGain();
    g.gain.value = 0.08;

    osc.connect(g).connect(master);
    osc.start();

    nodes.push(osc);
  });

  const bpm = 60;
  const beat = 60 / bpm;
  let cancelled = false;

  const playKick = (t, vel = 0.7) => {
    const osc = ctx.createOscillator();
    osc.type = "sine";
    osc.frequency.setValueAtTime(110, t);
    osc.frequency.exponentialRampToValueAtTime(45, t + 0.12);

    const g = ctx.createGain();
    g.gain.setValueAtTime(0, t);
    g.gain.linearRampToValueAtTime(vel, t + 0.005);
    g.gain.exponentialRampToValueAtTime(0.0001, t + 0.32);

    osc.connect(g).connect(master);
    osc.start(t);
    osc.stop(t + 0.4);
  };

  const playTick = (t) => {
    const noise = makeNoiseShort(ctx, 0.06);

    const filt = ctx.createBiquadFilter();
    filt.type = "highpass";
    filt.frequency.value = 7000;

    const g = ctx.createGain();
    g.gain.setValueAtTime(0, t);
    g.gain.linearRampToValueAtTime(0.22, t + 0.003);
    g.gain.exponentialRampToValueAtTime(0.0001, t + 0.05);

    noise.connect(filt).connect(g).connect(master);
    noise.start(t);
    noise.stop(t + 0.07);
  };

  let step = 0;
  const lookAhead = 0.4;
  const interval = 0.1;
  const startTime = ctx.currentTime + 0.4;

  const tick = () => {
    if (cancelled) return;

    while (startTime + step * beat < ctx.currentTime + lookAhead) {
      const t = startTime + step * beat;
      const inBar = step % 4;

      if (inBar === 0) {
        playKick(t, 0.85);
        playKick(t + 0.18, 0.45);
      }

      if (inBar === 2) {
        playKick(t, 0.65);
      }

      playTick(t + beat / 2);
      step++;
    }

    if (!cancelled) {
      setTimeout(tick, interval * 1000);
    }
  };

  tick();

  const noise = makeNoise(ctx);

  const filt = ctx.createBiquadFilter();
  filt.type = "lowpass";
  filt.frequency.value = 600;

  const nGain = ctx.createGain();
  nGain.gain.value = 0.025;

  noise.connect(filt).connect(nGain).connect(master);
  noise.start();

  nodes.push(noise);

  return () => {
    cancelled = true;
    stopNodes(nodes);
  };
}

function makeNoise(ctx) {
  const buf = ctx.createBuffer(1, ctx.sampleRate * 4, ctx.sampleRate);
  const data = buf.getChannelData(0);

  for (let i = 0; i < data.length; i++) {
    data[i] = Math.random() - 0.5;
  }

  const node = ctx.createBufferSource();
  node.buffer = buf;
  node.loop = true;

  return node;
}

function makeNoiseShort(ctx, dur) {
  const buf = ctx.createBuffer(
    1,
    Math.max(1, ctx.sampleRate * dur),
    ctx.sampleRate
  );

  const data = buf.getChannelData(0);

  for (let i = 0; i < data.length; i++) {
    data[i] = Math.random() - 0.5;
  }

  const node = ctx.createBufferSource();
  node.buffer = buf;

  return node;
}

function stopNodes(nodes) {
  nodes.forEach((n) => {
    try {
      n.stop();
    } catch {}
    try {
      n.disconnect();
    } catch {}
  });
}

Object.assign(window, { AudioToggle });
