import type { SynthEngine, PitchLock } from './base/SynthEngine';

enum OscillatorWaveform {
  Sine,
  Triangle,
  Square,
  Saw,
  Pulse,
}

enum SweepCurve {
  Linear,
  Exponential,
  Logarithmic,
  Bounce,
  Elastic,
}

enum FilterType {
  None,
  LowPass,
  HighPass,
  BandPass,
}

interface DubSirenParams {
  startFreq: number;
  endFreq: number;
  sweepCurve: SweepCurve;
  waveform: OscillatorWaveform;
  pulseWidth: number;
  harmonics: number;
  harmonicSpread: number;
  lfoRate: number;
  lfoDepth: number;
  filterType: FilterType;
  filterFreq: number;
  filterResonance: number;
  filterSweepAmount: number;
  attack: number;
  decay: number;
  sustain: number;
  release: number;
  feedback: number;
  stereoWidth: number;
  distortion: number;
}

export class DubSiren implements SynthEngine<DubSirenParams> {
  private filterHistoryL1 = 0;
  private filterHistoryL2 = 0;
  private filterHistoryR1 = 0;
  private filterHistoryR2 = 0;
  private dcBlockerL = 0;
  private dcBlockerR = 0;
  private readonly DENORMAL_OFFSET = 1e-24;

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

  getDescription(): string {
    return 'Siren generator with pitch sweeps, anti-aliased oscillators and stable filtering';
  }

  getType() {
    return 'generative' as const;
  }

  getCategory() {
    return 'Experimental' as const;
  }

  generate(params: DubSirenParams, sampleRate: number, duration: number): [Float32Array, Float32Array] {
    const numSamples = Math.floor(sampleRate * duration);
    const leftBuffer = new Float32Array(numSamples);
    const rightBuffer = new Float32Array(numSamples);
    const TAU = Math.PI * 2;
    const invSampleRate = 1 / sampleRate;

    // Initialize phases for oscillators
    const numOscillators = 1 + params.harmonics;
    const phasesL: number[] = new Array(numOscillators).fill(0);
    const phasesR: number[] = new Array(numOscillators).fill(0);

    // Stereo phase offset
    const stereoPhaseOffset = Math.PI * params.stereoWidth * 0.1;
    for (let i = 0; i < numOscillators; i++) {
      phasesR[i] = stereoPhaseOffset * (i + 1);
    }

    // LFO setup
    let lfoPhaseL = 0;
    let lfoPhaseR = Math.PI * params.stereoWidth * 0.25;
    const lfoIncrement = TAU * params.lfoRate * invSampleRate;

    // Feedback buffers with slight delay for richness
    const feedbackDelaySize = 64;
    const feedbackBufferL = new Float32Array(feedbackDelaySize);
    const feedbackBufferR = new Float32Array(feedbackDelaySize);
    let feedbackIndex = 0;

    // Reset filter state
    this.filterHistoryL1 = 0;
    this.filterHistoryL2 = 0;
    this.filterHistoryR1 = 0;
    this.filterHistoryR2 = 0;
    this.dcBlockerL = 0;
    this.dcBlockerR = 0;

    // Envelope smoothing
    let lastEnv = 0;
    const envSmoothCoeff = 0.001;

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

      // Calculate and smooth envelope
      const targetEnv = this.calculateEnvelope(
        t * duration,
        duration,
        params.attack,
        params.decay,
        params.sustain,
        params.release
      );
      const env = lastEnv + (targetEnv - lastEnv) * envSmoothCoeff;
      lastEnv = env;

      // Calculate pitch sweep
      const sweepProgress = this.calculateSweepCurve(t, params.sweepCurve);
      const currentFreq = params.startFreq + (params.endFreq - params.startFreq) * sweepProgress;

      // LFO modulation (using fast approximation)
      const lfoL = this.fastSin(lfoPhaseL);
      const lfoR = this.fastSin(lfoPhaseR);
      const pitchModL = 1 + lfoL * params.lfoDepth * 0.1;
      const pitchModR = 1 + lfoR * params.lfoDepth * 0.1;

      // Generate oscillators with harmonics
      let sampleL = 0;
      let sampleR = 0;

      for (let osc = 0; osc < numOscillators; osc++) {
        const harmonicMultiplier = 1 + osc * params.harmonicSpread;
        const harmonicLevel = 1 / (osc + 1);

        const freqL = currentFreq * harmonicMultiplier * pitchModL;
        const freqR = currentFreq * harmonicMultiplier * pitchModR;

        // Add delayed feedback to first oscillator
        const fbL = osc === 0 ? feedbackBufferL[feedbackIndex] * params.feedback * 0.3 : 0;
        const fbR = osc === 0 ? feedbackBufferR[feedbackIndex] * params.feedback * 0.3 : 0;

        // Use bandlimited oscillators
        const oscL = this.generateBandlimitedWaveform(
          phasesL[osc],
          params.waveform,
          params.pulseWidth,
          freqL,
          sampleRate
        );
        const oscR = this.generateBandlimitedWaveform(
          phasesR[osc],
          params.waveform,
          params.pulseWidth,
          freqR,
          sampleRate
        );

        sampleL += (oscL + fbL) * harmonicLevel;
        sampleR += (oscR + fbR) * harmonicLevel;

        // Update phases with proper wrapping
        const phaseIncrementL = TAU * freqL * invSampleRate;
        const phaseIncrementR = TAU * freqR * invSampleRate;
        phasesL[osc] = (phasesL[osc] + phaseIncrementL) % TAU;
        phasesR[osc] = (phasesR[osc] + phaseIncrementR) % TAU;
      }

      // Normalize with headroom
      const normFactor = 0.5 / Math.sqrt(numOscillators);
      sampleL *= normFactor;
      sampleR *= normFactor;

      // Apply soft saturation if needed
      if (params.distortion > 0) {
        sampleL = this.fastTanh(sampleL * (1 + params.distortion * 3)) * 0.8;
        sampleR = this.fastTanh(sampleR * (1 + params.distortion * 3)) * 0.8;
      }

      // Apply filter with stability checks
      if (params.filterType !== FilterType.None) {
        const filterFreqMod = params.filterFreq * (1 + params.filterSweepAmount * sweepProgress * 2);
        const filterFreqWithLFO = Math.min(filterFreqMod * (1 + lfoL * params.lfoDepth * 0.2), sampleRate * 0.45);
        const safeResonance = Math.min(params.filterResonance, 0.98);

        [sampleL, this.filterHistoryL1, this.filterHistoryL2] = this.applyStableFilter(
          sampleL,
          params.filterType,
          filterFreqWithLFO,
          safeResonance,
          sampleRate,
          this.filterHistoryL1,
          this.filterHistoryL2
        );

        [sampleR, this.filterHistoryR1, this.filterHistoryR2] = this.applyStableFilter(
          sampleR,
          params.filterType,
          filterFreqWithLFO,
          safeResonance,
          sampleRate,
          this.filterHistoryR1,
          this.filterHistoryR2
        );
      }

      // Update feedback delay buffer
      feedbackBufferL[feedbackIndex] = sampleL;
      feedbackBufferR[feedbackIndex] = sampleR;
      feedbackIndex = (feedbackIndex + 1) % feedbackDelaySize;

      // DC blocking
      const dcCutoff = 0.995;
      const blockedL = sampleL - this.dcBlockerL;
      const blockedR = sampleR - this.dcBlockerR;
      this.dcBlockerL = sampleL - blockedL * dcCutoff;
      this.dcBlockerR = sampleR - blockedR * dcCutoff;

      // Apply envelope and final limiting
      leftBuffer[i] = Math.max(-1, Math.min(1, blockedL * env));
      rightBuffer[i] = Math.max(-1, Math.min(1, blockedR * env));

      // Update LFO phases
      lfoPhaseL = (lfoPhaseL + lfoIncrement) % TAU;
      lfoPhaseR = (lfoPhaseR + lfoIncrement) % TAU;
    }

    // Peak normalization with headroom
    let peakL = 0;
    let peakR = 0;
    for (let i = 0; i < numSamples; i++) {
      peakL = Math.max(peakL, Math.abs(leftBuffer[i]));
      peakR = Math.max(peakR, Math.abs(rightBuffer[i]));
    }
    const peak = Math.max(peakL, peakR);

    if (peak > 0.001) {
      const normalizeGain = 0.85 / peak;
      for (let i = 0; i < numSamples; i++) {
        leftBuffer[i] *= normalizeGain;
        rightBuffer[i] *= normalizeGain;
      }
    }

    return [leftBuffer, rightBuffer];
  }

  private generateBandlimitedWaveform(
    phase: number,
    waveform: OscillatorWaveform,
    pulseWidth: number,
    frequency: number,
    sampleRate: number
  ): number {
    const nyquist = sampleRate / 2;
    const maxHarmonic = Math.floor(nyquist / frequency);

    switch (waveform) {
      case OscillatorWaveform.Sine:
        return Math.sin(phase);

      case OscillatorWaveform.Triangle:
        // Bandlimited triangle using additive synthesis
        let tri = 0;
        const harmonics = Math.min(maxHarmonic, 32);
        for (let h = 1; h <= harmonics; h += 2) {
          const sign = ((h - 1) / 2) % 2 === 0 ? 1 : -1;
          tri += sign * Math.sin(phase * h) / (h * h);
        }
        return tri * (8 / (Math.PI * Math.PI));

      case OscillatorWaveform.Square:
        // Bandlimited square using additive synthesis
        let square = 0;
        const squareHarmonics = Math.min(maxHarmonic, 32);
        for (let h = 1; h <= squareHarmonics; h += 2) {
          square += Math.sin(phase * h) / h;
        }
        return square * (4 / Math.PI);

      case OscillatorWaveform.Saw:
        // Bandlimited saw using additive synthesis
        let saw = 0;
        const sawHarmonics = Math.min(maxHarmonic, 32);
        for (let h = 1; h <= sawHarmonics; h++) {
          saw += Math.sin(phase * h) / h;
        }
        return -saw * (2 / Math.PI);

      case OscillatorWaveform.Pulse:
        // Bandlimited pulse as difference of two saws
        let pulse1 = 0;
        let pulse2 = 0;
        const pulseHarmonics = Math.min(maxHarmonic, 32);
        const phaseShift = phase + Math.PI * 2 * pulseWidth;
        for (let h = 1; h <= pulseHarmonics; h++) {
          pulse1 += Math.sin(phase * h) / h;
          pulse2 += Math.sin(phaseShift * h) / h;
        }
        return (pulse1 - pulse2) * (2 / Math.PI);

      default:
        return 0;
    }
  }

  private calculateSweepCurve(t: number, curve: SweepCurve): number {
    switch (curve) {
      case SweepCurve.Linear:
        return t;

      case SweepCurve.Exponential:
        return t * t;

      case SweepCurve.Logarithmic:
        return Math.sqrt(t);

      case SweepCurve.Bounce:
        return t < 0.5 ? t * 2 : 2 - t * 2;

      case SweepCurve.Elastic:
        const p = 0.3;
        const s = p / 4;
        if (t <= 0.001) return 0;
        if (t >= 0.999) return 1;
        return Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;

      default:
        return t;
    }
  }

  private calculateEnvelope(
    t: number,
    duration: number,
    attack: number,
    decay: number,
    sustain: number,
    release: number
  ): number {
    const attackTime = attack * duration;
    const decayTime = decay * duration;
    const releaseTime = release * duration;
    const sustainStart = attackTime + decayTime;
    const releaseStart = duration - releaseTime;

    if (t < attackTime) {
      // Exponential attack for smoother onset
      const progress = t / attackTime;
      return progress * progress;
    } else if (t < sustainStart) {
      const decayProgress = (t - attackTime) / decayTime;
      return 1 - decayProgress * (1 - sustain);
    } else if (t < releaseStart) {
      return sustain;
    } else {
      const releaseProgress = (t - releaseStart) / releaseTime;
      // Exponential release for smoother tail
      return sustain * Math.pow(1 - releaseProgress, 2);
    }
  }

  private fastSin(phase: number): number {
    // Fast sine approximation using parabolic approximation
    const x = (phase % (Math.PI * 2)) / Math.PI - 1;
    const x2 = x * x;
    return x * (1 - x2 * (0.16666 - x2 * 0.00833));
  }

  private fastTanh(x: number): number {
    // Fast tanh approximation for soft clipping
    const x2 = x * x;
    return x * (27 + x2) / (27 + 9 * x2);
  }

  private applyStableFilter(
    input: number,
    filterType: FilterType,
    freq: number,
    resonance: number,
    sampleRate: number,
    history1: number,
    history2: number
  ): [number, number, number] {
    // Add denormal prevention
    input += this.DENORMAL_OFFSET;

    // Improved state-variable filter with pre-warping
    const w = Math.tan((Math.PI * freq) / sampleRate);
    const g = w / (1 + w);
    const k = 2 - 2 * resonance; // Stability-safe resonance scaling

    // State variable filter equations
    const v0 = input;
    const v1 = history1;
    const v2 = history2;
    const v3 = v0 - v2;
    const v1Next = v1 + g * (v3 - k * v1);
    const v2Next = v2 + g * v1Next;

    let output: number;
    switch (filterType) {
      case FilterType.LowPass:
        output = v2Next;
        break;
      case FilterType.HighPass:
        output = v0 - k * v1Next - v2Next;
        break;
      case FilterType.BandPass:
        output = v1Next;
        break;
      default:
        output = input;
    }

    // Remove denormal offset
    output -= this.DENORMAL_OFFSET;

    return [output, v1Next, v2Next];
  }

  randomParams(pitchLock?: PitchLock): DubSirenParams {
    let startFreq: number;
    let endFreq: number;

    if (pitchLock?.enabled) {
      // When pitch locked, sweep around the locked frequency
      startFreq = pitchLock.frequency;
      endFreq = pitchLock.frequency * (Math.random() < 0.5 ? 0.5 : 2);
    } else {
      const freqPairs = [
        [100, 1200],
        [200, 800],
        [300, 2000],
        [50, 400],
        [500, 3000],
        [150, 600],
      ];

      const [freq1, freq2] = this.randomChoice(freqPairs);
      const shouldReverse = Math.random() < 0.3;
      startFreq = shouldReverse ? freq2 : freq1;
      endFreq = shouldReverse ? freq1 : freq2;
    }

    return {
      startFreq,
      endFreq,
      sweepCurve: this.randomInt(0, 4) as SweepCurve,
      waveform: this.randomInt(0, 4) as OscillatorWaveform,
      pulseWidth: this.randomRange(0.1, 0.9),
      harmonics: this.randomInt(0, 3),
      harmonicSpread: this.randomRange(1.5, 3.0),
      lfoRate: this.randomRange(0.5, 8),
      lfoDepth: this.randomRange(0, 0.5),
      filterType: this.randomInt(0, 3) as FilterType,
      filterFreq: this.randomRange(200, 4000),
      filterResonance: this.randomRange(0.1, 0.85), // Safer maximum
      filterSweepAmount: this.randomRange(0, 1),
      attack: this.randomRange(0.005, 0.1), // Minimum 220 samples
      decay: this.randomRange(0.01, 0.2),
      sustain: this.randomRange(0.3, 0.9),
      release: this.randomRange(0.1, 0.4),
      feedback: this.randomRange(0, 0.5),
      stereoWidth: this.randomRange(0.2, 0.8),
      distortion: this.randomRange(0, 0.3),
    };
  }

  mutateParams(params: DubSirenParams, mutationAmount: number = 0.15, pitchLock?: PitchLock): DubSirenParams {
    let startFreq: number;
    let endFreq: number;

    if (pitchLock?.enabled) {
      // When pitch locked, keep one frequency at the locked value
      if (Math.random() < 0.5) {
        startFreq = pitchLock.frequency;
        endFreq = this.mutateValue(params.endFreq, mutationAmount, 20, 5000);
      } else {
        startFreq = this.mutateValue(params.startFreq, mutationAmount, 20, 5000);
        endFreq = pitchLock.frequency;
      }
    } else {
      startFreq = this.mutateValue(params.startFreq, mutationAmount, 20, 5000);
      endFreq = this.mutateValue(params.endFreq, mutationAmount, 20, 5000);
    }

    return {
      startFreq,
      endFreq,
      sweepCurve: Math.random() < 0.1 ? this.randomInt(0, 4) as SweepCurve : params.sweepCurve,
      waveform: Math.random() < 0.1 ? this.randomInt(0, 4) as OscillatorWaveform : params.waveform,
      pulseWidth: this.mutateValue(params.pulseWidth, mutationAmount, 0.05, 0.95),
      harmonics: Math.random() < 0.15 ? this.randomInt(0, 3) : params.harmonics,
      harmonicSpread: this.mutateValue(params.harmonicSpread, mutationAmount, 1, 4),
      lfoRate: this.mutateValue(params.lfoRate, mutationAmount, 0.1, 12),
      lfoDepth: this.mutateValue(params.lfoDepth, mutationAmount, 0, 0.7),
      filterType: Math.random() < 0.1 ? this.randomInt(0, 3) as FilterType : params.filterType,
      filterFreq: this.mutateValue(params.filterFreq, mutationAmount, 100, 8000),
      filterResonance: this.mutateValue(params.filterResonance, mutationAmount, 0, 0.85),
      filterSweepAmount: this.mutateValue(params.filterSweepAmount, mutationAmount, 0, 1),
      attack: this.mutateValue(params.attack, mutationAmount, 0.005, 0.2),
      decay: this.mutateValue(params.decay, mutationAmount, 0.01, 0.3),
      sustain: this.mutateValue(params.sustain, mutationAmount, 0.1, 1),
      release: this.mutateValue(params.release, mutationAmount, 0.05, 0.5),
      feedback: this.mutateValue(params.feedback, mutationAmount, 0, 0.7),
      stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 1),
      distortion: this.mutateValue(params.distortion, mutationAmount, 0, 0.5),
    };
  }

  private randomRange(min: number, max: number): number {
    return min + Math.random() * (max - min);
  }

  private randomInt(min: number, max: number): number {
    return Math.floor(this.randomRange(min, max + 1));
  }

  private randomChoice<T>(choices: readonly T[]): T {
    return choices[Math.floor(Math.random() * choices.length)];
  }

  private mutateValue(value: number, amount: number, min: number, max: number): number {
    const variation = (max - min) * amount * (Math.random() * 2 - 1);
    return Math.max(min, Math.min(max, value + variation));
  }
}