/*!
 * audio.js — All sound is synthesized with the Web Audio API.
 * There are no external sound files: this avoids any licensing question
 * around sourced sound effects/music, keeps the game fully offline, and
 * keeps the download small. Every effect is generated from oscillators
 * and filtered noise at play-time.
 */
(function (global) {
  'use strict';
  const LAB = (global.LAB = global.LAB || {});

  function AudioManager() {
    this.ctx = null;
    this.master = null;
    this.musicGain = null;
    this.sfxGain = null;
    this.muted = false;
    this.volume = 0.7;
    this.musicNodes = [];
    this.rollNode = null;
  }

  AudioManager.prototype.ensureCtx = function () {
    if (this.ctx) return;
    const Ctx = global.AudioContext || global.webkitAudioContext;
    if (!Ctx) return; // Web Audio unsupported - fail silently, game still playable
    this.ctx = new Ctx();
    this.master = this.ctx.createGain();
    this.master.gain.value = this.muted ? 0 : this.volume;
    this.master.connect(this.ctx.destination);
    this.musicGain = this.ctx.createGain();
    this.musicGain.gain.value = 0.35;
    this.musicGain.connect(this.master);
    this.sfxGain = this.ctx.createGain();
    this.sfxGain.gain.value = 1;
    this.sfxGain.connect(this.master);
  };

  // Browsers block audio until a user gesture; call this from the first
  // click/touch/keydown handler.
  AudioManager.prototype.resumeIfSuspended = function () {
    if (this.ctx && this.ctx.state === 'suspended') this.ctx.resume().catch(() => {});
  };

  AudioManager.prototype.setVolume = function (v) {
    this.volume = v;
    if (this.master) this.master.gain.setTargetAtTime(this.muted ? 0 : v, this.ctx.currentTime, 0.05);
  };
  AudioManager.prototype.setMuted = function (m) {
    this.muted = m;
    if (this.master) this.master.gain.setTargetAtTime(m ? 0 : this.volume, this.ctx.currentTime, 0.05);
  };

  function noiseBuffer(ctx, seconds) {
    const buf = ctx.createBuffer(1, Math.floor(ctx.sampleRate * seconds), ctx.sampleRate);
    const data = buf.getChannelData(0);
    for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
    return buf;
  }

  AudioManager.prototype.playCollision = function (intensity) {
    this.ensureCtx(); if (!this.ctx) return;
    intensity = Math.min(1, Math.max(0, intensity || 0.3));
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator(), gain = this.ctx.createGain();
    osc.type = 'triangle';
    osc.frequency.setValueAtTime(220 + intensity * 140, t);
    osc.frequency.exponentialRampToValueAtTime(80, t + 0.09);
    gain.gain.setValueAtTime(Math.min(0.5, 0.12 + intensity * 0.35), t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.12);
    osc.connect(gain); gain.connect(this.sfxGain);
    osc.start(t); osc.stop(t + 0.13);
  };

  AudioManager.prototype.playVictory = function () {
    this.ensureCtx(); if (!this.ctx) return;
    const notes = [523.25, 659.25, 783.99, 1046.5]; // simple original ascending arpeggio (C5 E5 G5 C6)
    const t0 = this.ctx.currentTime;
    notes.forEach((f, i) => {
      const osc = this.ctx.createOscillator(), gain = this.ctx.createGain();
      osc.type = 'sine'; osc.frequency.value = f;
      const t = t0 + i * 0.11;
      gain.gain.setValueAtTime(0.0001, t);
      gain.gain.linearRampToValueAtTime(0.3, t + 0.02);
      gain.gain.exponentialRampToValueAtTime(0.001, t + 0.35);
      osc.connect(gain); gain.connect(this.sfxGain);
      osc.start(t); osc.stop(t + 0.4);
    });
  };

  AudioManager.prototype.playFail = function () {
    this.ensureCtx(); if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator(), gain = this.ctx.createGain();
    osc.type = 'sawtooth';
    osc.frequency.setValueAtTime(220, t);
    osc.frequency.exponentialRampToValueAtTime(60, t + 0.35);
    gain.gain.setValueAtTime(0.22, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.4);
    osc.connect(gain); gain.connect(this.sfxGain);
    osc.start(t); osc.stop(t + 0.42);
  };

  AudioManager.prototype.setRolling = function (speed) {
    this.ensureCtx(); if (!this.ctx) return;
    if (!this.rollNode) {
      const src = this.ctx.createBufferSource();
      src.buffer = noiseBuffer(this.ctx, 2); src.loop = true;
      const filter = this.ctx.createBiquadFilter();
      filter.type = 'lowpass'; filter.frequency.value = 300;
      const gain = this.ctx.createGain(); gain.gain.value = 0;
      src.connect(filter); filter.connect(gain); gain.connect(this.sfxGain);
      src.start();
      this.rollNode = { src, filter, gain };
    }
    const target = Math.min(0.16, (speed || 0) / 900 * 0.16);
    this.rollNode.gain.gain.setTargetAtTime(target, this.ctx.currentTime, 0.08);
    this.rollNode.filter.frequency.setTargetAtTime(200 + (speed || 0) * 0.6, this.ctx.currentTime, 0.08);
  };

  AudioManager.prototype.startMusic = function () {
    this.ensureCtx(); if (!this.ctx || this.musicNodes.length) return;
    const freqs = [130.81, 164.81, 196.0]; // soft C3-E3-G3 pad, original ambient drone
    freqs.forEach((f, i) => {
      const osc = this.ctx.createOscillator();
      osc.type = 'sine'; osc.frequency.value = f;
      const lfo = this.ctx.createOscillator();
      lfo.frequency.value = 0.08 + i * 0.03;
      const lfoGain = this.ctx.createGain(); lfoGain.gain.value = 3;
      lfo.connect(lfoGain); lfoGain.connect(osc.frequency);
      const g = this.ctx.createGain(); g.gain.value = 0.05;
      osc.connect(g); g.connect(this.musicGain);
      osc.start(); lfo.start();
      this.musicNodes.push(osc, lfo);
    });
  };
  AudioManager.prototype.stopMusic = function () {
    this.musicNodes.forEach(n => { try { n.stop(); } catch (e) { /* already stopped */ } });
    this.musicNodes = [];
  };

  LAB.Audio = new AudioManager();
})(typeof window !== 'undefined' ? window : globalThis);
