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

enum SourceType {
  Saw = 0,
  Pulse = 1,
  NoiseBurst = 2,
  Triangle = 3,
}

interface CombParams {
  feedback: number;
  brightness: number;
}

interface ResonatorParams {
  frequency: number;
  resonance: number;
  envAmount: number;
  attack: number;
  decay: number;
  sustain: number;
  release: number;
}

interface DelayParams {
  time1: number;
  time2: number;
  feedback: number;
  filterFreq: number;
  filterSweep: number;
  mix: number;
}

export interface CombResonatorParams {
  baseFreq: number;
  sourceType: SourceType;
  sourceDecay: number;
  comb: CombParams;
  resonator: ResonatorParams;
  delay: DelayParams;
  stereoWidth: number;
}

export class CombResonator extends CsoundEngine<CombResonatorParams> {
  getName(): string {
    return 'CombRes';
  }

  getDescription(): string {
    return 'Comb filter and resonator for metallic and bell-like sounds';
  }

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

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

  protected getOrchestra(): string {
    return `
instr 1
  ibasefreq chnget "basefreq"
  isourcetype chnget "sourcetype"
  isourcedecay chnget "sourcedecay"
  icombfeedback chnget "comb_feedback"
  icombbright chnget "comb_brightness"
  iresfreq chnget "res_frequency"
  iresres chnget "res_resonance"
  iresenvamt chnget "res_envamt"
  iresattack chnget "res_attack"
  iresdecay chnget "res_decay"
  iressustain chnget "res_sustain"
  iresrelease chnget "res_release"
  ideltime1 chnget "delay_time1"
  ideltime2 chnget "delay_time2"
  idelfeedback chnget "delay_feedback"
  ifilterfreq chnget "delay_filterfreq"
  ifiltersweep chnget "delay_filtersweep"
  idelmix chnget "delay_mix"
  istereo chnget "stereowidth"

  idur = p3

  ; Convert ratios to time values
  iresatt = iresattack * idur
  iresdec = iresdecay * idur
  iresrel = iresrelease * idur
  isrcdec = isourcedecay * idur
  idt1 = ideltime1 * idur
  idt2 = ideltime2 * idur

  ; Stereo detuning
  idetune = 1 + (istereo * 0.003)
  ifreqL = ibasefreq / idetune
  ifreqR = ibasefreq * idetune

  ; Source envelope - sharp attack, exponential decay
  ksrcenv expon 1, isrcdec, 0.001

  ; Generate source signals with more amplitude
  if isourcetype == 0 then
    ; Saw wave with harmonics
    asrcL vco2 ksrcenv * 0.8, ifreqL, 0
    asrcR vco2 ksrcenv * 0.8, ifreqR, 0
  elseif isourcetype == 1 then
    ; Pulse wave
    asrcL vco2 ksrcenv * 0.8, ifreqL, 2, 0.3
    asrcR vco2 ksrcenv * 0.8, ifreqR, 2, 0.3
  elseif isourcetype == 2 then
    ; Noise burst with pitch
    anoise1 noise ksrcenv * 0.6, 0
    anoise2 noise ksrcenv * 0.6, 0
    asrcL butterbp anoise1, ifreqL, ifreqL * 2
    asrcR butterbp anoise2, ifreqR, ifreqR * 2
  else
    ; Triangle wave
    asrcL vco2 ksrcenv * 0.8, ifreqL, 12
    asrcR vco2 ksrcenv * 0.8, ifreqR, 12
  endif

  ; Direct path for immediate sound (no delay)
  adirectL = asrcL * 0.3
  adirectR = asrcR * 0.3

  ; Comb filter using vcomb for better feedback control
  idelaytimeL = 1 / ifreqL
  idelaytimeR = 1 / ifreqR
  ilooptime = 0.1

  acombL vcomb asrcL, idelaytimeL, ilooptime, icombfeedback
  acombR vcomb asrcR, idelaytimeR, ilooptime, icombfeedback

  ; Damping filter
  adampl tone acombL, ibasefreq * icombbright
  adampR tone acombR, ibasefreq * icombbright

  ; Resonator envelope for filter sweep
  kresenv madsr iresatt, iresdec, iressustain, iresrel
  kresfreq = iresfreq + (kresenv * iresenvamt * ibasefreq * 4)
  kresfreq = limit(kresfreq, 80, 18000)

  ; Multiple resonators for richer tone
  kbw = kresfreq / iresres
  ares1L butterbp adampl, kresfreq, kbw
  ares1R butterbp adampR, kresfreq, kbw

  ares2L butterbp adampl, kresfreq * 1.5, kbw * 1.2
  ares2R butterbp adampR, kresfreq * 1.5, kbw * 1.2

  ; Mix resonators
  aresL = ares1L + (ares2L * 0.5)
  aresR = ares1R + (ares2R * 0.5)

  ; Dry signal (this plays immediately)
  adryL = (adampl * 0.2) + (aresL * 0.8)
  adryR = (adampR * 0.2) + (aresR * 0.8)

  ; Direct signal envelope: loud at start, fades out quickly
  kdirectenv linseg 1, idur * 0.05, 0, idur * 0.95, 0

  ; Crossfade envelope: start with dry signal, fade in delays
  kdryenv linseg 0.6, idur * 0.15, 0.3, idur * 0.85, 0.2
  kwetenv linseg 0, idur * 0.1, 1, idur * 0.9, 1

  ; Extreme filter sweep envelopes with multiple segments
  kfilter1 linseg ifilterfreq, idur * 0.2, ifilterfreq + (ifiltersweep * 12000), idur * 0.3, ifilterfreq - (ifiltersweep * 5000), idur * 0.5, ifilterfreq + (ifiltersweep * 15000)
  kfilter1 = limit(kfilter1, 250, 19000)

  kfilter2 expseg ifilterfreq * 2, idur * 0.15, ifilterfreq * 0.3 + 200, idur * 0.35, ifilterfreq * 3 + 100, idur * 0.5, ifilterfreq + (ifiltersweep * 10000) + 100
  kfilter2 = limit(kfilter2, 200, 18000)

  kfilter3 linseg ifilterfreq * 0.5, idur * 0.25, ifilterfreq + (ifiltersweep * 8000), idur * 0.25, ifilterfreq * 2, idur * 0.5, ifilterfreq - (ifiltersweep * 3000)
  kfilter3 = limit(kfilter3, 300, 16000)

  kfilter4 expon ifilterfreq + 100, idur, ifilterfreq + (ifiltersweep * 14000) + 100
  kfilter4 = limit(kfilter4, 350, 17000)

  ; LFO for delay time modulation
  klfo1 lfo 0.03, 3 + (ifiltersweep * 2)
  klfo2 lfo 0.04, 5 - (ifiltersweep * 1.5)

  ; Multi-tap delay line 1 (Left -> Right) with modulation
  abuf1 delayr idt1 * 1.1
  kdt1a = (idt1 * 0.2) + klfo1
  kdt1b = (idt1 * 0.45) - klfo2
  kdt1c = (idt1 * 0.75) + (klfo1 * 0.5)
  kdt1d = idt1
  adel1a deltap3 kdt1a
  adel1b deltap3 kdt1b
  adel1c deltap3 kdt1c
  adel1d deltap3 kdt1d
  delayw adryL + (adel1d * idelfeedback * 0.95)

  afilt1a butterbp adel1a, kfilter1, kfilter1 * 0.15
  afilt1b butterbp adel1b, kfilter2, kfilter2 * 0.2
  afilt1c butterbp adel1c, kfilter3, kfilter3 * 0.25
  afilt1d butterbp adel1d, kfilter4, kfilter4 * 0.18

  adelR = (afilt1a * 0.6) + (afilt1b * 0.8) + (afilt1c * 0.9) + (afilt1d * 0.7)

  ; Multi-tap delay line 2 (Right -> Left) with modulation
  abuf2 delayr idt2 * 1.1
  kdt2a = (idt2 * 0.18) - klfo2
  kdt2b = (idt2 * 0.42) + klfo1
  kdt2c = (idt2 * 0.7) - (klfo2 * 0.5)
  kdt2d = idt2
  adel2a deltap3 kdt2a
  adel2b deltap3 kdt2b
  adel2c deltap3 kdt2c
  adel2d deltap3 kdt2d
  delayw adryR + (adel2d * idelfeedback * 0.95)

  afilt2a butterbp adel2a, kfilter1 * 1.4, kfilter1 * 0.12
  afilt2b butterbp adel2b, kfilter2 * 0.7, kfilter2 * 0.22
  afilt2c butterbp adel2c, kfilter3 * 1.2, kfilter3 * 0.16
  afilt2d butterbp adel2d, kfilter4 * 0.9, kfilter4 * 0.2

  adelL = (afilt2a * 0.7) + (afilt2b * 0.6) + (afilt2c * 0.85) + (afilt2d * 0.8)

  ; Additional chaotic resonant delays
  abuf3 delayr idt1 * 1.6
  kdt3 = (idt1 * 1.4) + (klfo1 * 2)
  adel3 deltap3 kdt3
  delayw (adryL + adryR) * 0.5 + (adel3 * idelfeedback * 0.85)
  afilt3 butterbp adel3, kfilter2 * 0.6, kfilter2 * 0.1

  abuf4 delayr idt2 * 1.8
  kdt4 = (idt2 * 1.6) - (klfo2 * 2)
  adel4 deltap3 kdt4
  delayw (adryR + adryL) * 0.5 + (adel4 * idelfeedback * 0.8)
  afilt4 butterbp adel4, kfilter3 * 1.3, kfilter3 * 0.12

  abuf5 delayr idt1 * 2.2
  kdt5 = (idt1 * 2.0) + klfo1 + klfo2
  adel5 deltap3 kdt5
  delayw (adelL + adelR) * 0.4 + (adel5 * idelfeedback * 0.75)
  afilt5 butterbp adel5, kfilter4 * 0.8, kfilter4 * 0.08

  ; Mix: direct signal (immediate), dry (soon after), delays (build up)
  amixL = (adirectL * kdirectenv) + (adryL * kdryenv) + ((adelL * 1.2 + afilt3 * 0.8 + afilt4 * 0.7 + afilt5 * 0.6) * idelmix * kwetenv)
  amixR = (adirectR * kdirectenv) + (adryR * kdryenv) + ((adelR * 1.2 + afilt4 * 0.8 + afilt3 * 0.7 + afilt5 * 0.6) * idelmix * kwetenv)

  outs amixL, amixR
endin
`;
  }

  protected getParametersForCsound(params: CombResonatorParams): CsoundParameter[] {
    return [
      { channelName: 'basefreq', value: params.baseFreq },
      { channelName: 'sourcetype', value: params.sourceType },
      { channelName: 'sourcedecay', value: params.sourceDecay },
      { channelName: 'comb_feedback', value: params.comb.feedback },
      { channelName: 'comb_brightness', value: params.comb.brightness },
      { channelName: 'res_frequency', value: params.resonator.frequency },
      { channelName: 'res_resonance', value: params.resonator.resonance },
      { channelName: 'res_envamt', value: params.resonator.envAmount },
      { channelName: 'res_attack', value: params.resonator.attack },
      { channelName: 'res_decay', value: params.resonator.decay },
      { channelName: 'res_sustain', value: params.resonator.sustain },
      { channelName: 'res_release', value: params.resonator.release },
      { channelName: 'delay_time1', value: params.delay.time1 },
      { channelName: 'delay_time2', value: params.delay.time2 },
      { channelName: 'delay_feedback', value: params.delay.feedback },
      { channelName: 'delay_filterfreq', value: params.delay.filterFreq },
      { channelName: 'delay_filtersweep', value: params.delay.filterSweep },
      { channelName: 'delay_mix', value: params.delay.mix },
      { channelName: 'stereowidth', value: params.stereoWidth },
    ];
  }

  randomParams(pitchLock?: PitchLock): CombResonatorParams {
    let baseFreq: number;
    if (pitchLock?.enabled) {
      baseFreq = pitchLock.frequency;
    } else {
      const baseFreqChoices = [82.4, 110, 146.8, 220, 293.7, 440, 587.3, 880];
      baseFreq = this.randomChoice(baseFreqChoices) * this.randomRange(0.97, 1.03);
    }

    return {
      baseFreq,
      sourceType: this.randomInt(0, 3) as SourceType,
      sourceDecay: this.randomRange(0.02, 0.15),
      comb: {
        feedback: this.randomRange(0.7, 0.95),
        brightness: this.randomRange(3, 20),
      },
      resonator: {
        frequency: baseFreq * this.randomChoice([1, 1.3, 1.6, 2, 2.4, 3, 3.5, 4, 5]),
        resonance: this.randomRange(8, 80),
        envAmount: this.randomRange(0.3, 2.0),
        attack: this.randomRange(0.001, 0.05),
        decay: this.randomRange(0.15, 0.6),
        sustain: this.randomRange(0.05, 0.4),
        release: this.randomRange(0.3, 0.8),
      },
      delay: {
        time1: this.randomRange(0.08, 0.35),
        time2: this.randomRange(0.1, 0.4),
        feedback: this.randomRange(0.82, 0.98),
        filterFreq: this.randomRange(400, 5000),
        filterSweep: this.randomRange(-1.2, 2.0),
        mix: this.randomRange(0.6, 1.0),
      },
      stereoWidth: this.randomRange(0.2, 0.8),
    };
  }

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

    return {
      baseFreq,
      sourceType: Math.random() < 0.15 ? (this.randomInt(0, 3) as SourceType) : params.sourceType,
      sourceDecay: this.mutateValue(params.sourceDecay, mutationAmount, 0.02, 0.5),
      comb: {
        feedback: this.mutateValue(params.comb.feedback, mutationAmount, 0.3, 0.95),
        brightness: this.mutateValue(params.comb.brightness, mutationAmount, 1, 20),
      },
      resonator: {
        frequency: Math.random() < 0.2
          ? baseFreq * this.randomChoice([1, 1.5, 2, 2.5, 3, 3.5, 4])
          : this.mutateValue(params.resonator.frequency, mutationAmount, baseFreq * 0.5, baseFreq * 6),
        resonance: this.mutateValue(params.resonator.resonance, mutationAmount, 3, 80),
        envAmount: this.mutateValue(params.resonator.envAmount, mutationAmount, 0, 2),
        attack: this.mutateValue(params.resonator.attack, mutationAmount, 0.001, 0.2),
        decay: this.mutateValue(params.resonator.decay, mutationAmount, 0.05, 0.6),
        sustain: this.mutateValue(params.resonator.sustain, mutationAmount, 0, 0.7),
        release: this.mutateValue(params.resonator.release, mutationAmount, 0.1, 0.9),
      },
      delay: {
        time1: this.mutateValue(params.delay.time1, mutationAmount, 0.05, 0.5),
        time2: this.mutateValue(params.delay.time2, mutationAmount, 0.08, 0.55),
        feedback: this.mutateValue(params.delay.feedback, mutationAmount, 0.75, 0.99),
        filterFreq: this.mutateValue(params.delay.filterFreq, mutationAmount, 350, 7000),
        filterSweep: this.mutateValue(params.delay.filterSweep, mutationAmount, -1.5, 2.5),
        mix: this.mutateValue(params.delay.mix, mutationAmount, 0.5, 1.0),
      },
      stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 1),
    };
  }
}
