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

interface SnareParams {
  // Core frequency (base pitch of the snare)
  baseFreq: number;

  // Tone control (0 = more tonal, 1 = more noise)
  tone: number;

  // Snap amount (pitch modulation intensity)
  snap: number;

  // Decay times
  tonalDecay: number;
  noiseDecay: number;

  // Accent (volume boost)
  accent: number;

  // Tuning offset
  tuning: number;
}

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

  getDescription(): string {
    return 'Classic snare drum with triangle wave and noise';
  }

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

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

  randomParams(pitchLock?: PitchLock): SnareParams {
    return {
      baseFreq: pitchLock ? this.freqToParam(pitchLock.frequency) : 0.3 + Math.random() * 0.4,
      tone: 0.4 + Math.random() * 0.4,
      snap: 0.5 + Math.random() * 0.5,
      tonalDecay: 0.15 + Math.random() * 0.35,
      noiseDecay: 0.4 + Math.random() * 0.4,
      accent: 0.5 + Math.random() * 0.5,
      tuning: pitchLock ? 0.5 : 0.4 + Math.random() * 0.2,
    };
  }

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

    return {
      baseFreq: pitchLock ? params.baseFreq : mutate(params.baseFreq, 0.2),
      tone: mutate(params.tone, 0.25),
      snap: mutate(params.snap, 0.2),
      tonalDecay: mutate(params.tonalDecay, 0.2),
      noiseDecay: mutate(params.noiseDecay, 0.2),
      accent: mutate(params.accent, 0.2),
      tuning: pitchLock ? params.tuning : mutate(params.tuning, 0.15),
    };
  }

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

    // Base frequency: 100Hz to 400Hz
    const baseFreq = pitchLock ? pitchLock.frequency : 100 + params.baseFreq * 300;

    // Tuning offset: -12% to +12%
    const tuningFactor = 0.88 + params.tuning * 0.24;
    const tunedFreq = baseFreq * tuningFactor;

    // Pitch modulation parameters (starts 4x higher, drops over 10ms)
    const pitchMultiplier = 1 + params.snap * 3; // 1x to 4x
    const modDuration = 0.008 + params.snap * 0.007; // 8ms to 15ms

    // Decay times scaled by duration
    const tonalDecayTime = (0.05 + params.tonalDecay * 0.15) * duration; // 50ms to 200ms
    const noiseDecayTime = (0.15 + params.noiseDecay * 0.35) * duration; // 150ms to 500ms

    // Volume and gain staging
    const baseVolume = 0.5;
    const accentGain = baseVolume * (1 + params.accent);

    // Attack time (5ms ramp)
    const attackTime = 0.005;

    // Notch filter parameters for noise (fixed at 1000Hz)
    const notchFreq = 1000;
    const notchQ = 5;

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

      // Triangle oscillator phase
      let phase = Math.random() * Math.PI * 2;

      // Notch filter state variables
      let notchState1 = 0;
      let notchState2 = 0;

      // Stereo variation (slight detune and phase offset)
      const stereoDetune = channel === 0 ? 0.998 : 1.002;
      const stereoPhaseOffset = channel === 0 ? 0 : 0.1;

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

        // Pitch envelope: exponential ramp from pitchMultiplier*freq down to base freq
        const pitchEnv = Math.exp(-t / modDuration);
        const currentFreq = tunedFreq * stereoDetune * (1 + (pitchMultiplier - 1) * pitchEnv);

        // Generate triangle wave
        phase += (2 * Math.PI * currentFreq) / sampleRate;
        if (phase > 2 * Math.PI) phase -= 2 * Math.PI;

        // Triangle wave from phase
        const triangle = phase < Math.PI
          ? -1 + (2 * phase) / Math.PI
          : 3 - (2 * phase) / Math.PI;

        // Attack envelope (5ms linear ramp)
        const attackEnv = t < attackTime ? t / attackTime : 1.0;

        // Tonal envelope: exponential decay
        const tonalEnv = Math.exp(-t / tonalDecayTime) * attackEnv;
        const tonalSignal = triangle * tonalEnv;

        // Generate white noise
        const noise = Math.random() * 2 - 1;

        // Apply notch filter to noise (removes 1000Hz component)
        const notchFiltered = this.notchFilter(
          noise,
          notchFreq,
          notchQ,
          sampleRate,
          notchState1,
          notchState2
        );
        notchState1 = notchFiltered.state1;
        notchState2 = notchFiltered.state2;

        // Noise envelope: exponential decay (longer than tonal)
        const noiseEnv = Math.exp(-t / noiseDecayTime);
        const noiseSignal = notchFiltered.output * noiseEnv;

        // Mix tonal and noise
        const tonalGain = baseVolume * (1 - params.tone * 0.5); // 0.5 to 0.25
        const noiseGain = 0.15 + params.tone * 0.2; // 0.15 to 0.35

        let sample = tonalSignal * tonalGain + noiseSignal * noiseGain;

        // Apply accent
        sample *= (0.5 + params.accent * 0.5);

        // Soft clipping
        sample = this.softClip(sample);

        output[i] = sample;
      }
    }

    // Normalize output to consistent level
    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 normGain = 0.5 / peak; // Normalize to 0.5 peak for more headroom
      for (let i = 0; i < numSamples; i++) {
        left[i] *= normGain;
        right[i] *= normGain;
      }
    }

    return [left, right];
  }

  private notchFilter(
    input: number,
    frequency: number,
    q: number,
    sampleRate: number,
    state1: number,
    state2: number
  ): { output: number; state1: number; state2: number } {
    // State variable filter configured as notch
    const normalizedFreq = Math.min(frequency / sampleRate, 0.48);
    const f = 2 * Math.sin(Math.PI * normalizedFreq);
    const qRecip = 1 / Math.max(q, 0.5);

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

    // Notch = input - bandpass
    const notch = input - bandpass;

    // Update states with clamping
    const newState1 = Math.max(-2, Math.min(2, bandpass));
    const newState2 = Math.max(-2, Math.min(2, lowpass));

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

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

  private freqToParam(freq: number): number {
    // Map frequency to 0-1 range (100-400 Hz)
    return Math.max(0, Math.min(1, (freq - 100) / 300));
  }
}
