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

interface HiHatParams {
  // Decay time (0 = closed/tight, 1 = open/long)
  decay: number;

  // Tone/brightness (filter cutoff frequency)
  tone: number;

  // Noise mix (0 = pure metallic, 1 = more noise)
  noise: number;

  // Base pitch
  pitch: number;

  // Timbre (shifts frequency ratios for different metallic characters)
  timbre: number;
}

export class HiHat implements SynthEngine {
  getName(): string {
    return 'Noise Hi-Hat';
  }

  getDescription(): string {
    return 'Metallic hi-hat from square wave oscillators and noise';
  }

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

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

  randomParams(pitchLock?: PitchLock): HiHatParams {
    return {
      decay: Math.random(),
      tone: 0.4 + Math.random() * 0.6,
      noise: 0.3 + Math.random() * 0.5,
      pitch: pitchLock ? this.freqToParam(pitchLock.frequency) : 0.4 + Math.random() * 0.2,
      timbre: Math.random(),
    };
  }

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

    return {
      decay: mutate(params.decay, 0.25),
      tone: mutate(params.tone, 0.25),
      noise: mutate(params.noise, 0.2),
      pitch: pitchLock ? params.pitch : mutate(params.pitch, 0.15),
      timbre: mutate(params.timbre, 0.25),
    };
  }

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

    // Decay time: 30ms (tight closed) to 800ms (long open)
    const decayTime = (0.03 + params.decay * 0.77) * duration;

    // Bandpass filter frequencies for metallic character (based on classic hi-hat synthesis)
    // Pitch shifts all frequencies, timbre changes the spread
    const basePitch = pitchLock ? pitchLock.frequency : 600 + params.pitch * 800;
    const pitchScale = basePitch / 1000; // normalize to ~1000Hz center

    // Timbre controls frequency spread: 0 = tight/focused, 1 = wide/trashy
    const spread = 0.7 + params.timbre * 0.6;

    const filterFreqs = [
      4500 * pitchScale * spread,
      6200 * pitchScale * spread,
      7800 * pitchScale * spread,
      9600 * pitchScale * spread,
      11400 * pitchScale * spread,
      13200 * pitchScale * spread,
    ];

    // Tone controls overall brightness via highpass cutoff
    const hpFreq = 4000 + params.tone * 6000; // 4kHz to 10kHz

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

      // Bandpass filter states (6 filters, 2 states each)
      const bpStates1 = new Array(6).fill(0);
      const bpStates2 = new Array(6).fill(0);

      // Highpass filter state
      let hp1 = 0, hp2 = 0;

      // Stereo variation in filter frequencies
      const stereoShift = channel === 0 ? 0.995 : 1.005;

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

        // Exponential decay envelope
        const env = Math.exp(-t / decayTime);

        // White noise source
        const noise = Math.random() * 2 - 1;

        // Pass noise through 6 bandpass filters at different frequencies
        let filtered = 0;
        for (let j = 0; j < filterFreqs.length; j++) {
          const freq = filterFreqs[j] * stereoShift;
          const q = 8 + j * 2; // Higher Q for higher frequencies

          const freqNorm = Math.min(freq / sampleRate, 0.48);
          const f = 2 * Math.sin(Math.PI * freqNorm);
          const qRecip = 1 / q;

          const lp = bpStates2[j] + f * bpStates1[j];
          const hp = noise - lp - qRecip * bpStates1[j];
          const bp = f * hp + bpStates1[j];

          bpStates1[j] = Math.max(-2, Math.min(2, bp));
          bpStates2[j] = Math.max(-2, Math.min(2, lp));

          filtered += bp;
        }

        // Average the bandpass outputs
        let sample = filtered / filterFreqs.length;

        // Add raw noise for more character (controlled by noise param)
        sample = sample * (1 - params.noise * 0.5) + noise * params.noise * 0.3;

        // Apply envelope
        sample *= env;

        // Highpass filter for brightness control
        const hpNorm = Math.min(hpFreq / sampleRate, 0.48);
        const hpA = 1 - hpNorm * 2;
        const hpFiltered = hpA * (hp1 + sample - hp2);
        hp2 = sample;
        hp1 = hpFiltered;

        sample = hpFiltered;

        // Soft clip
        if (sample > 0.7) sample = 0.7 + (sample - 0.7) * 0.3;
        if (sample < -0.7) sample = -0.7 + (sample + 0.7) * 0.3;

        output[i] = sample;
      }
    }

    // Normalize
    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;
      for (let i = 0; i < numSamples; i++) {
        left[i] *= normGain;
        right[i] *= normGain;
      }
    }

    return [left, right];
  }

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