// Tiny utilities: sound, confetti, localStorage memory, spotlight cursor.

// ─── Web Audio "ta-dum" stinger ─────────────────────────────────────────
// Procedural — no external file. Two big descending tones with a touch of
// reverb-y release.
window.__bns_audio = null;
function bnsAudioCtx() {
  if (!window.__bns_audio) {
    try { window.__bns_audio = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) {}
  }
  return window.__bns_audio;
}

function playTaDum() {
  const ctx = bnsAudioCtx(); if (!ctx) return;
  if (ctx.state === 'suspended') ctx.resume();
  const now = ctx.currentTime;

  // master gain w/ a soft compressor
  const master = ctx.createGain(); master.gain.value = 0.0001;
  master.gain.exponentialRampToValueAtTime(0.5, now + 0.02);
  master.gain.exponentialRampToValueAtTime(0.0001, now + 2.2);
  master.connect(ctx.destination);

  // helper to play one note
  const note = (freq, t0, dur) => {
    const o = ctx.createOscillator();
    const g = ctx.createGain();
    o.type = 'triangle';
    o.frequency.setValueAtTime(freq, now + t0);
    o.frequency.exponentialRampToValueAtTime(freq * 0.94, now + t0 + dur * 0.8);
    g.gain.setValueAtTime(0.0001, now + t0);
    g.gain.exponentialRampToValueAtTime(0.6, now + t0 + 0.02);
    g.gain.exponentialRampToValueAtTime(0.0001, now + t0 + dur);
    o.connect(g); g.connect(master);
    o.start(now + t0); o.stop(now + t0 + dur + 0.05);
    // a sub-octave for body
    const sub = ctx.createOscillator(), sg = ctx.createGain();
    sub.type = 'sine'; sub.frequency.value = freq / 2;
    sg.gain.setValueAtTime(0.0001, now + t0);
    sg.gain.exponentialRampToValueAtTime(0.4, now + t0 + 0.03);
    sg.gain.exponentialRampToValueAtTime(0.0001, now + t0 + dur);
    sub.connect(sg); sg.connect(master);
    sub.start(now + t0); sub.stop(now + t0 + dur + 0.05);
  };

  // ta…
  note(196, 0.0, 0.4);   // G3
  // dum (lower, longer)
  note(130.8, 0.42, 1.6); // C3
}

function playClick() {
  const ctx = bnsAudioCtx(); if (!ctx) return;
  if (ctx.state === 'suspended') ctx.resume();
  const now = ctx.currentTime;
  const o = ctx.createOscillator(), g = ctx.createGain();
  o.type = 'square';
  o.frequency.setValueAtTime(680, now);
  o.frequency.exponentialRampToValueAtTime(280, now + 0.12);
  g.gain.setValueAtTime(0.0001, now);
  g.gain.exponentialRampToValueAtTime(0.18, now + 0.01);
  g.gain.exponentialRampToValueAtTime(0.0001, now + 0.16);
  o.connect(g); g.connect(ctx.destination);
  o.start(now); o.stop(now + 0.2);
}

window.__bns_last_click_sound = 0;
function playUiClick() {
  const now = performance.now();
  if (now - window.__bns_last_click_sound < 70) return;
  window.__bns_last_click_sound = now;
  playClick();
}

window.__bns_ambient = {
  wanted: false,
  running: false,
  step: 0,
  timer: null,
  master: null,
};

function playRetroTone(ctx, freq, delay, dur, gain, destination) {
  const now = ctx.currentTime + delay;
  const osc = ctx.createOscillator();
  const tone = ctx.createGain();
  const filter = ctx.createBiquadFilter();

  osc.type = 'square';
  osc.frequency.setValueAtTime(freq, now);
  osc.frequency.exponentialRampToValueAtTime(freq * 0.985, now + dur);
  filter.type = 'lowpass';
  filter.frequency.value = 1150;
  filter.Q.value = 0.8;
  tone.gain.setValueAtTime(0.0001, now);
  tone.gain.exponentialRampToValueAtTime(gain, now + 0.012);
  tone.gain.exponentialRampToValueAtTime(0.0001, now + dur);

  osc.connect(filter); filter.connect(tone); tone.connect(destination);
  osc.start(now); osc.stop(now + dur + 0.04);
}

function startRetroAmbience() {
  const ambient = window.__bns_ambient;
  ambient.wanted = true;
  if (ambient.running || document.visibilityState === 'hidden') return;
  if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return;

  const ctx = bnsAudioCtx(); if (!ctx) return;
  if (ctx.state === 'suspended') ctx.resume();

  const master = ctx.createGain();
  master.gain.setValueAtTime(0.0001, ctx.currentTime);
  master.gain.exponentialRampToValueAtTime(0.028, ctx.currentTime + 0.18);
  master.connect(ctx.destination);
  ambient.master = master;
  ambient.running = true;

  const bass = [110, 146.83, 164.81, 146.83, 130.81, 164.81, 196, 146.83];
  const sparkle = [440, 493.88, 587.33, 659.25, 587.33, 493.88];
  const tick = () => {
    if (!ambient.running || !ambient.master) return;
    const i = ambient.step++;
    playRetroTone(ctx, bass[i % bass.length], 0, 0.11, 0.055, ambient.master);
    if (i % 4 === 2) {
      playRetroTone(ctx, sparkle[(i / 2) % sparkle.length | 0], 0.08, 0.075, 0.022, ambient.master);
    }
  };

  tick();
  ambient.timer = setInterval(tick, 520);
}

function stopRetroAmbience() {
  const ambient = window.__bns_ambient;
  ambient.wanted = false;
  if (ambient.timer) clearInterval(ambient.timer);
  ambient.timer = null;
  ambient.running = false;
  if (ambient.master) {
    const ctx = bnsAudioCtx();
    const master = ambient.master;
    try {
      master.gain.exponentialRampToValueAtTime(0.0001, (ctx?.currentTime || 0) + 0.12);
      setTimeout(() => master.disconnect(), 160);
    } catch {}
  }
  ambient.master = null;
}

document.addEventListener('visibilitychange', () => {
  const ambient = window.__bns_ambient;
  if (document.visibilityState === 'hidden') {
    const wanted = ambient.wanted;
    stopRetroAmbience();
    ambient.wanted = wanted;
  } else if (ambient.wanted) {
    startRetroAmbience();
  }
});

// ─── Confetti burst ─────────────────────────────────────────────────────
function confettiBurst(x, y, colors = ['#ff4d8d','#ffd93d','#22d3ee','#a3e635','#fb923c','#a78bfa']) {
  const wrap = document.createElement('div');
  wrap.className = 'bns-confetti';
  wrap.style.cssText = `position:fixed; left:${x}px; top:${y}px; pointer-events:none; z-index:9999;`;
  document.body.appendChild(wrap);
  const N = 36;
  for (let i = 0; i < N; i++) {
    const p = document.createElement('i');
    const angle = (Math.PI * 2 * i) / N + Math.random() * 0.5;
    const dist = 80 + Math.random() * 140;
    const dx = Math.cos(angle) * dist;
    const dy = Math.sin(angle) * dist;
    const rot = (Math.random() * 720 - 360) | 0;
    const size = 6 + Math.random() * 8;
    const color = colors[i % colors.length];
    const isCircle = Math.random() > 0.6;
    p.style.cssText = `
      position:absolute; left:0; top:0;
      width:${size}px; height:${size * (isCircle ? 1 : 1.6)}px;
      background:${color}; border-radius:${isCircle ? '50%' : '2px'};
      transform: translate(-50%, -50%) rotate(0deg);
      animation: bns-conf .9s cubic-bezier(.2,.7,.4,1) forwards;
      --dx:${dx}px; --dy:${dy}px; --rot:${rot}deg;
    `;
    wrap.appendChild(p);
  }
  setTimeout(() => wrap.remove(), 1100);
}

// ─── Memory: "Continue Watching" ────────────────────────────────────────
const BNS_LS = 'bns-portfolio-v1';
function bnsLoadMemory() {
  try { return JSON.parse(localStorage.getItem(BNS_LS) || '{}'); } catch { return {}; }
}
function bnsSaveMemory(patch) {
  const cur = bnsLoadMemory();
  const next = { ...cur, ...patch };
  try { localStorage.setItem(BNS_LS, JSON.stringify(next)); } catch {}
  return next;
}
function bnsMarkVisited(id) {
  const cur = bnsLoadMemory();
  const visited = { ...(cur.visited || {}) };
  const prev = visited[id]?.progress || 0;
  visited[id] = { ts: Date.now(), progress: prev >= 100 ? 100 : Math.max(prev, 25) };
  return bnsSaveMemory({ visited, last: id });
}
function bnsCompleteViewing(id) {
  const cur = bnsLoadMemory();
  const visited = { ...(cur.visited || {}) };
  visited[id] = { ts: Date.now(), progress: 100 };
  return bnsSaveMemory({ visited, last: id });
}

// ─── Inject confetti CSS once ───────────────────────────────────────────
(function () {
  if (document.getElementById('bns-util-styles')) return;
  const s = document.createElement('style');
  s.id = 'bns-util-styles';
  s.textContent = `
    @keyframes bns-conf {
      0%   { transform: translate(-50%, -50%) rotate(0); opacity: 1; }
      100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) rotate(var(--rot)); opacity: 0; }
    }
    /* Spotlight cursor mode */
    body.bns-spotlight::after{
      content:""; position: fixed; inset: 0; pointer-events:none; z-index: 998;
      background: radial-gradient(circle 240px at var(--mx, 50%) var(--my, 50%),
        transparent 0, transparent 50%, rgba(4,6,26,.7) 100%);
      mix-blend-mode: multiply; transition: background .12s linear;
    }
  `;
  document.head.appendChild(s);
})();

// ─── Spotlight cursor tracker ───────────────────────────────────────────
window.__bnsSpotlight = false;
window.addEventListener('mousemove', (e) => {
  if (!window.__bnsSpotlight) return;
  document.body.style.setProperty('--mx', e.clientX + 'px');
  document.body.style.setProperty('--my', e.clientY + 'px');
});
function setSpotlight(on) {
  window.__bnsSpotlight = on;
  document.body.classList.toggle('bns-spotlight', !!on);
}

// ─── Analytics: SPA virtual pageviews + key events (GA4 via gtag) ────────
// This is a single-page app, so GA4 only auto-fires one page_view on load.
// These helpers send a synthetic page_view per in-app view (section / picker)
// so GA4's "Pages and screens" report reflects which section is being viewed,
// plus named events for key interactions. No-ops safely if gtag isn't present.
function bnsTrackPage(path, title) {
  if (typeof window.gtag !== 'function') return;
  try {
    window.gtag('event', 'page_view', {
      page_path: path,
      page_location: location.origin + path,
      page_title: title || document.title,
    });
  } catch {}
}
function bnsTrackEvent(name, params = {}) {
  if (typeof window.gtag !== 'function') return;
  try { window.gtag('event', name, params); } catch {}
}

Object.assign(window, {
  playTaDum,
  playClick,
  playUiClick,
  startRetroAmbience,
  stopRetroAmbience,
  confettiBurst,
  bnsLoadMemory,
  bnsSaveMemory,
  bnsMarkVisited,
  bnsCompleteViewing,
  setSpotlight,
  bnsTrackPage,
  bnsTrackEvent
});
