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

interface TechnoKickParams {
  startFreq: number;
  endFreq: number;
  freqDecay: number;
  resonance: number;
  cutoffStart: number;
  cutoffEnd: number;
  cutoffDecay: number;
  ampAttack: number;
  ampDecay: number;
  noiseMix: number;
  punch: number;
  stereoWidth: number;
}

export class TechnoKick extends CsoundEngine<TechnoKickParams> {
  getName(): string {
    return 'Techno Kick';
  }

  getDescription(): string {
    return 'Noise through resonant low-pass filter with frequency sweep and RMS compression for punchy electronic kicks';
  }

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

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

  protected getOrchestra(): string {
    return `
instr 1
  iStartFreq chnget "startFreq"
  iEndFreq chnget "endFreq"
  iFreqDecay chnget "freqDecay"
  iResonance chnget "resonance"
  iCutoffStart chnget "cutoffStart"
  iCutoffEnd chnget "cutoffEnd"
  iCutoffDecay chnget "cutoffDecay"
  iAmpAttack chnget "ampAttack"
  iAmpDecay chnget "ampDecay"
  iNoiseMix chnget "noiseMix"
  iPunch chnget "punch"
  iStereoWidth chnget "stereoWidth"

  idur = p3
  iFreqDecayTime = iFreqDecay * idur
  iCutoffDecayTime = iCutoffDecay * idur
  iAmpAttackTime = iAmpAttack * idur
  iAmpDecayTime = iAmpDecay * idur

  ; Generate random noise
  aNoise noise 1, 0

  ; Frequency envelope for the filter cutoff (exponential sweep)
  kFreqEnv expseg iStartFreq, iFreqDecayTime, iEndFreq, idur - iFreqDecayTime, iEndFreq

  ; Cutoff modulation envelope
  kCutoffEnv expseg iCutoffStart, iCutoffDecayTime, iCutoffEnd, idur - iCutoffDecayTime, iCutoffEnd

  ; Apply resonant low-pass filter (rezzy)
  aFiltered rezzy aNoise * (1 + iNoiseMix), kCutoffEnv, iResonance

  ; Add sine sub-bass component for more weight
  kSubFreqEnv expseg iStartFreq * 0.5, iFreqDecayTime, iEndFreq * 0.5, idur - iFreqDecayTime, iEndFreq * 0.5
  aSubBass oscili 0.6, kSubFreqEnv

  ; Mix filtered noise and sub-bass
  aMix = aFiltered * 0.5 + aSubBass

  ; Amplitude envelope (exponential)
  kAmpEnv expseg 0.001, iAmpAttackTime, 1, iAmpDecayTime, 0.001, idur - iAmpAttackTime - iAmpDecayTime, 0.001

  ; Apply amplitude envelope
  aEnveloped = aMix * kAmpEnv

  ; RMS compression for punch
  ; Calculate RMS of signal
  kRMS rms aEnveloped
  if kRMS < 0.01 then
    kCompGain = 1
  else
    kCompGain = 1 + iPunch * (0.3 / kRMS - 1)
  endif
  kCompGain limit kCompGain, 0.5, 3

  aOut = aEnveloped * kCompGain

  ; Right channel with stereo width
  iStartFreqR = iStartFreq * (1 + iStereoWidth * 0.01)
  iEndFreqR = iEndFreq * (1 + iStereoWidth * 0.01)

  kFreqEnvR expseg iStartFreqR, iFreqDecayTime, iEndFreqR, idur - iFreqDecayTime, iEndFreqR
  kCutoffEnvR expseg iCutoffStart * (1 + iStereoWidth * 0.02), iCutoffDecayTime, iCutoffEnd * (1 + iStereoWidth * 0.02), idur - iCutoffDecayTime, iCutoffEnd * (1 + iStereoWidth * 0.02)

  aNoiseR noise 1, 0
  aFilteredR rezzy aNoiseR * (1 + iNoiseMix), kCutoffEnvR, iResonance

  kSubFreqEnvR expseg iStartFreqR * 0.5, iFreqDecayTime, iEndFreqR * 0.5, idur - iFreqDecayTime, iEndFreqR * 0.5
  aSubBassR oscili 0.6, kSubFreqEnvR

  aMixR = aFilteredR * 0.5 + aSubBassR
  aEnvelopedR = aMixR * kAmpEnv

  kRMSR rms aEnvelopedR
  if kRMSR < 0.01 then
    kCompGainR = 1
  else
    kCompGainR = 1 + iPunch * (0.3 / kRMSR - 1)
  endif
  kCompGainR limit kCompGainR, 0.5, 3

  aOutR = aEnvelopedR * kCompGainR

  outs aOut, aOutR
endin
`;
  }

  protected getParametersForCsound(params: TechnoKickParams): CsoundParameter[] {
    return [
      { channelName: 'startFreq', value: params.startFreq },
      { channelName: 'endFreq', value: params.endFreq },
      { channelName: 'freqDecay', value: params.freqDecay },
      { channelName: 'resonance', value: params.resonance },
      { channelName: 'cutoffStart', value: params.cutoffStart },
      { channelName: 'cutoffEnd', value: params.cutoffEnd },
      { channelName: 'cutoffDecay', value: params.cutoffDecay },
      { channelName: 'ampAttack', value: params.ampAttack },
      { channelName: 'ampDecay', value: params.ampDecay },
      { channelName: 'noiseMix', value: params.noiseMix },
      { channelName: 'punch', value: params.punch },
      { channelName: 'stereoWidth', value: params.stereoWidth },
    ];
  }

  randomParams(pitchLock?: PitchLock): TechnoKickParams {
    const endFreqChoices = [40, 45, 50, 55, 60, 70, 80];
    const endFreq = pitchLock?.enabled
      ? pitchLock.frequency
      : this.randomChoice(endFreqChoices) * this.randomRange(0.95, 1.05);

    return {
      startFreq: this.randomRange(800, 1200),
      endFreq,
      freqDecay: this.randomRange(0.05, 0.25),
      resonance: this.randomRange(5, 40),
      cutoffStart: this.randomRange(300, 800),
      cutoffEnd: this.randomRange(80, 200),
      cutoffDecay: this.randomRange(0.1, 0.4),
      ampAttack: this.randomRange(0.001, 0.005),
      ampDecay: this.randomRange(0.2, 0.6),
      noiseMix: this.randomRange(0.1, 0.8),
      punch: this.randomRange(0.3, 0.9),
      stereoWidth: this.randomRange(0, 0.3),
    };
  }

  mutateParams(
    params: TechnoKickParams,
    mutationAmount: number = 0.15,
    pitchLock?: PitchLock
  ): TechnoKickParams {
    const endFreq = pitchLock?.enabled ? pitchLock.frequency : params.endFreq;

    return {
      startFreq: this.mutateValue(params.startFreq, mutationAmount, 600, 1500),
      endFreq,
      freqDecay: this.mutateValue(params.freqDecay, mutationAmount, 0.02, 0.4),
      resonance: this.mutateValue(params.resonance, mutationAmount, 3, 50),
      cutoffStart: this.mutateValue(params.cutoffStart, mutationAmount, 200, 1000),
      cutoffEnd: this.mutateValue(params.cutoffEnd, mutationAmount, 60, 300),
      cutoffDecay: this.mutateValue(params.cutoffDecay, mutationAmount, 0.05, 0.6),
      ampAttack: this.mutateValue(params.ampAttack, mutationAmount, 0.001, 0.01),
      ampDecay: this.mutateValue(params.ampDecay, mutationAmount, 0.1, 0.8),
      noiseMix: this.mutateValue(params.noiseMix, mutationAmount, 0, 1),
      punch: this.mutateValue(params.punch, mutationAmount, 0.1, 1),
      stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 0.5),
    };
  }
}
