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

interface BenjolinParams {
  // Core oscillators
  osc1Freq: number;
  osc2Freq: number;
  osc1Wave: number;  // 0=tri, 1=saw, morphable
  osc2Wave: number;  // 0=tri, 1=pulse

  // Cross modulation matrix
  crossMod1to2: number;
  crossMod2to1: number;
  crossMod1toFilter: number;
  crossMod2toRungler: number;

  // Rungler parameters
  runglerToOsc1: number;
  runglerToOsc2: number;
  runglerToFilter: number;
  runglerBits: number;  // 8, 12, or 16 bit modes
  runglerFeedback: number;
  runglerChaos: number;  // Amount of XOR chaos

  // Filter parameters
  filterCutoff: number;
  filterResonance: number;
  filterMode: number;  // 0=LP, 0.5=BP, 1=HP morphable
  filterDrive: number;  // Input overdrive
  filterFeedback: number;  // Self-oscillation amount

  // Evolution parameters (new!)
  evolutionRate: number;  // How fast parameters drift
  evolutionDepth: number;  // How much they drift
  chaosAttractorRate: number;  // Secondary chaos source
  chaosAttractorDepth: number;

  // Modulation LFOs (new!)
  lfo1Rate: number;  // Ratio of duration
  lfo1Depth: number;
  lfo1Target: number;  // 0=freq, 0.5=filter, 1=chaos
  lfo2Rate: number;
  lfo2Depth: number;
  lfo2Wave: number;  // 0=sine, 0.5=tri, 1=S&H

  // Output shaping
  wavefoldAmount: number;
  distortionType: number;  // 0=soft, 0.5=fold, 1=digital
  stereoWidth: number;
  outputGain: number;

  // Envelope
  envelopeAttack: number;
  envelopeDecay: number;
  envelopeSustain: number;
  envelopeRelease: number;
  envelopeToFilter: number;
  envelopeToFold: number;
}

// Preset configuration count
const PRESET_COUNT = 18;

export class Benjolin implements SynthEngine<BenjolinParams> {
  getName(): string {
    return 'Bubolin';
  }

  getDescription(): string {
    return 'Some kind of rungler/benjolin inspired generator';
  }

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

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

  generate(params: BenjolinParams, sampleRate: number, duration: number): [Float32Array, Float32Array] {
    const numSamples = Math.floor(duration * sampleRate);
    const left = new Float32Array(numSamples);
    const right = new Float32Array(numSamples);

    // Extract parameters with intelligent defaults
    const osc1Freq = params.osc1Freq ?? 220;
    const osc2Freq = params.osc2Freq ?? 330;
    const osc1Wave = params.osc1Wave ?? 0;
    const osc2Wave = params.osc2Wave ?? 0;

    const crossMod1to2 = params.crossMod1to2 ?? 0.3;
    const crossMod2to1 = params.crossMod2to1 ?? 0.3;
    const crossMod1toFilter = params.crossMod1toFilter ?? 0.2;
    const crossMod2toRungler = params.crossMod2toRungler ?? 0.2;

    const runglerToOsc1 = params.runglerToOsc1 ?? 0.2;
    const runglerToOsc2 = params.runglerToOsc2 ?? 0.2;
    const runglerToFilter = params.runglerToFilter ?? 0.3;
    const runglerBits = Math.floor(params.runglerBits ?? 8);
    const runglerFeedback = params.runglerFeedback ?? 0.3;
    const runglerChaos = params.runglerChaos ?? 0.5;

    const filterCutoff = params.filterCutoff ?? 1000;
    const filterResonance = params.filterResonance ?? 0.8;
    const filterMode = params.filterMode ?? 0;
    const filterDrive = params.filterDrive ?? 1.5;
    const filterFeedback = params.filterFeedback ?? 0;

    const evolutionRate = params.evolutionRate ?? 0.1;
    const evolutionDepth = params.evolutionDepth ?? 0.2;
    const chaosAttractorRate = params.chaosAttractorRate ?? 0.05;
    const chaosAttractorDepth = params.chaosAttractorDepth ?? 0.3;

    const lfo1Rate = params.lfo1Rate ?? 0.1;
    const lfo1Depth = params.lfo1Depth ?? 0.2;
    const lfo1Target = params.lfo1Target ?? 0.5;
    const lfo2Rate = params.lfo2Rate ?? 0.3;
    const lfo2Depth = params.lfo2Depth ?? 0.15;
    const lfo2Wave = params.lfo2Wave ?? 0;

    const wavefoldAmount = params.wavefoldAmount ?? 0;
    const distortionType = params.distortionType ?? 0;
    const stereoWidth = params.stereoWidth ?? 0.5;
    const outputGain = params.outputGain ?? 0.5;

    const envelopeAttack = params.envelopeAttack ?? 0.05;
    const envelopeDecay = params.envelopeDecay ?? 0.15;
    const envelopeSustain = params.envelopeSustain ?? 0.3;
    const envelopeRelease = params.envelopeRelease ?? 0.5;
    const envelopeToFilter = params.envelopeToFilter ?? 0.3;
    const envelopeToFold = params.envelopeToFold ?? 0.2;

    // Oscillator states
    let osc1Phase = 0;
    let osc2Phase = 0;
    let osc1LastOutput = 0;
    let osc2LastOutput = 0;
    let osc2LastCrossing = false;

    // Extended rungler state (up to 16-bit)
    const runglerMask = (1 << runglerBits) - 1;
    let runglerRegister = Math.floor(Math.random() * runglerMask);
    let runglerCV = 0;
    let runglerSmoothed = 0;
    let runglerHistory = 0;  // For feedback
    const runglerSmoothFactor = 0.995;

    // Filter states (dual state-variable filters)
    let filter1LP = 0, filter1HP = 0, filter1BP = 0;
    let filter2LP = 0, filter2HP = 0, filter2BP = 0;
    let filterSelfOsc = 0;

    // Evolution and chaos states
    let evolutionPhase = 0;
    let evolutionValue = 0;
    let chaosX = 0.1, chaosY = 0.1, chaosZ = 0.1;  // Lorenz attractor
    let driftAccumulator = 0;

    // LFO states
    let lfo1Phase = 0;
    let lfo2Phase = 0;
    let lfo2SampleHold = 0;
    let lfo2LastPhase = 0;

    // Envelope follower for rungler
    let runglerEnvelope = 0;
    const envelopeFollowRate = 0.99;

    // Wavefolder state
    let wavefoldIntegrator = 0;

    // DC blocker states
    let dcBlockerX1L = 0, dcBlockerY1L = 0;
    let dcBlockerX1R = 0, dcBlockerY1R = 0;
    const dcBlockerCutoff = 20 / sampleRate;
    const dcBlockerAlpha = 1 - dcBlockerCutoff;

    // Envelope
    const attackSamples = Math.floor(envelopeAttack * duration * sampleRate);
    const decaySamples = Math.floor(envelopeDecay * duration * sampleRate);
    const releaseSamples = Math.floor(envelopeRelease * duration * sampleRate);
    const sustainSamples = Math.max(0, numSamples - attackSamples - decaySamples - releaseSamples);

    // Stereo delay line for width
    const delayLength = Math.floor(0.003 * sampleRate);  // 3ms
    const delayBuffer = new Float32Array(delayLength);
    let delayIndex = 0;

    for (let i = 0; i < numSamples; i++) {
      // Calculate main envelope
      let envelope = 0;
      if (i < attackSamples) {
        envelope = i / attackSamples;
      } else if (i < attackSamples + decaySamples) {
        const decayProgress = (i - attackSamples) / decaySamples;
        envelope = 1 - decayProgress * (1 - envelopeSustain);
      } else if (i < attackSamples + decaySamples + sustainSamples) {
        envelope = envelopeSustain;
      } else {
        const releaseProgress = (i - attackSamples - decaySamples - sustainSamples) / releaseSamples;
        envelope = envelopeSustain * (1 - releaseProgress);
      }

      // Update evolution (slow parameter drift)
      evolutionPhase += evolutionRate / sampleRate;
      evolutionValue = Math.sin(evolutionPhase * 2 * Math.PI) * evolutionDepth;

      // Update chaos attractor (Lorenz system) with safety bounds
      const dt = 0.01 * chaosAttractorRate;
      const sigma = 10;
      const rho = 28;
      const beta = 8/3;

      // Calculate derivatives
      const dx = sigma * (chaosY - chaosX) * dt;
      const dy = (chaosX * (rho - chaosZ) - chaosY) * dt;
      const dz = (chaosX * chaosY - beta * chaosZ) * dt;

      // Update with bounds checking
      chaosX = Math.max(-50, Math.min(50, chaosX + dx));
      chaosY = Math.max(-50, Math.min(50, chaosY + dy));
      chaosZ = Math.max(-50, Math.min(50, chaosZ + dz));

      // Check for NaN and reset if necessary
      if (!isFinite(chaosX) || !isFinite(chaosY) || !isFinite(chaosZ)) {
        chaosX = 0.1;
        chaosY = 0.1;
        chaosZ = 0.1;
      }

      const chaosValue = Math.tanh(chaosZ * 0.01) * chaosAttractorDepth;

      // Update LFOs
      lfo1Phase += lfo1Rate / sampleRate;
      if (lfo1Phase >= 1) lfo1Phase -= 1;
      const lfo1Value = Math.sin(lfo1Phase * 2 * Math.PI) * lfo1Depth;

      lfo2Phase += lfo2Rate / sampleRate;
      if (lfo2Phase >= 1) lfo2Phase -= 1;

      // LFO2 waveform selection
      let lfo2Value = 0;
      if (lfo2Wave < 0.33) {
        // Sine
        lfo2Value = Math.sin(lfo2Phase * 2 * Math.PI);
      } else if (lfo2Wave < 0.67) {
        // Triangle
        lfo2Value = lfo2Phase < 0.5 ? lfo2Phase * 4 - 1 : 3 - lfo2Phase * 4;
      } else {
        // Sample & Hold
        if (lfo2Phase < lfo2LastPhase) {
          lfo2SampleHold = Math.random() * 2 - 1;
        }
        lfo2Value = lfo2SampleHold;
      }
      lfo2LastPhase = lfo2Phase;
      lfo2Value *= lfo2Depth;

      // Track rungler envelope
      runglerEnvelope = runglerEnvelope * envelopeFollowRate +
                       Math.abs(runglerCV) * (1 - envelopeFollowRate);

      // Smooth rungler CV with drift
      driftAccumulator += (Math.random() - 0.5) * 0.001;
      driftAccumulator *= 0.999;  // Decay
      runglerSmoothed += (runglerCV - runglerSmoothed + driftAccumulator) * (1 - runglerSmoothFactor);

      // Apply LFO1 based on target
      let lfo1Modulation = 0;
      if (lfo1Target < 0.33) {
        lfo1Modulation = lfo1Value;  // To frequency
      } else if (lfo1Target < 0.67) {
        lfo1Modulation = lfo1Value * 0.5;  // To filter
      } else {
        lfo1Modulation = lfo1Value * 0.3;  // To chaos
      }

      // Calculate oscillator frequencies with complex modulation
      const osc1ModFreq = osc1Freq *
        (1 + osc2LastOutput * crossMod2to1 * (2 + evolutionValue) +
         runglerSmoothed * runglerToOsc1 * (1 + chaosValue) +
         lfo1Modulation * (lfo1Target < 0.5 ? 1 : 0));

      const osc2ModFreq = osc2Freq *
        (1 + osc1LastOutput * crossMod1to2 * (2 + chaosValue) +
         runglerSmoothed * runglerToOsc2 * (1 + evolutionValue) +
         lfo2Value * 0.5);

      // Clamp frequencies but allow wider range
      const clampedOsc1Freq = Math.max(1, Math.min(12000, osc1ModFreq));
      const clampedOsc2Freq = Math.max(1, Math.min(12000, osc2ModFreq));

      // Update oscillator phases
      const osc1PhaseInc = clampedOsc1Freq / sampleRate;
      const osc2PhaseInc = clampedOsc2Freq / sampleRate;

      osc1Phase += osc1PhaseInc;
      osc2Phase += osc2PhaseInc;

      if (osc1Phase >= 1) osc1Phase -= 1;
      if (osc2Phase >= 1) osc2Phase -= 1;

      // Generate morphable oscillator waveforms
      const osc1Tri = this.polyBlepTriangle(osc1Phase, osc1PhaseInc);
      const osc1Saw = this.polyBlepSaw(osc1Phase, osc1PhaseInc);
      const osc1Output = osc1Tri * (1 - osc1Wave) + osc1Saw * osc1Wave;

      const osc2Tri = this.polyBlepTriangle(osc2Phase, osc2PhaseInc);
      const osc2Pulse = this.polyBlepPulse(osc2Phase, osc2PhaseInc, 0.5 + osc2Wave * 0.45);
      const osc2Output = osc2Tri * (1 - osc2Wave) + osc2Pulse * osc2Wave;

      // Enhanced rungler with feedback
      const osc2Crossing = osc2Output > 0;
      if (osc2Crossing && !osc2LastCrossing) {
        // Shift with feedback
        runglerRegister = (runglerRegister << 1) & runglerMask;

        // Sample input with cross-modulation influence
        const runglerInput = osc1Output + crossMod2toRungler * osc2Output;
        if (runglerInput > runglerHistory * runglerFeedback) {
          runglerRegister |= 1;
        }
        runglerHistory = runglerInput;

        // Enhanced XOR chaos
        let xorResult = 0;
        const numBits = runglerBits - 1;
        for (let bit = 0; bit < numBits; bit++) {
          const bitA = (runglerRegister >> bit) & 1;
          const bitB = (runglerRegister >> ((bit + 1) % runglerBits)) & 1;
          const bitC = (runglerRegister >> ((bit + 3) % runglerBits)) & 1;
          // Three-way XOR for more chaos
          xorResult |= ((bitA ^ bitB ^ bitC) & 1) << bit;
        }

        // Mix direct register value with XOR chaos
        const directValue = runglerRegister / runglerMask;
        const chaosValueNorm = xorResult / runglerMask;
        runglerCV = (directValue * (1 - runglerChaos) + chaosValueNorm * runglerChaos) * 2 - 1;
      }
      osc2LastCrossing = osc2Crossing;

      // Dual state-variable filters with self-oscillation
      const modulatedCutoff = filterCutoff *
        (1 + runglerSmoothed * runglerToFilter * 0.5 +
         osc1Output * crossMod1toFilter * 0.3 +
         envelope * envelopeToFilter +
         (lfo1Target > 0.33 && lfo1Target < 0.67 ? lfo1Modulation : 0) +
         chaosValue * 0.2);

      const clampedCutoff = Math.max(20, Math.min(sampleRate * 0.48, modulatedCutoff));
      const filterFreq = 2 * Math.sin(Math.PI * clampedCutoff / sampleRate);

      // Allow self-oscillation with safety limits
      const baseQ = 1 / Math.max(0.05, 2 - filterResonance * 1.95);
      const filterQ = Math.max(0.5, Math.min(20, baseQ + filterFeedback * 5));

      // Drive the filter input with limiting
      const filterInput = (osc1Output * 0.7 + osc2Output * 0.3) * Math.min(3, filterDrive);
      const drivenInput = Math.tanh(filterInput);

      // First filter stage with stability checks
      const filter1Input = drivenInput - filter1LP - filterQ * filter1BP + filterSelfOsc * filterFeedback * 0.5;
      filter1HP = Math.max(-5, Math.min(5, filter1Input));
      filter1BP += filterFreq * filter1HP;
      filter1LP += filterFreq * filter1BP;

      // Prevent filter state explosion
      filter1BP = Math.max(-2, Math.min(2, filter1BP));
      filter1LP = Math.max(-2, Math.min(2, filter1LP));

      // Check for NaN and reset if necessary
      if (!isFinite(filter1LP) || !isFinite(filter1BP) || !isFinite(filter1HP)) {
        filter1LP = 0;
        filter1BP = 0;
        filter1HP = 0;
      }

      // Second filter stage (in series for 24dB/oct)
      const filter1Out = filter1LP * (1 - filterMode) +
                        filter1BP * (filterMode * 2 * (filterMode < 0.5 ? 1 : 0)) +
                        filter1HP * (filterMode > 0.5 ? (filterMode - 0.5) * 2 : 0);

      const filter2Input = filter1Out - filter2LP - filterQ * 0.7 * filter2BP;
      filter2HP = Math.max(-5, Math.min(5, filter2Input));
      filter2BP += filterFreq * filter2HP;
      filter2LP += filterFreq * filter2BP;

      // Prevent filter state explosion
      filter2BP = Math.max(-2, Math.min(2, filter2BP));
      filter2LP = Math.max(-2, Math.min(2, filter2LP));

      // Check for NaN and reset if necessary
      if (!isFinite(filter2LP) || !isFinite(filter2BP) || !isFinite(filter2HP)) {
        filter2LP = 0;
        filter2BP = 0;
        filter2HP = 0;
      }

      // Mix filter outputs with morphing
      let filterOut = filter2LP * (1 - filterMode) +
                     filter2BP * (filterMode * 2 * (filterMode < 0.5 ? 1 : 0)) +
                     filter2HP * (filterMode > 0.5 ? (filterMode - 0.5) * 2 : 0);

      // Track filter self-oscillation with limiting
      filterSelfOsc = Math.tanh(filter2BP * 0.1);

      // Wavefolding stage
      let foldedSignal = filterOut;
      if (wavefoldAmount > 0) {
        const foldGain = 1 + wavefoldAmount * (4 + envelope * envelopeToFold * 2);
        foldedSignal = this.wavefold(filterOut * foldGain, 2 + wavefoldAmount * 3) / foldGain;

        // Integrate for smoother folding
        wavefoldIntegrator += (foldedSignal - wavefoldIntegrator) * 0.1;
        foldedSignal = foldedSignal * (1 - wavefoldAmount * 0.3) + wavefoldIntegrator * wavefoldAmount * 0.3;
      }

      // Distortion stage
      let output = foldedSignal;
      if (distortionType < 0.33) {
        // Soft saturation
        output = Math.tanh(output * (1 + distortionType * 3));
      } else if (distortionType < 0.67) {
        // Wavefolder
        const foldAmount = (distortionType - 0.33) * 3;
        output = this.wavefold(output * (1 + foldAmount * 2), 3);
      } else {
        // Digital decimation
        const bitDepth = Math.floor(16 - (distortionType - 0.67) * 3 * 12);
        const scale = Math.pow(2, bitDepth);
        output = Math.floor(output * scale) / scale;
      }

      // Apply envelope and gain
      output *= envelope * outputGain;

      // Final soft limiting
      output = Math.tanh(output * 0.9) * 0.95;

      // DC blocking for left channel
      let dcBlockerYL = output - dcBlockerX1L + dcBlockerAlpha * dcBlockerY1L;
      dcBlockerX1L = output;
      dcBlockerY1L = dcBlockerYL;

      // Stereo processing
      const monoSignal = dcBlockerYL;

      // Create stereo width with micro-delay and phase differences
      delayBuffer[delayIndex] = monoSignal + filter1BP * 0.05;
      const delayedSignal = delayBuffer[(delayIndex + delayLength - Math.floor(stereoWidth * delayLength)) % delayLength];
      delayIndex = (delayIndex + 1) % delayLength;

      // Add evolving stereo movement
      const stereoPan = Math.sin(i / sampleRate * 0.7 + runglerEnvelope * 3) * stereoWidth * 0.3;

      left[i] = monoSignal * (1 - stereoPan * 0.5) + delayedSignal * stereoWidth * 0.2;

      // Right channel with different filtering for width
      const rightSignal = monoSignal * 0.7 + filter2BP * 0.15 + filterSelfOsc * 0.15;

      // DC blocking for right channel
      let dcBlockerYR = rightSignal - dcBlockerX1R + dcBlockerAlpha * dcBlockerY1R;
      dcBlockerX1R = rightSignal;
      dcBlockerY1R = dcBlockerYR;

      right[i] = dcBlockerYR * (1 + stereoPan * 0.5) + delayedSignal * stereoWidth * 0.3;

      // Store states for next iteration
      osc1LastOutput = osc1Output;
      osc2LastOutput = osc2Output;
    }

    // Normalize the output to use full dynamic range
    let peak = 0;
    for (let i = 0; i < numSamples; i++) {
      peak = Math.max(peak, Math.abs(left[i]), Math.abs(right[i]));
    }

    if (peak > 0.001) {
      const normalizationGain = 0.95 / peak;
      for (let i = 0; i < numSamples; i++) {
        left[i] *= normalizationGain;
        right[i] *= normalizationGain;
      }
    }

    return [left, right];
  }

  // Oscillator generation methods
  private polyBlepSaw(phase: number, phaseInc: number): number {
    let value = 2 * phase - 1;
    value -= this.polyBlep(phase, phaseInc);
    return value;
  }

  private polyBlepPulse(phase: number, phaseInc: number, pulseWidth: number): number {
    let value = phase < pulseWidth ? 1 : -1;
    value += this.polyBlep(phase, phaseInc);
    value -= this.polyBlep((phase - pulseWidth + 1) % 1, phaseInc);
    return value;
  }

  private polyBlepTriangle(phase: number, phaseInc: number): number {
    let value = phase < 0.5 ? phase * 4 - 1 : 3 - phase * 4;
    const polyBlepCorrection = this.polyBlepIntegral(phase, phaseInc) -
                              this.polyBlepIntegral((phase + 0.5) % 1, phaseInc);
    value += polyBlepCorrection * 4;
    return value;
  }

  private polyBlep(t: number, dt: number): number {
    if (t < dt) {
      t /= dt;
      return t + t - t * t - 1;
    } else if (t > 1 - dt) {
      t = (t - 1) / dt;
      return t * t + t + t + 1;
    }
    return 0;
  }

  private polyBlepIntegral(t: number, dt: number): number {
    if (t < dt) {
      t /= dt;
      return (t * t * t) / 3 - t * t / 2 - t / 2;
    } else if (t > 1 - dt) {
      t = (t - 1) / dt;
      return t * t * t / 3 + t * t / 2 + t / 2;
    }
    return 0;
  }

  // Wavefolding function - safe implementation
  private wavefold(input: number, folds: number): number {
    // Limit input to prevent numerical issues
    const clampedInput = Math.max(-10, Math.min(10, input));
    const threshold = 1 / Math.max(1, folds);

    let folded = clampedInput;
    // Maximum iterations to prevent infinite loop
    const maxIterations = 10;
    let iterations = 0;

    while (Math.abs(folded) > threshold && iterations < maxIterations) {
      if (folded > threshold) {
        folded = threshold * 2 - folded;
      } else if (folded < -threshold) {
        folded = -threshold * 2 - folded;
      }
      iterations++;
    }

    // Final clamp to ensure output is bounded
    return Math.max(-1, Math.min(1, folded * folds));
  }

  // Configuration-based parameter generation
  private generatePresetParams(preset: number): Partial<BenjolinParams> {
    switch (preset) {
      case 0:
        return {
          osc1Freq: 50 + Math.random() * 150,
          osc2Freq: 80 + Math.random() * 200,
          crossMod1to2: 0.3 + Math.random() * 0.3,
          crossMod2to1: 0.3 + Math.random() * 0.3,
          runglerChaos: 0.6 + Math.random() * 0.3,
          filterResonance: 0.7 + Math.random() * 0.25,
          filterMode: Math.random() * 0.3,
          evolutionRate: 0.1 + Math.random() * 0.2,
          lfo1Rate: 0.05 + Math.random() * 0.1
        };

      case 1:
        return {
          osc1Freq: 200 + Math.random() * 800,
          osc2Freq: 300 + Math.random() * 1200,
          crossMod1to2: 0.6 + Math.random() * 0.4,
          crossMod2to1: 0.6 + Math.random() * 0.4,
          runglerToFilter: 0.5 + Math.random() * 0.5,
          filterResonance: 0.8 + Math.random() * 0.15,
          filterFeedback: 0.3 + Math.random() * 0.4,
          filterDrive: 2 + Math.random() * 2,
          wavefoldAmount: 0.3 + Math.random() * 0.4
        };

      case 2: {
        const baseFreq = 50 + Math.random() * 100;
        return {
          osc1Freq: baseFreq,
          osc2Freq: baseFreq * (Math.floor(Math.random() * 4) + 1.5),
          runglerBits: Math.random() > 0.5 ? 8 : 4,
          runglerChaos: 0.7 + Math.random() * 0.3,
          lfo2Rate: 0.2 + Math.random() * 0.5,
          lfo2Wave: 0.8 + Math.random() * 0.2,
          envelopeAttack: 0.001 + Math.random() * 0.01,
          envelopeDecay: 0.01 + Math.random() * 0.05
        };
      }

      case 3:
        return {
          osc1Freq: 30 + Math.random() * 100,
          osc2Freq: 40 + Math.random() * 120,
          crossMod1to2: 0.1 + Math.random() * 0.2,
          crossMod2to1: 0.1 + Math.random() * 0.2,
          evolutionRate: 0.01 + Math.random() * 0.05,
          evolutionDepth: 0.3 + Math.random() * 0.4,
          filterMode: Math.random() * 0.5,
          envelopeAttack: 0.3 + Math.random() * 0.4,
          envelopeRelease: 0.4 + Math.random() * 0.4,
          stereoWidth: 0.6 + Math.random() * 0.4
        };

      case 4: {
        const metalFreq = 100 + Math.random() * 500;
        return {
          osc1Freq: metalFreq,
          osc2Freq: metalFreq * (1.41 + Math.random() * 0.2),
          osc1Wave: 0.7 + Math.random() * 0.3,
          filterResonance: 0.85 + Math.random() * 0.1,
          filterDrive: 1.5 + Math.random(),
          distortionType: 0.5 + Math.random() * 0.5,
          wavefoldAmount: 0.2 + Math.random() * 0.3
        };
      }

      case 5:
        return {
          osc1Freq: 60 + Math.random() * 200,
          osc2Freq: 90 + Math.random() * 300,
          evolutionRate: 0.05 + Math.random() * 0.15,
          chaosAttractorRate: 0.02 + Math.random() * 0.08,
          chaosAttractorDepth: 0.2 + Math.random() * 0.3,
          lfo1Depth: 0.1 + Math.random() * 0.2,
          filterMode: 0.2 + Math.random() * 0.3,
          envelopeToFilter: 0.3 + Math.random() * 0.4
        };

      case 6:
        return {
          runglerBits: Math.floor(4 + Math.random() * 12),
          runglerFeedback: 0.5 + Math.random() * 0.5,
          runglerChaos: 0.8 + Math.random() * 0.2,
          distortionType: 0.7 + Math.random() * 0.3,
          lfo2Wave: 0.7 + Math.random() * 0.3,
          filterDrive: 3 + Math.random() * 2,
          wavefoldAmount: 0.4 + Math.random() * 0.4
        };

      case 7:
        return {
          osc1Freq: 40 + Math.random() * 150,
          osc2Freq: 60 + Math.random() * 200,
          crossMod1to2: 0.05 + Math.random() * 0.15,
          crossMod2to1: 0.05 + Math.random() * 0.15,
          evolutionRate: 0.02 + Math.random() * 0.08,
          filterMode: Math.random() * 0.4,
          stereoWidth: 0.7 + Math.random() * 0.3,
          envelopeAttack: 0.2 + Math.random() * 0.3,
          outputGain: 0.3 + Math.random() * 0.3
        };

      case 8:
        return {
          osc1Freq: 20 + Math.random() * 40,
          osc2Freq: 25 + Math.random() * 50,
          crossMod1to2: 0.4 + Math.random() * 0.3,
          crossMod2to1: 0.2 + Math.random() * 0.2,
          filterCutoff: 80 + Math.random() * 200,
          filterResonance: 0.6 + Math.random() * 0.3,
          filterDrive: 2 + Math.random() * 2,
          envelopeAttack: 0.001,
          envelopeDecay: 0.05 + Math.random() * 0.1,
          outputGain: 0.6 + Math.random() * 0.3
        };

      case 9: {
        const bellFreq = 200 + Math.random() * 600;
        return {
          osc1Freq: bellFreq,
          osc2Freq: bellFreq * (2.71 + Math.random() * 0.3),
          osc1Wave: 0.9,
          osc2Wave: 0.1,
          filterResonance: 0.9 + Math.random() * 0.05,
          filterMode: 0.7 + Math.random() * 0.3,
          envelopeAttack: 0.001,
          envelopeDecay: 0.02 + Math.random() * 0.03,
          envelopeSustain: 0,
          envelopeRelease: 0.3 + Math.random() * 0.4,
          stereoWidth: 0.8 + Math.random() * 0.2
        };
      }

      case 10:
        return {
          osc1Freq: 500 + Math.random() * 1500,
          osc2Freq: 800 + Math.random() * 2000,
          crossMod1to2: 0.8 + Math.random() * 0.2,
          crossMod2to1: 0.8 + Math.random() * 0.2,
          runglerChaos: 0.9 + Math.random() * 0.1,
          runglerBits: 4,
          filterDrive: 3 + Math.random() * 2,
          distortionType: 0.8 + Math.random() * 0.2,
          envelopeAttack: 0.001,
          envelopeDecay: 0.01 + Math.random() * 0.02
        };

      case 11: {
        const carrier = 100 + Math.random() * 300;
        return {
          osc1Freq: carrier,
          osc2Freq: carrier * (Math.floor(Math.random() * 5) + 1),
          crossMod1to2: 0.7 + Math.random() * 0.3,
          crossMod2to1: 0.1 + Math.random() * 0.2,
          osc1Wave: 0,
          osc2Wave: 0,
          filterMode: 0.1 + Math.random() * 0.2,
          lfo1Target: 0,
          lfo1Depth: 0.3 + Math.random() * 0.3,
          lfo1Rate: 0.1 + Math.random() * 0.3
        };
      }

      case 12:
        return {
          osc1Freq: 150 + Math.random() * 350,
          osc2Freq: 200 + Math.random() * 400,
          runglerBits: 16,
          runglerToOsc1: 0.4 + Math.random() * 0.3,
          runglerToOsc2: 0.4 + Math.random() * 0.3,
          evolutionRate: 0.2 + Math.random() * 0.3,
          chaosAttractorRate: 0.1 + Math.random() * 0.2,
          envelopeAttack: 0.01 + Math.random() * 0.03,
          envelopeDecay: 0.02 + Math.random() * 0.05,
          wavefoldAmount: 0.1 + Math.random() * 0.2
        };

      case 13:
        return {
          osc1Freq: 80 + Math.random() * 200,
          osc2Freq: 120 + Math.random() * 300,
          filterCutoff: 200 + Math.random() * 800,
          filterResonance: 0.85 + Math.random() * 0.1,
          filterFeedback: 0.4 + Math.random() * 0.3,
          lfo1Target: 0.5,
          lfo1Rate: 0.3 + Math.random() * 0.4,
          lfo1Depth: 0.5 + Math.random() * 0.3,
          envelopeToFilter: 0.6 + Math.random() * 0.3
        };

      case 14:
        return {
          osc1Freq: 60 + Math.random() * 150,
          osc2Freq: 100 + Math.random() * 250,
          osc2Wave: 0.8 + Math.random() * 0.2,
          filterDrive: 2.5 + Math.random() * 1.5,
          filterMode: 0.6 + Math.random() * 0.4,
          distortionType: 0.4 + Math.random() * 0.3,
          envelopeAttack: 0.001,
          envelopeDecay: 0.01 + Math.random() * 0.03,
          envelopeSustain: 0.1 + Math.random() * 0.2,
          envelopeRelease: 0.05 + Math.random() * 0.1
        };

      case 15: {
        const aliasFreq = 5000 + Math.random() * 7000;
        return {
          osc1Freq: aliasFreq,
          osc2Freq: aliasFreq * (1.01 + Math.random() * 0.02),
          osc1Wave: 1,
          osc2Wave: 0.5,
          filterCutoff: 8000 + Math.random() * 4000,
          filterMode: 0.8 + Math.random() * 0.2,
          distortionType: 0.85 + Math.random() * 0.15,
          outputGain: 0.3 + Math.random() * 0.2
        };
      }

      case 16:
        return {
          osc1Freq: 100 + Math.random() * 200,
          osc2Freq: 150 + Math.random() * 250,
          crossMod1to2: 0.2 + Math.random() * 0.2,
          crossMod2to1: 0.2 + Math.random() * 0.2,
          lfo1Rate: 0.01 + Math.random() * 0.03,
          lfo2Rate: 0.02 + Math.random() * 0.04,
          lfo1Depth: 0.3 + Math.random() * 0.4,
          lfo2Depth: 0.3 + Math.random() * 0.4,
          evolutionRate: 0.005 + Math.random() * 0.02,
          evolutionDepth: 0.4 + Math.random() * 0.4
        };

      case 17:
        return {
          osc1Freq: 100 + Math.random() * 400,
          osc2Freq: 150 + Math.random() * 600,
          crossMod1to2: 0.7 + Math.random() * 0.3,
          crossMod2to1: 0.7 + Math.random() * 0.3,
          runglerFeedback: 0.8 + Math.random() * 0.2,
          filterFeedback: 0.6 + Math.random() * 0.3,
          filterResonance: 0.9 + Math.random() * 0.05,
          filterDrive: 3 + Math.random() * 2,
          wavefoldAmount: 0.5 + Math.random() * 0.4,
          distortionType: 0.3 + Math.random() * 0.4
        };

      default:
        // Fallback to fully random
        return {};
    }
  }

  randomParams(pitchLock?: PitchLock): BenjolinParams {
    // Choose a random preset configuration
    const preset = Math.floor(Math.random() * PRESET_COUNT);

    // Get preset-specific parameters
    const presetParams = this.generatePresetParams(preset);

    // Generate full parameter set with preset biases
    const params: BenjolinParams = {
      // Core oscillators
      osc1Freq: pitchLock?.enabled
        ? pitchLock.frequency
        : presetParams.osc1Freq ?? (20 + Math.random() * 800),
      osc2Freq: presetParams.osc2Freq ?? (30 + Math.random() * 1200),
      osc1Wave: presetParams.osc1Wave ?? Math.random(),
      osc2Wave: presetParams.osc2Wave ?? Math.random(),

      // Cross modulation
      crossMod1to2: presetParams.crossMod1to2 ?? Math.random() * 0.8,
      crossMod2to1: presetParams.crossMod2to1 ?? Math.random() * 0.8,
      crossMod1toFilter: presetParams.crossMod1toFilter ?? Math.random() * 0.5,
      crossMod2toRungler: presetParams.crossMod2toRungler ?? Math.random() * 0.4,

      // Rungler
      runglerToOsc1: presetParams.runglerToOsc1 ?? Math.random() * 0.6,
      runglerToOsc2: presetParams.runglerToOsc2 ?? Math.random() * 0.6,
      runglerToFilter: presetParams.runglerToFilter ?? Math.random() * 0.8,
      runglerBits: presetParams.runglerBits ?? (Math.random() > 0.7 ? 16 : Math.random() > 0.4 ? 12 : 8),
      runglerFeedback: presetParams.runglerFeedback ?? Math.random() * 0.7,
      runglerChaos: presetParams.runglerChaos ?? (0.3 + Math.random() * 0.7),

      // Filter
      filterCutoff: presetParams.filterCutoff ?? (100 + Math.random() * 3000),
      filterResonance: presetParams.filterResonance ?? (0.3 + Math.random() * 0.65),
      filterMode: presetParams.filterMode ?? Math.random(),
      filterDrive: presetParams.filterDrive ?? (0.5 + Math.random() * 2.5),
      filterFeedback: presetParams.filterFeedback ?? Math.random() * 0.5,

      // Evolution
      evolutionRate: presetParams.evolutionRate ?? Math.random() * 0.3,
      evolutionDepth: presetParams.evolutionDepth ?? Math.random() * 0.5,
      chaosAttractorRate: presetParams.chaosAttractorRate ?? Math.random() * 0.2,
      chaosAttractorDepth: presetParams.chaosAttractorDepth ?? Math.random() * 0.5,

      // LFOs
      lfo1Rate: presetParams.lfo1Rate ?? Math.random() * 0.5,
      lfo1Depth: presetParams.lfo1Depth ?? Math.random() * 0.4,
      lfo1Target: presetParams.lfo1Target ?? Math.random(),
      lfo2Rate: presetParams.lfo2Rate ?? Math.random() * 0.8,
      lfo2Depth: presetParams.lfo2Depth ?? Math.random() * 0.3,
      lfo2Wave: presetParams.lfo2Wave ?? Math.random(),

      // Output shaping
      wavefoldAmount: presetParams.wavefoldAmount ?? Math.random() * 0.6,
      distortionType: presetParams.distortionType ?? Math.random(),
      stereoWidth: presetParams.stereoWidth ?? (0.2 + Math.random() * 0.8),
      outputGain: presetParams.outputGain ?? (0.2 + Math.random() * 0.5),

      // Envelope
      envelopeAttack: presetParams.envelopeAttack ?? (0.001 + Math.random() * 0.2),
      envelopeDecay: presetParams.envelopeDecay ?? (0.01 + Math.random() * 0.3),
      envelopeSustain: presetParams.envelopeSustain ?? Math.random(),
      envelopeRelease: presetParams.envelopeRelease ?? (0.05 + Math.random() * 0.5),
      envelopeToFilter: presetParams.envelopeToFilter ?? Math.random() * 0.6,
      envelopeToFold: presetParams.envelopeToFold ?? Math.random() * 0.4
    };

    return params;
  }

  mutateParams(params: BenjolinParams, mutationAmount?: number, pitchLock?: PitchLock): BenjolinParams {
    const mutated = { ...params };

    // Determine mutation strength based on current "stability"
    const stability = (params.crossMod1to2 + params.crossMod2to1) / 2 +
                     params.runglerChaos + params.evolutionDepth;
    const mutAmount = mutationAmount ?? (stability > 1.5 ? 0.05 : stability < 0.5 ? 0.2 : 0.1);

    // Helper for correlated mutations
    const mutateValue = (value: number, min: number, max: number, correlation = 1): number => {
      const delta = (Math.random() - 0.5) * mutAmount * (max - min) * correlation;
      return Math.max(min, Math.min(max, value + delta));
    };

    // Decide mutation strategy
    const strategy = Math.random();

    if (strategy < 0.3) {
      // Mutate frequency relationships
      if (pitchLock?.enabled) {
        mutated.osc1Freq = pitchLock.frequency;
        const freqRatio = mutated.osc2Freq / params.osc1Freq;
        mutated.osc2Freq = mutated.osc1Freq * mutateValue(freqRatio, 0.5, 4);
      } else {
        const freqRatio = mutated.osc2Freq / mutated.osc1Freq;
        mutated.osc1Freq = mutateValue(mutated.osc1Freq, 20, 2000);
        mutated.osc2Freq = mutated.osc1Freq * mutateValue(freqRatio, 0.5, 4);
      }

      // Correlate cross-mod amounts
      const crossModDelta = (Math.random() - 0.5) * mutAmount;
      mutated.crossMod1to2 = Math.max(0, Math.min(1, mutated.crossMod1to2 + crossModDelta));
      mutated.crossMod2to1 = Math.max(0, Math.min(1, mutated.crossMod2to1 + crossModDelta * 0.7));

    } else if (strategy < 0.6) {
      // Mutate filter characteristics
      mutated.filterCutoff = mutateValue(mutated.filterCutoff, 100, 4000);
      mutated.filterResonance = mutateValue(mutated.filterResonance, 0, 0.95);
      mutated.filterMode = mutateValue(mutated.filterMode, 0, 1);

      // Correlate filter drive and feedback
      if (Math.random() > 0.5) {
        mutated.filterDrive = mutateValue(mutated.filterDrive, 0.5, 4);
        mutated.filterFeedback = mutateValue(mutated.filterFeedback, 0, 0.7, 0.5);
      }

    } else if (strategy < 0.8) {
      // Mutate evolution and chaos
      mutated.evolutionRate = mutateValue(mutated.evolutionRate, 0, 0.5);
      mutated.evolutionDepth = mutateValue(mutated.evolutionDepth, 0, 0.8);
      mutated.chaosAttractorRate = mutateValue(mutated.chaosAttractorRate, 0, 0.3);
      mutated.chaosAttractorDepth = mutateValue(mutated.chaosAttractorDepth, 0, 0.8);

      // Maybe change rungler behavior
      if (Math.random() > 0.7) {
        mutated.runglerChaos = mutateValue(mutated.runglerChaos, 0, 1);
        mutated.runglerFeedback = mutateValue(mutated.runglerFeedback, 0, 0.9);
      }

    } else {
      // Mutate output characteristics
      mutated.wavefoldAmount = mutateValue(mutated.wavefoldAmount, 0, 0.8);
      mutated.distortionType = mutateValue(mutated.distortionType, 0, 1);
      mutated.stereoWidth = mutateValue(mutated.stereoWidth, 0, 1);

      // Adjust envelope for new sound
      const envMutation = Math.random();
      if (envMutation < 0.5) {
        mutated.envelopeAttack = mutateValue(mutated.envelopeAttack, 0.001, 0.5);
        mutated.envelopeDecay = mutateValue(mutated.envelopeDecay, 0.01, 0.5);
      } else {
        mutated.envelopeSustain = mutateValue(mutated.envelopeSustain, 0, 1);
        mutated.envelopeRelease = mutateValue(mutated.envelopeRelease, 0.01, 0.8);
      }
    }

    // Always apply small random mutations to 2-3 other parameters
    const paramNames = Object.keys(params) as (keyof BenjolinParams)[];
    const numExtraMutations = 2 + Math.floor(Math.random() * 2);

    for (let i = 0; i < numExtraMutations; i++) {
      const paramName = paramNames[Math.floor(Math.random() * paramNames.length)];
      const currentValue = mutated[paramName];

      if (typeof currentValue === 'number') {
        // Apply subtle mutation based on parameter type
        if (paramName.includes('Freq')) {
          mutated[paramName] = mutateValue(currentValue, 20, 2000, 0.5) as any;
        } else if (paramName.includes('envelope')) {
          mutated[paramName] = mutateValue(currentValue, 0, 1, 0.3) as any;
        } else {
          mutated[paramName] = mutateValue(currentValue, 0, 1, 0.2) as any;
        }
      }
    }

    return mutated;
  }
}