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

export class PitchWobble implements AudioProcessor {
  getName(): string {
    return 'Pitch Wobble';
  }

  getDescription(): string {
    return 'Variable-rate playback with LFO modulation for tape wow/vibrato effects';
  }

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

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

    // Random parameters
    const lfoRate = 0.5 + Math.random() * 4.5; // 0.5-5 Hz
    const depth = 0.001 + Math.random() * 0.019; // 0.1% to 2% pitch deviation
    const lfoShape = Math.random(); // 0-1, controls sine vs triangle mix

    // Secondary LFO for more organic movement
    const lfo2Rate = lfoRate * (2 + Math.random() * 3);
    const lfo2Depth = depth * 0.3;

    const outputLeft = new Float32Array(length);
    const outputRight = new Float32Array(length);

    // Delay line size - enough for max pitch deviation
    const maxDelay = Math.ceil(sampleRate * 0.1); // 100ms max delay
    const delayLineL = new Float32Array(length + maxDelay);
    const delayLineR = new Float32Array(length + maxDelay);

    // Copy input to delay lines with padding
    for (let i = 0; i < length; i++) {
      delayLineL[i + maxDelay / 2] = leftChannel[i];
      delayLineR[i + maxDelay / 2] = rightChannel[i];
    }

    // Phase accumulator for continuous phase
    let readPos = maxDelay / 2;

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

      // Primary LFO - mix of sine and triangle
      const sineLfo = Math.sin(2 * Math.PI * lfoRate * t);
      const triangleLfo = 2 * Math.abs(2 * ((lfoRate * t) % 1) - 1) - 1;
      const lfo1 = sineLfo * (1 - lfoShape) + triangleLfo * lfoShape;

      // Secondary LFO for complexity
      const lfo2 = Math.sin(2 * Math.PI * lfo2Rate * t + Math.PI / 3);

      // Combined modulation
      const pitchMod = 1 + (lfo1 * depth + lfo2 * lfo2Depth);

      // Variable playback rate
      const currentRate = pitchMod;

      // Cubic interpolation for smooth pitch changes
      outputLeft[i] = this.cubicInterpolate(delayLineL, readPos);
      outputRight[i] = this.cubicInterpolate(delayLineR, readPos);

      // Advance read position with variable rate
      readPos += currentRate;
    }

    // Soft limiting to prevent clipping
    for (let i = 0; i < length; i++) {
      outputLeft[i] = this.softClip(outputLeft[i]);
      outputRight[i] = this.softClip(outputRight[i]);
    }

    return [outputLeft, outputRight];
  }

  private cubicInterpolate(buffer: Float32Array, position: number): number {
    const index = Math.floor(position);
    const fraction = position - index;

    // Bounds checking
    if (index < 1 || index >= buffer.length - 2) {
      return buffer[Math.min(Math.max(index, 0), buffer.length - 1)];
    }

    const y0 = buffer[index - 1];
    const y1 = buffer[index];
    const y2 = buffer[index + 1];
    const y3 = buffer[index + 2];

    // Cubic Hermite interpolation
    const a0 = y3 - y2 - y0 + y1;
    const a1 = y0 - y1 - a0;
    const a2 = y2 - y0;
    const a3 = y1;

    const fraction2 = fraction * fraction;
    const fraction3 = fraction2 * fraction;

    return a0 * fraction3 + a1 * fraction2 + a2 * fraction + a3;
  }

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