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

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

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

  getDescription(): string {
    return 'Multi-band resonant filter bank that adds tonal character through resonance';
  }

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

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

    const numResonators = Math.floor(Math.random() * 3) + 2; // 2-4 resonators
    const baseFreq = Math.random() * 200 + 100; // 100-300 Hz base frequency
    const spread = Math.random() * 0.6 + 0.4; // 0.4-1.0 harmonic spread
    const resonance = Math.random() * 8 + 4; // Q factor 4-12
    const mix = Math.random() * 0.6 + 0.3; // 30-90% wet
    const stereoSpread = Math.random() * 0.2; // 0-20% stereo detuning
    const modulationRate = Math.random() * 0.8 + 0.1; // 0.1-0.9 Hz modulation
    const modulationDepth = Math.random() * 0.3 + 0.1; // 10-40% pitch modulation
    const drive = Math.random() * 0.5; // 0-50% input drive

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

    const leftResonators: Array<{
      freq: number;
      state1: number;
      state2: number;
    }> = [];

    const rightResonators: Array<{
      freq: number;
      state1: number;
      state2: number;
    }> = [];

    // Create resonator banks with harmonic or inharmonic relationships
    const isHarmonic = Math.random() < 0.6;

    for (let i = 0; i < numResonators; i++) {
      let freqMultiplier: number;
      if (isHarmonic) {
        // Harmonic series
        freqMultiplier = Math.pow(2, i * spread);
      } else {
        // Inharmonic/stretched partials
        freqMultiplier = Math.pow(2, i * spread * (1 + Math.random() * 0.4));
      }

      const leftFreq = baseFreq * freqMultiplier;
      const rightFreq = leftFreq * (1 + (Math.random() - 0.5) * stereoSpread);

      leftResonators.push({
        freq: leftFreq,
        state1: 0,
        state2: 0
      });

      rightResonators.push({
        freq: rightFreq,
        state1: 0,
        state2: 0
      });
    }

    for (let i = 0; i < length; i++) {
      const t = i / this.sampleRate;

      // LFO for frequency modulation
      const lfo = Math.sin(2 * Math.PI * modulationRate * t);

      // Apply input drive
      let leftInput = leftChannel[i];
      let rightInput = rightChannel[i];

      if (drive > 0.1) {
        const driveAmount = 1 + drive * 2;
        leftInput = this.softSaturation(leftInput * driveAmount);
        rightInput = this.softSaturation(rightInput * driveAmount);
      }

      // Process through all resonators
      let leftResonant = 0;
      let rightResonant = 0;

      for (let r = 0; r < numResonators; r++) {
        const leftRes = leftResonators[r];
        const rightRes = rightResonators[r];

        // Modulate frequency
        const freqMod = 1 + lfo * modulationDepth;
        const leftModFreq = Math.min(leftRes.freq * freqMod, this.sampleRate * 0.45);
        const rightModFreq = Math.min(rightRes.freq * freqMod, this.sampleRate * 0.45);

        // Apply resonant filter
        const leftFiltered = this.stateVariableFilter(
          leftInput,
          leftModFreq,
          resonance,
          leftRes.state1,
          leftRes.state2
        );

        leftRes.state1 = leftFiltered.state1;
        leftRes.state2 = leftFiltered.state2;

        const rightFiltered = this.stateVariableFilter(
          rightInput,
          rightModFreq,
          resonance,
          rightRes.state1,
          rightRes.state2
        );

        rightRes.state1 = rightFiltered.state1;
        rightRes.state2 = rightFiltered.state2;

        // Amplitude compensation for number of resonators
        const ampScale = 1 / Math.sqrt(numResonators);
        leftResonant += leftFiltered.output * ampScale;
        rightResonant += rightFiltered.output * ampScale;
      }

      // Mix dry and wet signals
      const dryGain = Math.sqrt(1 - mix);
      const wetGain = Math.sqrt(mix);

      leftOut[i] = leftChannel[i] * dryGain + leftResonant * wetGain;
      rightOut[i] = rightChannel[i] * dryGain + rightResonant * wetGain;

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

    this.normalizeOutput(leftOut, rightOut);

    return [leftOut, rightOut];
  }

  private stateVariableFilter(
    input: number,
    cutoff: number,
    resonance: number,
    state1: number,
    state2: number
  ): { output: number; state1: number; state2: number } {
    const normalizedFreq = Math.min(cutoff / this.sampleRate, 0.48);
    const f = 2 * Math.sin(Math.PI * normalizedFreq);
    const q = Math.max(1 / Math.min(resonance, 20), 0.01);

    const lowpass = state2 + f * state1;
    const highpass = input - lowpass - q * state1;
    const bandpass = f * highpass + state1;

    // Clamp states to prevent instability
    const newState1 = Math.max(-3, Math.min(3, Math.abs(bandpass) > 1e-10 ? bandpass : 0));
    const newState2 = Math.max(-3, Math.min(3, Math.abs(lowpass) > 1e-10 ? lowpass : 0));

    return {
      output: bandpass,
      state1: newState1,
      state2: newState2
    };
  }

  private softSaturation(x: number): number {
    return x / (1 + Math.abs(x));
  }

  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;
      }
    }
  }
}
