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

interface NoiseDrumParams {
  // Noise characteristics
  noiseColor: number;
  noiseBurst: number;
  burstCount: number;

  // Filter section
  filterFreq: number;
  filterQ: number;
  filterType: number;
  filterEnvAmount: number;
  filterEnvSpeed: number;

  // Amplitude envelope
  ampAttack: number;
  ampDecay: number;
  ampPunch: number;

  // Pitch section (for tonal components)
  pitchAmount: number;
  pitchStart: number;
  pitchDecay: number;

  // Body resonance (adds tonal character)
  bodyFreq: number;
  bodyDecay: number;
  bodyAmount: number;

  // Noise modulation
  noiseMod: number;
  noiseModRate: number;

  // Stereo and character
  stereoSpread: number;
  drive: number;
}

export class NoiseDrum implements SynthEngine {
  getName(): string {
    return 'Noise Perc';
  }

  getDescription(): string {
    return 'Versatile noise-based percussion synthesizer inspired by classic drum machines';
  }

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

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

  randomParams(): NoiseDrumParams {
    // Intelligent parameter generation based on correlated characteristics

    // Choose a frequency range that defines the drum character
    const freqBias = Math.random();
    let filterFreq: number;
    let noiseColor: number;
    let pitchAmount: number;
    let bodyAmount: number;

    if (freqBias < 0.25) {
      // Low frequency - kick/bass territory
      filterFreq = 0.05 + Math.random() * 0.12;
      noiseColor = Math.random() * 0.3;
      pitchAmount = 0.15 + Math.random() * 0.2;
      bodyAmount = 0.25 + Math.random() * 0.35;
    } else if (freqBias < 0.5) {
      // Mid frequency - tom/snare territory
      filterFreq = 0.2 + Math.random() * 0.35;
      noiseColor = 0.3 + Math.random() * 0.4;
      pitchAmount = 0.05 + Math.random() * 0.2;
      bodyAmount = 0.15 + Math.random() * 0.3;
    } else if (freqBias < 0.75) {
      // High frequency - hi-hat/ride territory
      filterFreq = 0.6 + Math.random() * 0.3;
      noiseColor = 0.65 + Math.random() * 0.35;
      pitchAmount = Math.random() * 0.08;
      bodyAmount = 0.1 + Math.random() * 0.2;
    } else {
      // Very high - cymbal/crash territory
      filterFreq = 0.75 + Math.random() * 0.2;
      noiseColor = 0.8 + Math.random() * 0.2;
      pitchAmount = 0;
      bodyAmount = 0.15 + Math.random() * 0.25;
    }

    // Q correlates with frequency - higher freq = higher Q acceptable
    const filterQ = 0.3 + Math.random() * 0.4 + filterFreq * 0.3;

    // Filter type based on frequency range
    const filterType = filterFreq < 0.3 ?
      Math.random() * 0.35 : // Low freq prefers lowpass
      filterFreq > 0.7 ?
        0.5 + Math.random() * 0.5 : // High freq prefers highpass/bandpass
        Math.random(); // Mid freq - any type

    // Decay time inversely correlates with frequency
    const decayBias = Math.random();
    const ampDecay = filterFreq < 0.3 ?
      0.25 + decayBias * 0.4 : // Low freq can be longer
      filterFreq > 0.6 ?
        0.08 + decayBias * 0.35 : // High freq shorter
        0.2 + decayBias * 0.45; // Mid range

    // Attack is generally very short for percussion
    const ampAttack = Math.random() < 0.85 ?
      Math.random() * 0.008 : // Most drums: instant attack
      Math.random() * 0.02; // Some: slight attack

    // Punch correlates with attack - shorter attack = more punch available
    const ampPunch = ampAttack < 0.01 ?
      Math.random() * 0.7 :
      Math.random() * 0.4;

    // Filter envelope amount and speed
    const filterEnvAmount = 0.1 + Math.random() * 0.4;
    const filterEnvSpeed = 0.08 + Math.random() * 0.35;

    // Burst pattern (for claps/complex sounds)
    const useBurst = Math.random() < 0.15; // 15% chance of burst
    const noiseBurst = useBurst ? 0.5 + Math.random() * 0.5 : 0;
    const burstCount = useBurst ? 0.3 + Math.random() * 0.7 : 0;

    // Pitch characteristics
    const pitchStart = pitchAmount > 0.1 ?
      0.2 + Math.random() * 0.6 : // If pitched, varied start freq
      0.5; // If not pitched, doesn't matter

    const pitchDecay = pitchAmount > 0.1 ?
      0.03 + Math.random() * 0.12 :
      0.05;

    // Body resonance frequency should relate to filter frequency
    const bodyFreq = filterFreq * (0.8 + Math.random() * 0.4);
    const bodyDecay = 0.2 + Math.random() * 0.4;

    // Noise modulation more common on mid-high frequencies
    const useNoiseMod = filterFreq > 0.4 && Math.random() < 0.5;
    const noiseMod = useNoiseMod ? 0.15 + Math.random() * 0.4 : Math.random() * 0.15;
    const noiseModRate = useNoiseMod ? 0.3 + Math.random() * 0.7 : Math.random();

    // Stereo spread - more on high frequencies
    const stereoSpread = filterFreq > 0.5 ?
      0.15 + Math.random() * 0.3 :
      Math.random() * 0.2;

    // Drive - more on low frequencies for punch
    const drive = filterFreq < 0.3 ?
      0.2 + Math.random() * 0.35 :
      Math.random() * 0.3;

    return {
      noiseColor,
      noiseBurst,
      burstCount,
      filterFreq: Math.max(0, Math.min(1, filterFreq)),
      filterQ: Math.max(0, Math.min(1, filterQ)),
      filterType: Math.max(0, Math.min(1, filterType)),
      filterEnvAmount,
      filterEnvSpeed,
      ampAttack,
      ampDecay,
      ampPunch,
      pitchAmount: Math.max(0, Math.min(0.4, pitchAmount)),
      pitchStart,
      pitchDecay,
      bodyFreq: Math.max(0, Math.min(1, bodyFreq)),
      bodyDecay,
      bodyAmount: Math.max(0, Math.min(0.6, bodyAmount)),
      noiseMod,
      noiseModRate,
      stereoSpread,
      drive
    };
  }

  mutateParams(params: NoiseDrumParams): NoiseDrumParams {
    const mutate = (value: number, amount: number = 0.15): number => {
      return Math.max(0, Math.min(1, value + (Math.random() - 0.5) * amount));
    };

    return {
      noiseColor: mutate(params.noiseColor, 0.25),
      noiseBurst: mutate(params.noiseBurst, 0.2),
      burstCount: mutate(params.burstCount, 0.3),

      filterFreq: mutate(params.filterFreq, 0.2),
      filterQ: mutate(params.filterQ, 0.2),
      filterType: mutate(params.filterType, 0.25),
      filterEnvAmount: mutate(params.filterEnvAmount, 0.2),
      filterEnvSpeed: mutate(params.filterEnvSpeed, 0.2),

      ampAttack: mutate(params.ampAttack, 0.12),
      ampDecay: mutate(params.ampDecay, 0.2),
      ampPunch: mutate(params.ampPunch, 0.2),

      pitchAmount: mutate(params.pitchAmount, 0.2),
      pitchStart: mutate(params.pitchStart, 0.25),
      pitchDecay: mutate(params.pitchDecay, 0.2),

      bodyFreq: mutate(params.bodyFreq, 0.25),
      bodyDecay: mutate(params.bodyDecay, 0.2),
      bodyAmount: mutate(params.bodyAmount, 0.2),

      noiseMod: mutate(params.noiseMod, 0.2),
      noiseModRate: mutate(params.noiseModRate, 0.25),

      stereoSpread: mutate(params.stereoSpread, 0.15),
      drive: mutate(params.drive, 0.15)
    };
  }

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

    const attackSamples = Math.floor(params.ampAttack * duration * sampleRate);
    const decaySamples = Math.floor(params.ampDecay * duration * sampleRate);

    // Filter frequency range: 40Hz to 10kHz (reduced from 12kHz for less harshness)
    const baseFilterFreq = 40 + params.filterFreq * 9960;

    // Q range: 0.5 to 6 (reduced further for stability)
    const filterQ = 0.5 + params.filterQ * 5.5;

    // Pitch envelope: SWEEP DOWN from high to low (classic 808/909 style)
    const pitchStartFreq = 60 + params.pitchStart * 540; // 60Hz to 600Hz (not 800Hz)
    const pitchDecayTime = 0.01 + params.pitchDecay * 0.12;

    // Body resonance frequency (60Hz to 500Hz) - reduced upper range
    const bodyFreq = 60 + params.bodyFreq * 440;
    const bodyDecayTime = 0.08 + params.bodyDecay * 0.4;

    // Noise burst parameters - FIXED: burst envelope shouldn't kill the whole sound
    const shouldBurst = params.noiseBurst > 0.3;
    const numBursts = shouldBurst ? Math.floor(1 + params.burstCount * 3) : 1;
    const burstIntensity = shouldBurst ? 0.5 + params.noiseBurst * 0.5 : 0; // how much burst affects sound

    // Noise modulation rate (3Hz to 50Hz) - increased minimum
    const noiseModFreq = 3 + params.noiseModRate * 47;

    for (let channel = 0; channel < 2; channel++) {
      const output = channel === 0 ? left : right;

      // Stereo spread affects multiple parameters
      const spreadFactor = channel === 0 ? 1 - params.stereoSpread * 0.15 : 1 + params.stereoSpread * 0.15;
      const spreadPhase = channel === 0 ? 0 : params.stereoSpread * 0.08;

      // Noise generator state
      const pinkState = new Float32Array(7);
      let brownState = 0;

      // Filter state (main filter)
      let filterState1 = 0;
      let filterState2 = 0;

      // Body resonance filter state
      let bodyState1 = 0;
      let bodyState2 = 0;

      // DC blocker state
      let dcBlockerX = 0;
      let dcBlockerY = 0;

      // Pitch oscillator phase
      let pitchPhase = Math.random() * Math.PI * 2; // random start phase

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

        // Burst envelope for clap/layered sounds - FIXED: doesn't kill the sound
        let burstEnv = 1.0;
        if (shouldBurst && numBursts > 1 && burstIntensity > 0) {
          let burstSum = 0;
          const burstDuration = 0.04; // fixed short duration per burst
          const burstSpan = Math.min(duration * 0.15, 0.12); // bursts span 15% of duration, max 120ms

          for (let b = 0; b < numBursts; b++) {
            const burstStart = (b / numBursts) * burstSpan + spreadPhase * 0.01;
            const burstTime = t - burstStart;
            if (burstTime >= 0 && burstTime < burstDuration) {
              const burstPhase = burstTime / burstDuration;
              const burstAmp = Math.exp(-burstPhase * 25) * (1 - b * 0.25 / numBursts);
              burstSum += burstAmp;
            }
          }

          // Blend between continuous (1.0) and burst (burstSum)
          burstEnv = (1 - burstIntensity) + burstIntensity * Math.min(burstSum, 1.5);
        }

        // Generate noise
        const whiteNoise = Math.random() * 2 - 1;
        brownState = this.updateBrownState(brownState, whiteNoise);
        const noise = this.selectNoiseColor(params.noiseColor, whiteNoise, pinkState, brownState);

        // Noise modulation for texture variation
        const noiseMod = Math.sin(2 * Math.PI * noiseModFreq * t);
        const noiseModAmount = 1 - params.noiseMod * (noiseMod * 0.5 + 0.5) * 0.6;
        const modulatedNoise = noise * noiseModAmount;

        // Amplitude envelope
        let ampEnv = this.amplitudeEnvelope(i, attackSamples, decaySamples, numSamples);

        // Add punch (transient boost)
        if (i < attackSamples * 3) {
          const punchPhase = i / (attackSamples * 3);
          const punchBoost = Math.exp(-punchPhase * 12) * params.ampPunch * 0.25;
          ampEnv = ampEnv * (1 + punchBoost);
        }

        // Apply burst envelope
        ampEnv *= burstEnv;

        // Exponential pitch envelope - SWEEP DOWN (classic drum pitch behavior)
        const pitchEnv = Math.exp(-phase / pitchDecayTime);
        const currentPitchFreq = pitchStartFreq * (0.3 + pitchEnv * 0.7); // sweeps DOWN from start to 30% of start

        // Generate tonal component (sine oscillator with pitch envelope)
        const pitchIncrement = (2 * Math.PI * currentPitchFreq) / sampleRate;
        pitchPhase += pitchIncrement;
        const tonal = Math.sin(pitchPhase) * params.pitchAmount * ampEnv;

        // Combine noise and tonal - NOISE DOMINANT
        // At max pitchAmount (0.35), noise stays at ~0.88, tonal at 0.35
        // This ensures noise is ALWAYS the primary component
        const combined = modulatedNoise * (1 - params.pitchAmount * 0.35) + tonal;

        // Filter envelope (exponential decay for filter sweep)
        const filterEnv = Math.exp(-phase / (0.02 + params.filterEnvSpeed * 0.25));

        // Modulated filter frequency - REDUCED sweep range
        const freqMod = 1 + params.filterEnvAmount * filterEnv * 2; // reduced from 3x to 2x
        const modulatedFreq = Math.min(
          baseFilterFreq * spreadFactor * freqMod,
          sampleRate * 0.45
        );

        // Apply main filter with type blending
        const filtered = this.applyFilterBlend(
          combined,
          modulatedFreq,
          filterQ,
          params.filterType,
          sampleRate,
          filterState1,
          filterState2
        );

        filterState1 = filtered.state1;
        filterState2 = filtered.state2;

        // Body resonance (adds tonal character like drum shell resonance)
        let sample = filtered.output;
        if (params.bodyAmount > 0.05) {
          const bodyEnv = Math.exp(-phase / bodyDecayTime);
          const bodyFiltered = this.stateVariableFilter(
            sample,
            bodyFreq * spreadFactor,
            4 + params.bodyAmount * 6, // reduced resonance range further for stability
            sampleRate,
            bodyState1,
            bodyState2
          );

          bodyState1 = bodyFiltered.state1;
          bodyState2 = bodyFiltered.state2;

          // Blend body resonance - SUBTLE
          sample = sample * (1 - params.bodyAmount * 0.4) +
            bodyFiltered.output * params.bodyAmount * 0.6 * bodyEnv;
        }

        // Apply amplitude envelope
        sample *= ampEnv;

        // Drive/saturation for more aggressive sounds
        if (params.drive > 0.1) {
          const driveAmount = 1 + params.drive * 0.8; // reduced from 1.0
          sample = this.softClip(sample * driveAmount) / driveAmount;
        }

        // DC blocking filter
        const dcBlocked = this.dcBlocker(sample, dcBlockerX, dcBlockerY);
        dcBlockerX = sample;
        dcBlockerY = dcBlocked.y;
        sample = dcBlocked.output;

        // Final output scaling with soft clipping
        output[i] = this.softClip(sample * 0.85);
      }
    }

    // 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];
  }

  private updateBrownState(brownState: number, whiteNoise: number): number {
    return (brownState + whiteNoise * 0.02) * 0.98;
  }

  private selectNoiseColor(
    colorParam: number,
    whiteNoise: number,
    pinkState: Float32Array,
    brownState: number
  ): number {
    if (colorParam < 0.33) {
      return whiteNoise;
    } else if (colorParam < 0.66) {
      // Pink noise using Paul Kellett's filter
      pinkState[0] = 0.99886 * pinkState[0] + whiteNoise * 0.0555179;
      pinkState[1] = 0.99332 * pinkState[1] + whiteNoise * 0.0750759;
      pinkState[2] = 0.96900 * pinkState[2] + whiteNoise * 0.1538520;
      pinkState[3] = 0.86650 * pinkState[3] + whiteNoise * 0.3104856;
      pinkState[4] = 0.55000 * pinkState[4] + whiteNoise * 0.5329522;
      pinkState[5] = -0.7616 * pinkState[5] - whiteNoise * 0.0168980;

      const pink = pinkState[0] + pinkState[1] + pinkState[2] + pinkState[3] +
        pinkState[4] + pinkState[5] + pinkState[6] + whiteNoise * 0.5362;
      pinkState[6] = whiteNoise * 0.115926;

      return pink * 0.11;
    } else {
      return brownState * 2.5;
    }
  }

  private amplitudeEnvelope(
    sample: number,
    attackSamples: number,
    decaySamples: number,
    totalSamples: number
  ): number {
    if (sample < attackSamples && attackSamples > 0) {
      const attackPhase = sample / attackSamples;
      return attackPhase * attackPhase * (3 - 2 * attackPhase);
    } else {
      const decayStart = attackSamples;
      const decayLength = totalSamples - attackSamples;
      const decayPhase = (sample - decayStart) / decayLength;
      const decayRate = Math.max(decaySamples / totalSamples, 0.05); // increased minimum from 0.001
      return Math.exp(-decayPhase / decayRate);
    }
  }

  private stateVariableFilter(
    input: number,
    cutoff: number,
    resonance: number,
    sampleRate: number,
    state1: number,
    state2: number
  ): { output: number; state1: number; state2: number } {
    const normalizedFreq = Math.min(cutoff / sampleRate, 0.48);
    const f = 2 * Math.sin(Math.PI * normalizedFreq);

    const q = Math.max(1 / Math.min(resonance, 10), 0.02);

    const lowpass = state2 + f * state1;
    const highpass = input - lowpass - q * state1;
    const bandpass = f * highpass + state1;

    // Clamp filter states to prevent explosion
    const newState1 = Math.max(-2, Math.min(2, Math.abs(bandpass) > 1e-10 ? bandpass : 0));
    const newState2 = Math.max(-2, Math.min(2, Math.abs(lowpass) > 1e-10 ? lowpass : 0));

    return {
      output: bandpass,
      state1: newState1,
      state2: newState2
    };
  }

  private applyFilterBlend(
    input: number,
    cutoff: number,
    resonance: number,
    filterType: number,
    sampleRate: number,
    state1: number,
    state2: number
  ): { output: number; state1: number; state2: number } {
    const normalizedFreq = Math.min(cutoff / sampleRate, 0.48);
    const f = 2 * Math.sin(Math.PI * normalizedFreq);

    const q = Math.max(1 / Math.min(resonance, 10), 0.02);

    const lowpass = state2 + f * state1;
    const highpass = input - lowpass - q * state1;
    const bandpass = f * highpass + state1;

    // Clamp filter states to prevent explosion
    const newState1 = Math.max(-2, Math.min(2, Math.abs(bandpass) > 1e-10 ? bandpass : 0));
    const newState2 = Math.max(-2, Math.min(2, Math.abs(lowpass) > 1e-10 ? lowpass : 0));

    // Blend between filter types based on filterType parameter
    let output: number;
    if (filterType < 0.33) {
      // Lowpass
      output = lowpass;
    } else if (filterType < 0.66) {
      // Bandpass
      output = bandpass;
    } else {
      // Highpass
      output = highpass;
    }

    return {
      output,
      state1: newState1,
      state2: newState2
    };
  }

  private dcBlocker(input: number, prevX: number, prevY: number): { output: number; y: number } {
    const y = input - prevX + 0.995 * prevY;
    return { output: y, y };
  }

  private softClip(x: number): number {
    if (x > 1) {
      return 1;
    } else if (x < -1) {
      return -1;
    } else if (x > 0.66) {
      return (3 - (2 - 3 * x) ** 2) / 3;
    } else if (x < -0.66) {
      return -(3 - (2 - 3 * -x) ** 2) / 3;
    } else {
      return x;
    }
  }
}
