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

export class MicroPitch implements AudioProcessor {
  getName(): string {
    return 'Micro Pitch';
  }

  getDescription(): string {
    return 'Applies subtle random pitch variations for analog warmth and character';
  }

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

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

    // Random parameters for pitch variation
    const maxCents = 1 + Math.random() * 4; // 1-5 cents maximum deviation
    const lfoCount = 2 + Math.floor(Math.random() * 2); // 2-3 LFOs per channel
    const stereoSpread = 0.3 + Math.random() * 0.7; // 30-100% stereo difference

    // Process each channel independently for stereo variation
    const outputLeft = this.processMicroPitchChannel(
      leftChannel,
      length,
      sampleRate,
      maxCents,
      lfoCount,
      Math.random() // Random seed for left channel
    );

    const outputRight = this.processMicroPitchChannel(
      rightChannel,
      length,
      sampleRate,
      maxCents * (1 - stereoSpread * 0.5 + stereoSpread * Math.random()),
      lfoCount,
      Math.random() // Different seed for right channel
    );

    return [outputLeft, outputRight];
  }

  private processMicroPitchChannel(
    input: Float32Array,
    length: number,
    sampleRate: number,
    maxCents: number,
    lfoCount: number,
    seed: number
  ): Float32Array {
    const output = new Float32Array(length);

    // Initialize multiple LFOs with random parameters
    const lfos: Array<{
      frequency: number;
      phase: number;
      amplitude: number;
      type: 'sine' | 'triangle' | 'random-walk';
    }> = [];

    for (let i = 0; i < lfoCount; i++) {
      const randomValue = this.pseudoRandom(seed + i * 0.137);
      lfos.push({
        frequency: 0.1 + randomValue * 2, // 0.1-2.1 Hz
        phase: this.pseudoRandom(seed + i * 0.271) * Math.PI * 2,
        amplitude: 0.3 + this.pseudoRandom(seed + i * 0.419) * 0.7,
        type: this.pseudoRandom(seed + i * 0.613) > 0.6 ? 'triangle' :
               this.pseudoRandom(seed + i * 0.613) > 0.3 ? 'random-walk' : 'sine'
      });
    }

    // Random walk state for smooth random variations
    let randomWalkValue = 0;
    let randomWalkTarget = 0;
    const randomWalkSmooth = 0.999; // Very slow changes

    // Delay buffer for pitch shifting
    const maxDelayMs = 50; // Maximum delay in milliseconds
    const maxDelaySamples = Math.ceil(maxDelayMs * sampleRate / 1000);
    const delayBuffer = new Float32Array(length + maxDelaySamples * 2);

    // Copy input to delay buffer with padding
    for (let i = 0; i < input.length; i++) {
      delayBuffer[i + maxDelaySamples] = input[i];
    }

    // State for smooth delay interpolation
    let currentDelay = 0;
    const delaySmoothing = 0.9995; // Very smooth delay changes to avoid artifacts

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

      // Calculate combined pitch deviation from all LFOs
      let pitchDeviation = 0;

      for (const lfo of lfos) {
        let lfoValue = 0;

        if (lfo.type === 'sine') {
          lfoValue = Math.sin(2 * Math.PI * lfo.frequency * time + lfo.phase);
        } else if (lfo.type === 'triangle') {
          const phase = (lfo.frequency * time + lfo.phase / (2 * Math.PI)) % 1;
          lfoValue = 4 * Math.abs(phase - 0.5) - 1;
        } else if (lfo.type === 'random-walk') {
          // Update random walk every ~100 samples
          if (i % 100 === 0) {
            randomWalkTarget = (this.pseudoRandom(seed + i * 0.00001) - 0.5) * 2;
          }
          randomWalkValue = randomWalkValue * randomWalkSmooth +
                           randomWalkTarget * (1 - randomWalkSmooth);
          lfoValue = randomWalkValue;
        }

        pitchDeviation += lfoValue * lfo.amplitude;
      }

      // Normalize and scale pitch deviation
      pitchDeviation = (pitchDeviation / lfoCount) * (maxCents / 100);

      // Convert cents to pitch ratio (very small changes)
      const pitchRatio = Math.pow(2, pitchDeviation / 12);

      // Calculate time-varying delay for pitch shift
      const targetDelay = (pitchRatio - 1) * 1000; // Scaled for subtle effect
      currentDelay = currentDelay * delaySmoothing + targetDelay * (1 - delaySmoothing);

      // Read from delay buffer with high-quality interpolation
      const readPos = i + maxDelaySamples + currentDelay;
      const readIdx = Math.floor(readPos);
      const frac = readPos - readIdx;

      if (readIdx >= 2 && readIdx < delayBuffer.length - 3) {
        // 4-point Hermite interpolation for highest quality
        const y0 = delayBuffer[readIdx - 1];
        const y1 = delayBuffer[readIdx];
        const y2 = delayBuffer[readIdx + 1];
        const y3 = delayBuffer[readIdx + 2];

        output[i] = this.hermiteInterpolate(y0, y1, y2, y3, frac);
      } else if (readIdx >= 0 && readIdx < delayBuffer.length - 1) {
        // Linear interpolation fallback at boundaries
        const y1 = delayBuffer[readIdx];
        const y2 = readIdx + 1 < delayBuffer.length ? delayBuffer[readIdx + 1] : y1;
        output[i] = y1 + (y2 - y1) * frac;
      } else if (readIdx >= 0 && readIdx < delayBuffer.length) {
        output[i] = delayBuffer[readIdx];
      }
    }

    // Apply very subtle crossfade at boundaries to ensure smoothness
    const fadeLength = Math.min(100, Math.floor(length * 0.002));
    for (let i = 0; i < fadeLength; i++) {
      const fade = i / fadeLength;
      const smoothFade = 0.5 - 0.5 * Math.cos(Math.PI * fade);
      output[i] = input[i] * (1 - smoothFade) + output[i] * smoothFade;
      const endIdx = length - 1 - i;
      output[endIdx] = input[endIdx] * (1 - smoothFade) + output[endIdx] * smoothFade;
    }

    return output;
  }

  private hermiteInterpolate(y0: number, y1: number, y2: number, y3: number, x: number): number {
    // 4-point Hermite interpolation for smooth, high-quality interpolation
    const x2 = x * x;
    const x3 = x2 * x;

    // Hermite basis functions
    const h00 = 2 * x3 - 3 * x2 + 1;
    const h10 = x3 - 2 * x2 + x;
    const h01 = -2 * x3 + 3 * x2;
    const h11 = x3 - x2;

    // Tangents (using Catmull-Rom)
    const m0 = 0.5 * (y2 - y0);
    const m1 = 0.5 * (y3 - y1);

    return h00 * y1 + h10 * m0 + h01 * y2 + h11 * m1;
  }

  private pseudoRandom(seed: number): number {
    // Simple deterministic pseudo-random number generator
    const x = Math.sin(seed * 12.9898 + seed * 78.233) * 43758.5453;
    return x - Math.floor(x);
  }
}