export class AudioEngine {
  constructor() {
    this.ctx = null;
    this.master = null;
    this.sfx = null;
    this.musicGain = null;
    this.unlocked = false;
    this.vols = { master: 0.75, music: 0.45, sfx: 0.85 };
    this._musicTimer = null;
    this._step = 0;
    this._playing = false;
    this._lastCell = 0;
  }

  setVolumes(v) {
    this.vols = { ...this.vols, ...v };
    this._apply();
  }

  _apply() {
    if (!this.master) return;
    this.master.gain.value = this.vols.master;
    if (this.sfx) this.sfx.gain.value = this.vols.sfx;
    if (this.musicGain) this.musicGain.gain.value = this.vols.music * 0.55;
  }

  unlock() {
    if (this.unlocked) {
      if (this.ctx && this.ctx.state === 'suspended') this.ctx.resume();
      return;
    }
    const AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return;
    this.ctx = new AC();
    this.master = this.ctx.createGain();
    this.master.connect(this.ctx.destination);
    this.sfx = this.ctx.createGain();
    this.sfx.connect(this.master);
    this.musicGain = this.ctx.createGain();
    this.musicGain.connect(this.master);
    this.unlocked = true;
    this._apply();
    this.ctx.resume();
  }

  startMusic() {
    this.unlock();
    if (!this.ctx || this._playing) return;
    this._playing = true;
    this._step = 0;
    this._tickMusic();
  }

  stopMusic() {
    this._playing = false;
    if (this._musicTimer) {
      clearTimeout(this._musicTimer);
      this._musicTimer = null;
    }
  }

  _tickMusic() {
    if (!this._playing || !this.ctx) return;
    const t = this.ctx.currentTime;
    const bpm = 92;
    const beat = 60 / bpm;
    const scale = [110, 130.81, 146.83, 164.81, 196, 220, 261.63];
    const pattern = [0, 2, 4, 2, 5, 4, 2, 0, 4, 5, 7 % 7, 4, 2, 0, 3, 2];
    const note = scale[pattern[this._step % pattern.length] % scale.length];
    const bass = this._step % 8 === 0 ? 55 : this._step % 8 === 4 ? 73.4 : 0;

    this._pad(t, 164.8, beat * 2);
    if (bass) this._bass(t, bass, beat * 1.5);
    this._pluck(t, note * (this._step % 3 === 0 ? 2 : 1), 0.12, 0.035);

    if (this._step % 16 === 0) this._noiseSweep(t, 0.5, 0.04);

    this._step++;
    this._musicTimer = setTimeout(() => this._tickMusic(), beat * 1000 * 0.5);
  }

  _osc(type, freq, t0, dur, gain, dest, atk = 0.01, rel = 0.08) {
    const ctx = this.ctx;
    const o = ctx.createOscillator();
    const g = ctx.createGain();
    o.type = type;
    o.frequency.setValueAtTime(freq, t0);
    g.gain.setValueAtTime(0.0001, t0);
    g.gain.exponentialRampToValueAtTime(Math.max(0.0002, gain), t0 + atk);
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
    o.connect(g);
    g.connect(dest);
    o.start(t0);
    o.stop(t0 + dur + rel);
  }

  _pad(t, freq, dur) {
    const f = this.ctx.createBiquadFilter();
    f.type = 'lowpass';
    f.frequency.value = 640;
    f.Q.value = 0.7;
    f.connect(this.musicGain);
    this._osc('sine', freq, t, dur, 0.045, f, 0.2, 0.3);
    this._osc('sine', freq * 1.005, t, dur, 0.03, f, 0.2, 0.3);
    this._osc('triangle', freq * 0.5, t, dur, 0.02, f, 0.25, 0.4);
  }

  _bass(t, freq, dur) {
    this._osc('sine', freq, t, dur, 0.09, this.musicGain, 0.02, 0.12);
    this._osc('triangle', freq * 0.5, t, dur, 0.04, this.musicGain, 0.02, 0.12);
  }

  _pluck(t, freq, dur, gain) {
    const f = this.ctx.createBiquadFilter();
    f.type = 'lowpass';
    f.frequency.setValueAtTime(1800, t);
    f.frequency.exponentialRampToValueAtTime(420, t + dur);
    f.connect(this.musicGain);
    this._osc('square', freq, t, dur, gain, f, 0.005, 0.04);
  }

  _noiseSweep(t, dur, gain) {
    const buffer = this.ctx.createBuffer(1, this.ctx.sampleRate * dur, this.ctx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < data.length; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / data.length);
    const src = this.ctx.createBufferSource();
    src.buffer = buffer;
    const f = this.ctx.createBiquadFilter();
    f.type = 'highpass';
    f.frequency.value = 800;
    const g = this.ctx.createGain();
    g.gain.setValueAtTime(gain, t);
    g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
    src.connect(f);
    f.connect(g);
    g.connect(this.musicGain);
    src.start(t);
  }

  _click(freq, dur, gain, type = 'square') {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    this._osc(type, freq, t, dur, gain, this.sfx, 0.002, 0.03);
  }

  ui() {
    this.unlock();
    this._click(880, 0.04, 0.03, 'triangle');
  }

  place() {
    this.unlock();
    const t = this.ctx.currentTime;
    this._osc('sine', 420, t, 0.09, 0.07, this.sfx, 0.003, 0.05);
    this._osc('triangle', 840, t, 0.06, 0.04, this.sfx, 0.002, 0.04);
  }

  rotate() {
    this.unlock();
    this._click(620, 0.05, 0.04, 'sine');
    this._click(930, 0.04, 0.025, 'sine');
  }

  replace() {
    this.unlock();
    const t = this.ctx.currentTime;
    this._osc('sawtooth', 140, t, 0.12, 0.06, this.sfx, 0.002, 0.05);
    this._noiseBurst(t, 0.1, 0.06);
  }

  undo() {
    this.unlock();
    this._click(300, 0.08, 0.05, 'sine');
  }

  warn() {
    this.unlock();
    const t = this.ctx.currentTime;
    this._osc('square', 520, t, 0.09, 0.05, this.sfx, 0.002, 0.04);
    this._osc('square', 390, t + 0.12, 0.09, 0.05, this.sfx, 0.002, 0.04);
  }

  valve() {
    this.unlock();
    const t = this.ctx.currentTime;
    this._noiseBurst(t, 0.18, 0.08);
    this._osc('sine', 180, t, 0.25, 0.08, this.sfx, 0.01, 0.1);
    this._osc('triangle', 360, t + 0.05, 0.2, 0.05, this.sfx, 0.01, 0.1);
  }

  cell() {
    if (!this.ctx) return;
    const now = performance.now();
    if (now - this._lastCell < 90) return;
    this._lastCell = now;
    const t = this.ctx.currentTime;
    this._osc('sine', 220 + Math.random() * 80, t, 0.08, 0.035, this.sfx, 0.005, 0.05);
  }

  leakFail() {
    this.unlock();
    const t = this.ctx.currentTime;
    this._noiseBurst(t, 0.35, 0.16);
    this._osc('sawtooth', 90, t, 0.4, 0.1, this.sfx, 0.01, 0.12);
    this._osc('sine', 55, t, 0.5, 0.12, this.sfx, 0.01, 0.2);
  }

  leakWin() {
    this.unlock();
    const t = this.ctx.currentTime;
    [523.25, 659.25, 783.99, 1046.5].forEach((f, i) => {
      this._osc('triangle', f, t + i * 0.09, 0.35, 0.07, this.sfx, 0.01, 0.12);
    });
    this._noiseBurst(t, 0.2, 0.06);
  }

  combo(n) {
    this.unlock();
    const t = this.ctx.currentTime;
    this._osc('sine', 440 + n * 40, t, 0.1, 0.05, this.sfx, 0.003, 0.05);
  }

  _noiseBurst(t, dur, gain) {
    const ctx = this.ctx;
    const buffer = ctx.createBuffer(1, ctx.sampleRate * dur, ctx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < data.length; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / data.length);
    const src = ctx.createBufferSource();
    src.buffer = buffer;
    const f = ctx.createBiquadFilter();
    f.type = 'bandpass';
    f.frequency.value = 900;
    f.Q.value = 0.6;
    const g = ctx.createGain();
    g.gain.setValueAtTime(gain, t);
    g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
    src.connect(f);
    f.connect(g);
    g.connect(this.sfx);
    src.start(t);
  }
}
