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

export class Compressor implements AudioProcessor {
  getName(): string {
    return "Compressor";
  }

  getDescription(): string {
    return "Reduces dynamic range by taming peaks with makeup gain";
  }

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

  async process(
    leftChannel: Float32Array,
    rightChannel: Float32Array
  ): Promise<[Float32Array, Float32Array]> {
    const sampleRate = 44100;
    const thresholdDb = -12 - Math.random() * 6;
    const threshold = Math.pow(10, thresholdDb / 20);
    const ratio = 4 + Math.random() * 4;
    const attackMs = 1 + Math.random() * 4;
    const releaseMs = 50 + Math.random() * 100;

    const attackCoeff = Math.exp(-1 / (sampleRate * (attackMs / 1000)));
    const releaseCoeff = Math.exp(-1 / (sampleRate * (releaseMs / 1000)));

    const newLeft = new Float32Array(leftChannel.length);
    const newRight = new Float32Array(rightChannel.length);

    let envelopeL = 0;
    let envelopeR = 0;

    for (let i = 0; i < leftChannel.length; i++) {
      const inputL = Math.abs(leftChannel[i]);
      const inputR = Math.abs(rightChannel[i]);

      envelopeL = inputL > envelopeL
        ? attackCoeff * envelopeL + (1 - attackCoeff) * inputL
        : releaseCoeff * envelopeL + (1 - releaseCoeff) * inputL;

      envelopeR = inputR > envelopeR
        ? attackCoeff * envelopeR + (1 - attackCoeff) * inputR
        : releaseCoeff * envelopeR + (1 - releaseCoeff) * inputR;

      const peakEnvelope = Math.max(envelopeL, envelopeR);

      let gainReduction = 1;
      if (peakEnvelope > threshold) {
        const overThresholdDb = 20 * Math.log10(peakEnvelope / threshold);
        const compressedDb = overThresholdDb / ratio;
        const reductionDb = overThresholdDb - compressedDb;
        gainReduction = Math.pow(10, -reductionDb / 20);
      }

      newLeft[i] = leftChannel[i] * gainReduction;
      newRight[i] = rightChannel[i] * gainReduction;
    }

    const makeupGainDb = Math.abs(thresholdDb) * 0.5;
    const makeupGain = Math.pow(10, makeupGainDb / 20);

    for (let i = 0; i < newLeft.length; i++) {
      newLeft[i] *= makeupGain;
      newRight[i] *= makeupGain;
    }

    return [newLeft, newRight];
  }
}
