import type { AudioProcessor, ProcessorCategory } from './AudioProcessor';

export class Phaser implements AudioProcessor {
  private readonly sampleRate = 44100;

  getName(): string {
    return 'Phaser';
  }

  getDescription(): string {
    return 'Classic phaser effect with sweeping all-pass filters for swirling, spacey sounds';
  }

  getCategory(): ProcessorCategory {
    return 'Filter';
  }

  process(
    leftChannel: Float32Array,
    rightChannel: Float32Array
  ): [Float32Array, Float32Array] {
    const length = leftChannel.length;

    const numStages = Math.floor(Math.random() * 4) + 4; // 4-7 all-pass filter stages
    const lfoRate = Math.random() * 2.5 + 0.2; // 0.2-2.7 Hz LFO rate (pushed higher sometimes)
    const lfoDepth = Math.random() * 0.4 + 0.5; // 50-90% modulation depth
    const feedback = Math.random() * 0.7 + 0.2; // 20-90% feedback (increased range)
    const mix = Math.random() * 0.4 + 0.4; // 40-80% wet
    const minFreq = Math.random() * 200 + 200; // 200-400 Hz minimum frequency
    const maxFreq = Math.random() * 1500 + 1500; // 1500-3000 Hz maximum frequency
    const stereoPhaseOffset = Math.random() * Math.PI; // Random stereo offset

    const leftOut = new Float32Array(length);
    const rightOut = new Float32Array(length);

    const leftStates = this.createFilterStates(numStages);
    const rightStates = this.createFilterStates(numStages);

    let leftFeedback = 0;
    let rightFeedback = 0;

    for (let i = 0; i < length; i++) {
      const t = i / this.sampleRate;
      const lfoLeft = Math.sin(2 * Math.PI * lfoRate * t);
      const lfoRight = Math.sin(2 * Math.PI * lfoRate * t + stereoPhaseOffset);

      const freqLeft = this.mapLfoToFreq(lfoLeft, minFreq, maxFreq, lfoDepth);
      const freqRight = this.mapLfoToFreq(lfoRight, minFreq, maxFreq, lfoDepth);

      let leftProcessed = leftChannel[i] + leftFeedback * feedback;
      let rightProcessed = rightChannel[i] + rightFeedback * feedback;

      for (let stage = 0; stage < numStages; stage++) {
        leftProcessed = this.allPassFilter(
          leftProcessed,
          freqLeft,
          leftStates[stage]
        );
        rightProcessed = this.allPassFilter(
          rightProcessed,
          freqRight,
          rightStates[stage]
        );
      }

      leftFeedback = leftProcessed;
      rightFeedback = rightProcessed;

      const dryGain = Math.sqrt(1 - mix);
      const wetGain = Math.sqrt(mix);

      leftOut[i] = leftChannel[i] * dryGain + leftProcessed * wetGain;
      rightOut[i] = rightChannel[i] * dryGain + rightProcessed * wetGain;

      leftOut[i] = this.softClip(leftOut[i]);
      rightOut[i] = this.softClip(rightOut[i]);
    }

    this.normalizeOutput(leftOut, rightOut);

    return [leftOut, rightOut];
  }

  private createFilterStates(numStages: number): Array<{ x1: number; y1: number }> {
    const states = [];
    for (let i = 0; i < numStages; i++) {
      states.push({ x1: 0, y1: 0 });
    }
    return states;
  }

  private allPassFilter(
    input: number,
    frequency: number,
    state: { x1: number; y1: number }
  ): number {
    const c = (Math.tan(Math.PI * frequency / this.sampleRate) - 1) /
              (Math.tan(Math.PI * frequency / this.sampleRate) + 1);

    const output = c * input + state.x1 - c * state.y1;

    state.x1 = input;
    state.y1 = output;

    return output;
  }

  private mapLfoToFreq(
    lfo: number,
    minFreq: number,
    maxFreq: number,
    depth: number
  ): number {
    const normalizedLfo = (lfo + 1) * 0.5;
    const modulatedLfo = normalizedLfo * depth + (1 - depth) * 0.5;
    return minFreq + (maxFreq - minFreq) * modulatedLfo;
  }

  private softClip(sample: number): number {
    const threshold = 0.95;
    if (Math.abs(sample) < threshold) {
      return sample;
    }
    const sign = sample < 0 ? -1 : 1;
    const abs = Math.abs(sample);
    return sign * (threshold + (1 - threshold) * Math.tanh((abs - threshold) / (1 - threshold)));
  }

  private normalizeOutput(leftOut: Float32Array, rightOut: Float32Array): void {
    let maxPeak = 0;
    for (let i = 0; i < leftOut.length; i++) {
      maxPeak = Math.max(maxPeak, Math.abs(leftOut[i]), Math.abs(rightOut[i]));
    }

    if (maxPeak > 0.01) {
      const targetPeak = 0.95;
      const normalizeGain = Math.min(1.0, targetPeak / maxPeak);

      for (let i = 0; i < leftOut.length; i++) {
        leftOut[i] *= normalizeGain;
        rightOut[i] *= normalizeGain;
      }
    }
  }
}
