// Tobeads — AudioManager
// Lightweight 8-bit sound via the Web Audio API. NO audio files required —
// every sound is synthesized from oscillators, so the site works even if no
// assets exist. A global mute state is persisted to localStorage.
//
// ── Using real audio files instead ──────────────────────────────────────
// If you want to ship real .wav/.mp3 assets, drop them in /audio and set
// AudioManager.useSampleFiles(true). The expected files are:
//   audio/boot.wav        audio/start.wav      audio/transition.wav
//   audio/hover.wav       audio/move.wav       audio/eat.wav
//   audio/bgm.mp3 (looped, very low volume)
// The synth fallbacks below mirror each of those events 1:1.
//
// Public API (all safe no-ops if audio can't init):
//   AudioManager.unlock()        — call on first user gesture
//   AudioManager.playBoot()
//   AudioManager.playStart()
//   AudioManager.playTransition()
//   AudioManager.playHover()
//   AudioManager.playMove()
//   AudioManager.playEat()
//   AudioManager.startIntroBGM() / stopIntroBGM()  — intro screen loop (audio/intro-bgm.mp3)
//   AudioManager.startBGM() / stopBGM()            — legacy synth arpeggio (unused on site)
//   AudioManager.toggleMute() -> bool   AudioManager.isMuted()
//   AudioManager.subscribe(fn)          — notified on mute changes

(function () {
  const LS_KEY = 'tobeads.muted';
  let ctx = null;
  let master = null;
  let muted = false;
  try { muted = localStorage.getItem(LS_KEY) === '1'; } catch (e) {}
  const listeners = new Set();
  let unlocked = false;
  let bgmNodes = null;
  let introBgm = null;
  let introBgmWanted = false;
  const INTRO_BGM_SRC = 'audio/intro-bgm.mp3';
  let introAutoplayBound = false;
  let lastMove = 0;
  let lastEat = 0;
  let USE_FILES = false;
  const buffers = {};

  function ensureCtx() {
    if (ctx) return ctx;
    try {
      const AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return null;
      ctx = new AC();
      master = ctx.createGain();
      master.gain.value = muted ? 0 : 0.5;
      master.connect(ctx.destination);
    } catch (e) { ctx = null; }
    return ctx;
  }

  function unlock() {
    const c = ensureCtx();
    if (!c) return;
    if (c.state === 'suspended') c.resume().catch(() => {});
    unlocked = true;
    tryPlayIntroBGM();
  }

  function shouldPlayIntroBgm() {
    try { return sessionStorage.getItem('tobeads.booted') !== '1'; } catch (e) { return true; }
  }

  function ensureIntroBgm() {
    if (introBgm) return introBgm;
    if (window.__tobeadsIntroBgmEarly) {
      introBgm = window.__tobeadsIntroBgmEarly;
      delete window.__tobeadsIntroBgmEarly;
      introBgm.loop = true;
      introBgm.volume = 0.34;
      return introBgm;
    }
    try {
      introBgm = new Audio(INTRO_BGM_SRC);
      introBgm.loop = true;
      introBgm.preload = 'auto';
      introBgm.autoplay = true;
      introBgm.volume = 0.34;
    } catch (e) { introBgm = null; }
    return introBgm;
  }

  function bindIntroAutoplayRetries() {
    if (introAutoplayBound) return;
    const a = ensureIntroBgm();
    if (!a) return;
    introAutoplayBound = true;
    const retry = () => { if (introBgmWanted && shouldPlayIntroBgm() && !muted) tryPlayIntroBGM(); };
    a.addEventListener('canplay', retry);
    a.addEventListener('canplaythrough', retry);
    document.addEventListener('visibilitychange', () => { if (!document.hidden) retry(); });
    window.addEventListener('pageshow', retry);
  }

  function tryPlayIntroBGM() {
    if (!introBgmWanted || muted || !shouldPlayIntroBgm()) return;
    const a = ensureIntroBgm();
    if (!a) return;
    if (a.readyState < 2) {
      try { a.load(); } catch (e) {}
    }
    const p = a.play();
    if (p && typeof p.catch === 'function') {
      p.catch(() => { /* autoplay policy — retries on canplay / gesture */ });
    }
  }

  function bootIntroAutoplay() {
    if (muted || !shouldPlayIntroBgm()) return;
    introBgmWanted = true;
    bindIntroAutoplayRetries();
    tryPlayIntroBGM();
  }

  function startIntroBGM() {
    introBgmWanted = true;
    bindIntroAutoplayRetries();
    tryPlayIntroBGM();
  }

  function stopIntroBGM() {
    introBgmWanted = false;
    if (!introBgm) return;
    introBgm.pause();
    try { introBgm.currentTime = 0; } catch (e) {}
  }

  // ── core synth voice ──────────────────────────────────────────────────
  // type: 'square' | 'triangle' | 'sawtooth' | 'sine'
  function blip({ type = 'square', from = 440, to = null, dur = 0.12, vol = 0.3, delay = 0, attack = 0.005 }) {
    const c = ensureCtx();
    if (!c || muted || !unlocked) return;
    try {
      const t0 = c.currentTime + delay;
      const osc = c.createOscillator();
      const g = c.createGain();
      osc.type = type;
      osc.frequency.setValueAtTime(from, t0);
      if (to != null) osc.frequency.exponentialRampToValueAtTime(Math.max(1, to), t0 + dur);
      g.gain.setValueAtTime(0.0001, t0);
      g.gain.exponentialRampToValueAtTime(vol, t0 + attack);
      g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
      osc.connect(g); g.connect(master);
      osc.start(t0);
      osc.stop(t0 + dur + 0.02);
    } catch (e) {}
  }

  // short pixel noise burst (for CRT/boot texture)
  function noise({ dur = 0.18, vol = 0.12, delay = 0 }) {
    const c = ensureCtx();
    if (!c || muted || !unlocked) return;
    try {
      const t0 = c.currentTime + delay;
      const n = Math.floor(c.sampleRate * dur);
      const buf = c.createBuffer(1, n, c.sampleRate);
      const data = buf.getChannelData(0);
      for (let i = 0; i < n; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / n);
      const src = c.createBufferSource();
      src.buffer = buf;
      const g = c.createGain();
      g.gain.value = vol;
      const hp = c.createBiquadFilter();
      hp.type = 'highpass'; hp.frequency.value = 800;
      src.connect(hp); hp.connect(g); g.connect(master);
      src.start(t0);
    } catch (e) {}
  }

  // ── events ────────────────────────────────────────────────────────────
  function playBoot() {
    // low 8-bit power-on swell + crt noise + rising blips
    noise({ dur: 0.5, vol: 0.06 });
    blip({ type: 'triangle', from: 60, to: 180, dur: 0.5, vol: 0.25 });
    blip({ type: 'square', from: 220, to: 330, dur: 0.1, vol: 0.12, delay: 0.55 });
    blip({ type: 'square', from: 330, to: 440, dur: 0.1, vol: 0.12, delay: 0.7 });
    playBootTune();
  }

  // short royalty-free chiptune boot melody (synth) synced to the ~2.8s boot.
  // Lead = square arpeggio in A-minor-ish; bass = triangle pulses underneath.
  function playBootTune() {
    if (muted || !unlocked) return;
    // lead melody (start ~0.5s in, after the power swell)
    const lead = [
      [440, 0.16], [554, 0.16], [659, 0.16], [880, 0.22],
      [784, 0.16], [659, 0.16], [740, 0.18], [880, 0.30],
    ];
    let t = 0.55;
    lead.forEach(([f, d]) => {
      blip({ type: 'square', from: f, dur: d * 0.92, vol: 0.12, delay: t, attack: 0.008 });
      t += d;
    });
    // bass pulse line under the lead
    const bass = [110, 110, 146, 146, 130, 130, 98, 98];
    bass.forEach((f, i) => blip({ type: 'triangle', from: f, dur: 0.26, vol: 0.10, delay: 0.55 + i * 0.30 }));
    // final "ready" sparkle to land on the wordmark reveal
    blip({ type: 'square', from: 1320, dur: 0.10, vol: 0.10, delay: 0.55 + 1.7 });
    blip({ type: 'triangle', from: 880, to: 1760, dur: 0.4, vol: 0.10, delay: 0.55 + 1.85 });
  }
  function playTransition() {
    // rising "game ready" arpeggio
    const notes = [330, 440, 550, 660, 880];
    notes.forEach((f, i) => blip({ type: 'square', from: f, dur: 0.09, vol: 0.22, delay: i * 0.06 }));
    blip({ type: 'triangle', from: 880, to: 1320, dur: 0.25, vol: 0.18, delay: notes.length * 0.06 });
  }
  function playStart() {
    // tactile click + start chime + zoom whoosh
    blip({ type: 'square', from: 180, to: 90, dur: 0.06, vol: 0.3 });            // click
    blip({ type: 'square', from: 660, dur: 0.08, vol: 0.25, delay: 0.05 });
    blip({ type: 'square', from: 990, dur: 0.12, vol: 0.25, delay: 0.13 });       // chime
    blip({ type: 'sawtooth', from: 200, to: 1400, dur: 0.3, vol: 0.12, delay: 0.1 }); // zoom
  }
  function playHover() {
    blip({ type: 'square', from: 520, to: 680, dur: 0.05, vol: 0.14 });
  }
  function playSelect() {
    blip({ type: 'square', from: 700, dur: 0.06, vol: 0.2 });
    blip({ type: 'square', from: 1040, dur: 0.08, vol: 0.18, delay: 0.06 });
  }
  function playMove() {
    const now = performance.now();
    if (now - lastMove < 70) return;       // throttle so it isn't annoying
    lastMove = now;
    blip({ type: 'square', from: 300, to: 360, dur: 0.03, vol: 0.07 });
  }
  function playEat() {
    const now = performance.now();
    if (now - lastEat < 60) return;
    lastEat = now;
    blip({ type: 'square', from: 440, to: 620, dur: 0.04, vol: 0.10 });
  }

  // ── optional low-volume looped BGM (synth arpeggio) ───────────────────
  function startBGM() {
    const c = ensureCtx();
    if (!c || bgmNodes || muted || !unlocked) return;
    try {
      const g = c.createGain();
      g.gain.value = 0.05;
      g.connect(master);
      const seq = [220, 277, 330, 277, 247, 330, 392, 330];
      let i = 0;
      const tick = () => {
        if (!bgmNodes) return;
        const f = seq[i % seq.length]; i++;
        const osc = c.createOscillator();
        const eg = c.createGain();
        osc.type = 'square';
        osc.frequency.value = f;
        const t0 = c.currentTime;
        eg.gain.setValueAtTime(0.0001, t0);
        eg.gain.exponentialRampToValueAtTime(0.5, t0 + 0.02);
        eg.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.22);
        osc.connect(eg); eg.connect(g);
        osc.start(); osc.stop(t0 + 0.24);
      };
      const id = setInterval(tick, 260);
      bgmNodes = { g, id };
    } catch (e) {}
  }
  function stopBGM() {
    if (!bgmNodes) return;
    try { clearInterval(bgmNodes.id); bgmNodes.g.disconnect(); } catch (e) {}
    bgmNodes = null;
  }

  // ── mute ──────────────────────────────────────────────────────────────
  function setMute(v) {
    muted = !!v;
    try { localStorage.setItem(LS_KEY, muted ? '1' : '0'); } catch (e) {}
    if (master && ctx) {
      try { master.gain.setTargetAtTime(muted ? 0 : 0.5, ctx.currentTime, 0.02); } catch (e) {}
    }
    if (muted) {
      stopBGM();
      if (introBgm) introBgm.pause();
    } else if (introBgmWanted) tryPlayIntroBGM();
    listeners.forEach(fn => { try { fn(muted); } catch (e) {} });
  }
  function toggleMute() { setMute(!muted); return muted; }
  function isMuted() { return muted; }
  function isRunning() { return !!(ctx && ctx.state === 'running' && unlocked); }
  function subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); }

  function useSampleFiles(v) { USE_FILES = !!v; /* loader left as an exercise; synth is default */ }

  window.AudioManager = {
    unlock, playBoot, playBootTune, playTransition, playStart, playHover, playSelect,
    playMove, playEat, startIntroBGM, stopIntroBGM, startBGM, stopBGM,
    toggleMute, isMuted, isRunning, subscribe, setMute, useSampleFiles,
  };

  // Unlock on the very first user gesture anywhere (autoplay policy friendly).
  const onceUnlock = () => {
    unlock();
    window.removeEventListener('pointerdown', onceUnlock);
    window.removeEventListener('keydown', onceUnlock);
  };
  window.addEventListener('pointerdown', onceUnlock);
  window.addEventListener('keydown', onceUnlock);

  // Intro BGM: start as soon as the script loads (no click). Stops after START / booted flag.
  bootIntroAutoplay();
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', bootIntroAutoplay);
  }
  window.addEventListener('load', bootIntroAutoplay);
  window.addEventListener('pageshow', (e) => { if (e.persisted) bootIntroAutoplay(); });
})();
