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

interface DripwaterParams {
  baseFreq: number;
  dropRate: number;
  numElements: number;
  damping: number;
  shakeIntensity: number;
  freqRatio1: number;
  freqRatio2: number;
  freqRatio3: number;
  attack: number;
  release: number;
  amplitude: number;
  delayTime1: number;
  delayTime2: number;
  delayTime3: number;
  delayTime4: number;
  delayGain1: number;
  delayGain2: number;
  delayGain3: number;
  delayGain4: number;
  feedbackDelayTime: number;
  feedbackAmount: number;
  modDepth: number;
  modRate: number;
  stereoSpread: number;
  combDelayTime: number;
  combFeedback: number;
  reverseDelayTime: number;
  reverseGain: number;
  cascadeDelay1: number;
  cascadeDelay2: number;
  cascadeFeedback: number;
}

export class Dripwater extends CsoundEngine<DripwaterParams> {
  getName(): string {
    return 'Weird Waters';
  }

  getDescription(): string {
    return 'Physical model of water droplets through cascading delays, comb filters, and reverse echoes';
  }

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

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

  protected getOrchestra(): string {
    return `
instr 1
  iBaseFreq chnget "baseFreq"
  iDropRate chnget "dropRate"
  iNumElements chnget "numElements"
  iDamping chnget "damping"
  iShakeIntensity chnget "shakeIntensity"
  iFreqRatio1 chnget "freqRatio1"
  iFreqRatio2 chnget "freqRatio2"
  iFreqRatio3 chnget "freqRatio3"
  iAttack chnget "attack"
  iRelease chnget "release"
  iAmplitude chnget "amplitude"
  iDelayTime1 chnget "delayTime1"
  iDelayTime2 chnget "delayTime2"
  iDelayTime3 chnget "delayTime3"
  iDelayTime4 chnget "delayTime4"
  iDelayGain1 chnget "delayGain1"
  iDelayGain2 chnget "delayGain2"
  iDelayGain3 chnget "delayGain3"
  iDelayGain4 chnget "delayGain4"
  iFeedbackDelayTime chnget "feedbackDelayTime"
  iFeedbackAmount chnget "feedbackAmount"
  iModDepth chnget "modDepth"
  iModRate chnget "modRate"
  iStereoSpread chnget "stereoSpread"
  iCombDelayTime chnget "combDelayTime"
  iCombFeedback chnget "combFeedback"
  iReverseDelayTime chnget "reverseDelayTime"
  iReverseGain chnget "reverseGain"
  iCascadeDelay1 chnget "cascadeDelay1"
  iCascadeDelay2 chnget "cascadeDelay2"
  iCascadeFeedback chnget "cascadeFeedback"

  idur = p3
  iAttackTime = iAttack * idur
  iReleaseTime = iRelease * idur

  ; Scale delay times by duration so they fill the entire sound
  iScaledDelayTime1 = iDelayTime1 * idur
  iScaledDelayTime2 = iDelayTime2 * idur
  iScaledDelayTime3 = iDelayTime3 * idur
  iScaledDelayTime4 = iDelayTime4 * idur
  iScaledFeedbackDelay = iFeedbackDelayTime * idur
  iModDepthScaled = iModDepth * idur * 0.1
  iScaledCombDelay = iCombDelayTime * idur
  iScaledReverseDelay = iReverseDelayTime * idur
  iScaledCascade1 = iCascadeDelay1 * idur
  iScaledCascade2 = iCascadeDelay2 * idur

  ; Calculate resonant frequencies from base frequency and ratios
  iFreq1 = iBaseFreq * iFreqRatio1
  iFreq2 = iBaseFreq * iFreqRatio2
  iFreq3 = iBaseFreq * iFreqRatio3

  ; Generate dripwater sound
  ; Signature: ares dripwater kamp, idettack, inum, idamp, imaxshake, ifreq, ifreq1, ifreq2
  ; Reduce amplitude to prevent clipping with all the delays
  kAmp = iAmplitude * 0.08
  aDrip dripwater kAmp, iDropRate, iNumElements, iDamping, iShakeIntensity, iFreq1, iFreq2, iFreq3

  ; Apply AR envelope
  kEnv linsegr 0, iAttackTime, 1, iReleaseTime, 0

  aDry = aDrip * kEnv

  ; Apply multitap delay with duration-scaled times
  ; Signature: a1 [, a2, a3, ...] multitap asource [, itime1, igain1, itime2, igain2, ...]
  aMultitap multitap aDry, iScaledDelayTime1, iDelayGain1, iScaledDelayTime2, iDelayGain2, iScaledDelayTime3, iDelayGain3, iScaledDelayTime4, iDelayGain4

  ; Add modulated feedback delay
  ; LFO modulates delay time
  kLFO oscili iModDepthScaled, iModRate
  kDelayTime = iScaledFeedbackDelay + kLFO
  kDelayTime = max(kDelayTime, 0.001)

  ; Variable delay with feedback
  aBuf delayr idur
  aTapL deltapi kDelayTime
  delayw aDry + (aTapL * iFeedbackAmount)

  ; Comb filter delay (creates resonance)
  aComb comb aDry, iScaledCombDelay, iCombFeedback

  ; Reverse delay using delayr/delayw with interpolation
  aBufRev delayr iScaledReverseDelay
  aReverseTap deltapi iScaledReverseDelay * (1 - (line:a(0, idur, 1)))
  delayw aDry
  aReverse = aReverseTap * iReverseGain

  ; Cascaded delays (two delays feeding into each other)
  aBuf1 delayr idur
  aCascade1 deltapi iScaledCascade1
  aBuf2 delayr idur
  aCascade2 deltapi iScaledCascade2
  delayw aCascade1 * iCascadeFeedback
  delayw aDry + (aCascade2 * iCascadeFeedback)

  aOut = aDry + aMultitap + aTapL + aComb + aReverse + aCascade1 + aCascade2

  ; Create stereo width with slightly different parameters for right channel
  ; Slightly detune the resonant frequencies for right channel
  iFreq1_R = iFreq1 * 1.003
  iFreq2_R = iFreq2 * 0.998
  iFreq3_R = iFreq3 * 1.002

  ; Use slightly different initial values for variation
  iDropRate_R = iDropRate * 1.02
  iShakeIntensity_R = iShakeIntensity * 0.98

  kAmp_R = iAmplitude * 0.08
  aDrip_R dripwater kAmp_R, iDropRate_R, iNumElements, iDamping, iShakeIntensity_R, iFreq1_R, iFreq2_R, iFreq3_R

  aDry_R = aDrip_R * kEnv

  ; Apply multitap delay to right channel with slightly different times for stereo width
  iScaledDelayTime1_R = iScaledDelayTime1 * 1.03
  iScaledDelayTime2_R = iScaledDelayTime2 * 0.97
  iScaledDelayTime3_R = iScaledDelayTime3 * 1.05
  iScaledDelayTime4_R = iScaledDelayTime4 * 0.95

  aMultitap_R multitap aDry_R, iScaledDelayTime1_R, iDelayGain1, iScaledDelayTime2_R, iDelayGain2, iScaledDelayTime3_R, iDelayGain3, iScaledDelayTime4_R, iDelayGain4

  ; Right channel modulated delay with stereo spread
  kLFO_R oscili iModDepthScaled, iModRate * 1.07
  kDelayTime_R = (iScaledFeedbackDelay * iStereoSpread) + kLFO_R
  kDelayTime_R = max(kDelayTime_R, 0.001)

  aBuf_R delayr idur
  aTapR deltapi kDelayTime_R
  delayw aDry_R + (aTapR * iFeedbackAmount)

  ; Comb filter for right channel
  iScaledCombDelay_R = iScaledCombDelay * 1.07
  aComb_R comb aDry_R, iScaledCombDelay_R, iCombFeedback

  ; Reverse delay for right channel
  iScaledReverseDelay_R = iScaledReverseDelay * 0.93
  aBufRev_R delayr iScaledReverseDelay_R
  aReverseTap_R deltapi iScaledReverseDelay_R * (1 - (line:a(0, idur, 1)))
  delayw aDry_R
  aReverse_R = aReverseTap_R * iReverseGain

  ; Cascaded delays for right channel
  iScaledCascade1_R = iScaledCascade1 * 1.05
  iScaledCascade2_R = iScaledCascade2 * 0.95
  aBuf1_R delayr idur
  aCascade1_R deltapi iScaledCascade1_R
  aBuf2_R delayr idur
  aCascade2_R deltapi iScaledCascade2_R
  delayw aCascade1_R * iCascadeFeedback
  delayw aDry_R + (aCascade2_R * iCascadeFeedback)

  aOut_R = aDry_R + aMultitap_R + aTapR + aComb_R + aReverse_R + aCascade1_R + aCascade2_R

  outs aOut, aOut_R
endin
`;
  }

  protected getParametersForCsound(params: DripwaterParams): CsoundParameter[] {
    return [
      { channelName: 'baseFreq', value: params.baseFreq },
      { channelName: 'dropRate', value: params.dropRate },
      { channelName: 'numElements', value: params.numElements },
      { channelName: 'damping', value: params.damping },
      { channelName: 'shakeIntensity', value: params.shakeIntensity },
      { channelName: 'freqRatio1', value: params.freqRatio1 },
      { channelName: 'freqRatio2', value: params.freqRatio2 },
      { channelName: 'freqRatio3', value: params.freqRatio3 },
      { channelName: 'attack', value: params.attack },
      { channelName: 'release', value: params.release },
      { channelName: 'amplitude', value: params.amplitude },
      { channelName: 'delayTime1', value: params.delayTime1 },
      { channelName: 'delayTime2', value: params.delayTime2 },
      { channelName: 'delayTime3', value: params.delayTime3 },
      { channelName: 'delayTime4', value: params.delayTime4 },
      { channelName: 'delayGain1', value: params.delayGain1 },
      { channelName: 'delayGain2', value: params.delayGain2 },
      { channelName: 'delayGain3', value: params.delayGain3 },
      { channelName: 'delayGain4', value: params.delayGain4 },
      { channelName: 'feedbackDelayTime', value: params.feedbackDelayTime },
      { channelName: 'feedbackAmount', value: params.feedbackAmount },
      { channelName: 'modDepth', value: params.modDepth },
      { channelName: 'modRate', value: params.modRate },
      { channelName: 'stereoSpread', value: params.stereoSpread },
      { channelName: 'combDelayTime', value: params.combDelayTime },
      { channelName: 'combFeedback', value: params.combFeedback },
      { channelName: 'reverseDelayTime', value: params.reverseDelayTime },
      { channelName: 'reverseGain', value: params.reverseGain },
      { channelName: 'cascadeDelay1', value: params.cascadeDelay1 },
      { channelName: 'cascadeDelay2', value: params.cascadeDelay2 },
      { channelName: 'cascadeFeedback', value: params.cascadeFeedback },
    ];
  }

  randomParams(pitchLock?: PitchLock): DripwaterParams {
    const baseFreqChoices = [55, 82.4, 110, 146.8, 220, 293.7, 440, 587.3, 880];
    const baseFreq = pitchLock?.enabled
      ? pitchLock.frequency
      : this.randomChoice(baseFreqChoices) * this.randomRange(0.95, 1.05);

    return {
      baseFreq,
      dropRate: this.randomRange(2, 40),
      numElements: this.randomInt(4, 48),
      damping: this.randomRange(0.1, 0.85),
      shakeIntensity: this.randomRange(0.2, 0.9),
      freqRatio1: this.randomRange(1.0, 3.5),
      freqRatio2: this.randomRange(2.5, 7.0),
      freqRatio3: this.randomRange(5.0, 14.0),
      attack: this.randomRange(0.001, 0.04),
      release: this.randomRange(0.15, 0.9),
      amplitude: this.randomRange(0.6, 0.95),
      delayTime1: this.randomRange(0.1, 0.3),
      delayTime2: this.randomRange(0.25, 0.5),
      delayTime3: this.randomRange(0.45, 0.7),
      delayTime4: this.randomRange(0.65, 0.95),
      delayGain1: this.randomRange(0.4, 0.7),
      delayGain2: this.randomRange(0.3, 0.55),
      delayGain3: this.randomRange(0.2, 0.4),
      delayGain4: this.randomRange(0.1, 0.25),
      feedbackDelayTime: this.randomRange(0.15, 0.5),
      feedbackAmount: this.randomRange(0.3, 0.7),
      modDepth: this.randomRange(0.02, 0.12),
      modRate: this.randomRange(0.1, 3.0),
      stereoSpread: this.randomRange(0.85, 1.15),
      combDelayTime: this.randomRange(0.05, 0.25),
      combFeedback: this.randomRange(0.4, 0.8),
      reverseDelayTime: this.randomRange(0.3, 0.8),
      reverseGain: this.randomRange(0.15, 0.4),
      cascadeDelay1: this.randomRange(0.2, 0.45),
      cascadeDelay2: this.randomRange(0.35, 0.65),
      cascadeFeedback: this.randomRange(0.3, 0.65),
    };
  }

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

    return {
      baseFreq,
      dropRate: this.mutateValue(params.dropRate, mutationAmount, 1, 50),
      numElements: Math.round(this.mutateValue(params.numElements, mutationAmount, 2, 64)),
      damping: this.mutateValue(params.damping, mutationAmount, 0.0, 0.95),
      shakeIntensity: this.mutateValue(params.shakeIntensity, mutationAmount, 0.1, 1.0),
      freqRatio1: this.mutateValue(params.freqRatio1, mutationAmount, 1.0, 4.0),
      freqRatio2: this.mutateValue(params.freqRatio2, mutationAmount, 2.0, 8.0),
      freqRatio3: this.mutateValue(params.freqRatio3, mutationAmount, 4.0, 16.0),
      attack: this.mutateValue(params.attack, mutationAmount, 0.001, 0.05),
      release: this.mutateValue(params.release, mutationAmount, 0.1, 0.95),
      amplitude: this.mutateValue(params.amplitude, mutationAmount, 0.5, 1.0),
      delayTime1: this.mutateValue(params.delayTime1, mutationAmount, 0.05, 0.35),
      delayTime2: this.mutateValue(params.delayTime2, mutationAmount, 0.2, 0.6),
      delayTime3: this.mutateValue(params.delayTime3, mutationAmount, 0.4, 0.8),
      delayTime4: this.mutateValue(params.delayTime4, mutationAmount, 0.6, 0.98),
      delayGain1: this.mutateValue(params.delayGain1, mutationAmount, 0.3, 0.8),
      delayGain2: this.mutateValue(params.delayGain2, mutationAmount, 0.2, 0.65),
      delayGain3: this.mutateValue(params.delayGain3, mutationAmount, 0.1, 0.5),
      delayGain4: this.mutateValue(params.delayGain4, mutationAmount, 0.05, 0.35),
      feedbackDelayTime: this.mutateValue(params.feedbackDelayTime, mutationAmount, 0.1, 0.6),
      feedbackAmount: this.mutateValue(params.feedbackAmount, mutationAmount, 0.2, 0.8),
      modDepth: this.mutateValue(params.modDepth, mutationAmount, 0.01, 0.15),
      modRate: this.mutateValue(params.modRate, mutationAmount, 0.05, 4.0),
      stereoSpread: this.mutateValue(params.stereoSpread, mutationAmount, 0.7, 1.3),
      combDelayTime: this.mutateValue(params.combDelayTime, mutationAmount, 0.03, 0.3),
      combFeedback: this.mutateValue(params.combFeedback, mutationAmount, 0.3, 0.85),
      reverseDelayTime: this.mutateValue(params.reverseDelayTime, mutationAmount, 0.2, 0.9),
      reverseGain: this.mutateValue(params.reverseGain, mutationAmount, 0.1, 0.5),
      cascadeDelay1: this.mutateValue(params.cascadeDelay1, mutationAmount, 0.15, 0.5),
      cascadeDelay2: this.mutateValue(params.cascadeDelay2, mutationAmount, 0.3, 0.7),
      cascadeFeedback: this.mutateValue(params.cascadeFeedback, mutationAmount, 0.2, 0.75),
    };
  }
}
