import { director } from './CorruptionDirector';

const RATE = { S0: 1, S1: 1, S2: 0.97, S3: 0.92, S4: 0.84 } as const;
const CUTOFF = { S0: 20_000, S1: 20_000, S2: 20_000, S3: 8_000, S4: 4_000 } as const;

export class AudioDirector {
  private context?: AudioContext;
  private music?: AudioBufferSourceNode;
  private birds?: AudioBufferSourceNode;
  private musicGain?: GainNode;
  private birdGain?: GainNode;
  private filter?: BiquadFilterNode;
  private master?: GainNode;

  unlock(): void {
    if (!this.context) this.create();
    void this.context?.resume();
  }

  private create(): void {
    const context = new AudioContext();
    this.context = context;
    this.master = context.createGain();
    this.master.gain.value = 0.28;
    this.master.connect(context.destination);
    this.filter = context.createBiquadFilter();
    this.filter.type = 'lowpass';
    this.filter.connect(this.master);
    this.musicGain = context.createGain();
    this.musicGain.gain.value = 0.34;
    this.musicGain.connect(this.filter);
    this.birdGain = context.createGain();
    this.birdGain.gain.value = 0.12;
    this.birdGain.connect(this.master);
    this.music = context.createBufferSource();
    this.music.buffer = this.makeMusicBuffer(context);
    this.music.loop = true;
    this.music.connect(this.musicGain);
    this.music.start();
    this.birds = context.createBufferSource();
    this.birds.buffer = this.makeBirdBuffer(context);
    this.birds.loop = true;
    this.birds.connect(this.birdGain);
    this.birds.start();
    this.applyCorruption();
    director.on('change', this.applyCorruption, this);
  }

  private makeMusicBuffer(context: AudioContext): AudioBuffer {
    const sampleRate = context.sampleRate;
    const duration = 16;
    const buffer = context.createBuffer(1, sampleRate * duration, sampleRate);
    const data = buffer.getChannelData(0);
    const melody = [523.25, 659.25, 783.99, 659.25, 587.33, 698.46, 880, 698.46, 523.25, 659.25, 783.99, 1046.5, 880, 783.99, 659.25, 587.33];
    const bass = [130.81, 146.83, 164.81, 146.83, 130.81, 174.61, 146.83, 130.81];
    for (let i = 0; i < data.length; i += 1) {
      const t = i / sampleRate;
      const note = melody[Math.floor(t * 2) % melody.length];
      const low = bass[Math.floor(t / 2) % bass.length];
      const envelope = Math.min(1, (t % 0.5) * 20) * Math.max(0, 1 - (t % 0.5) * 0.9);
      const square = Math.sin(2 * Math.PI * note * t) >= 0 ? 1 : -1;
      const triangle = 2 * Math.abs(2 * ((t * low) % 1) - 1) - 1;
      data[i] = (square * 0.17 * envelope) + (triangle * 0.12);
    }
    return buffer;
  }

  private makeBirdBuffer(context: AudioContext): AudioBuffer {
    const duration = 4;
    const buffer = context.createBuffer(1, context.sampleRate * duration, context.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < data.length; i += 1) {
      const t = i / context.sampleRate;
      const active = (t > 0.45 && t < 0.6) || (t > 0.75 && t < 0.9) || (t > 2.3 && t < 2.52);
      if (active) data[i] = Math.sin(2 * Math.PI * (1250 + 500 * Math.sin(t * 35)) * t) * 0.25;
    }
    return buffer;
  }

  private applyCorruption(): void {
    if (!this.context || !this.music || !this.filter || !this.birdGain) return;
    const now = this.context.currentTime;
    this.music.playbackRate.setTargetAtTime(RATE[director.stage], now, 0.1);
    this.filter.frequency.setTargetAtTime(CUTOFF[director.stage], now, 0.1);
    this.birdGain.gain.setTargetAtTime(director.stage === 'S4' ? 0 : 0.12, now, 0.1);
  }

  fadeForSmile(seconds: number): void {
    if (!this.context || !this.master) return;
    const now = this.context.currentTime;
    this.master.gain.cancelScheduledValues(now);
    if (seconds < 40) this.master.gain.setTargetAtTime(0.28, now, 0.2);
    else this.master.gain.linearRampToValueAtTime(Math.max(0, 0.28 * (60 - seconds) / 20), now + 0.12);
  }

  restore(): void {
    if (!this.context || !this.master) return;
    this.master.gain.setTargetAtTime(0.28, this.context.currentTime, 0.3);
  }

  beep(kind: 'dialogue' | 'task' | 'bell' | 'monitor'): void {
    if (!this.context || !this.master) return;
    const frequency = { dialogue: 740, task: 1046, bell: 392, monitor: 880 }[kind];
    const duration = { dialogue: 0.035, task: 0.28, bell: 1.1, monitor: 0.22 }[kind];
    const oscillator = this.context.createOscillator();
    const gain = this.context.createGain();
    oscillator.type = kind === 'bell' ? 'sine' : 'square';
    oscillator.frequency.value = frequency;
    gain.gain.setValueAtTime(kind === 'dialogue' ? 0.035 : 0.12, this.context.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.0001, this.context.currentTime + duration);
    oscillator.connect(gain); gain.connect(this.master);
    oscillator.start(); oscillator.stop(this.context.currentTime + duration);
  }
}

export const audio = new AudioDirector();
