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

enum Waveform {
  Sine = 0,
  Saw = 1,
  Square = 2,
  Triangle = 3,
}

interface OscillatorParams {
  waveform: Waveform;
  ratio: number;
  level: number;
  attack: number;
  decay: number;
  sustain: number;
  release: number;
}

interface FilterParams {
  cutoff: number;
  resonance: number;
  envAmount: number;
  attack: number;
  decay: number;
  sustain: number;
  release: number;
}

export interface SubtractiveThreeOscParams {
  baseFreq: number;
  osc1: OscillatorParams;
  osc2: OscillatorParams;
  osc3: OscillatorParams;
  filter: FilterParams;
  stereoWidth: number;
}

export class SubtractiveThreeOsc extends CsoundEngine<SubtractiveThreeOscParams> {
  getName(): string {
    return '3OSC';
  }

  getDescription(): string {
    return 'Three-oscillator subtractive synthesis with resonant filter';
  }

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

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

  protected getOrchestra(): string {
    return `
instr 1
  ; Get base frequency
  ibasefreq chnget "basefreq"
  istereo chnget "stereowidth"

  ; Oscillator 1 parameters
  iosc1wave chnget "osc1_wave"
  iosc1ratio chnget "osc1_ratio"
  iosc1level chnget "osc1_level"
  iosc1attack chnget "osc1_attack"
  iosc1decay chnget "osc1_decay"
  iosc1sustain chnget "osc1_sustain"
  iosc1release chnget "osc1_release"

  ; Oscillator 2 parameters
  iosc2wave chnget "osc2_wave"
  iosc2ratio chnget "osc2_ratio"
  iosc2level chnget "osc2_level"
  iosc2attack chnget "osc2_attack"
  iosc2decay chnget "osc2_decay"
  iosc2sustain chnget "osc2_sustain"
  iosc2release chnget "osc2_release"

  ; Oscillator 3 parameters
  iosc3wave chnget "osc3_wave"
  iosc3ratio chnget "osc3_ratio"
  iosc3level chnget "osc3_level"
  iosc3attack chnget "osc3_attack"
  iosc3decay chnget "osc3_decay"
  iosc3sustain chnget "osc3_sustain"
  iosc3release chnget "osc3_release"

  ; Filter parameters
  ifiltcutoff chnget "filt_cutoff"
  ifiltres chnget "filt_resonance"
  ifiltenvamt chnget "filt_envamt"
  ifiltattack chnget "filt_attack"
  ifiltdecay chnget "filt_decay"
  ifiltsustain chnget "filt_sustain"
  ifiltrelease chnget "filt_release"

  idur = p3

  ; Convert ratios to time values
  iosc1att = iosc1attack * idur
  iosc1dec = iosc1decay * idur
  iosc1rel = iosc1release * idur

  iosc2att = iosc2attack * idur
  iosc2dec = iosc2decay * idur
  iosc2rel = iosc2release * idur

  iosc3att = iosc3attack * idur
  iosc3dec = iosc3decay * idur
  iosc3rel = iosc3release * idur

  ifiltatt = ifiltattack * idur
  ifiltdec = ifiltdecay * idur
  ifiltrel = ifiltrelease * idur

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

  ; Oscillator 1 envelopes
  kenv1 madsr iosc1att, iosc1dec, iosc1sustain, iosc1rel

  ; Oscillator 1 - Left
  if iosc1wave == 0 then
    aosc1L oscili kenv1 * iosc1level, ifreqL * iosc1ratio
  elseif iosc1wave == 1 then
    aosc1L vco2 kenv1 * iosc1level, ifreqL * iosc1ratio, 0
  elseif iosc1wave == 2 then
    aosc1L vco2 kenv1 * iosc1level, ifreqL * iosc1ratio, 10
  else
    aosc1L vco2 kenv1 * iosc1level, ifreqL * iosc1ratio, 12
  endif

  ; Oscillator 1 - Right
  if iosc1wave == 0 then
    aosc1R oscili kenv1 * iosc1level, ifreqR * iosc1ratio
  elseif iosc1wave == 1 then
    aosc1R vco2 kenv1 * iosc1level, ifreqR * iosc1ratio, 0
  elseif iosc1wave == 2 then
    aosc1R vco2 kenv1 * iosc1level, ifreqR * iosc1ratio, 10
  else
    aosc1R vco2 kenv1 * iosc1level, ifreqR * iosc1ratio, 12
  endif

  ; Oscillator 2 envelopes
  kenv2 madsr iosc2att, iosc2dec, iosc2sustain, iosc2rel

  ; Oscillator 2 - Left
  if iosc2wave == 0 then
    aosc2L oscili kenv2 * iosc2level, ifreqL * iosc2ratio
  elseif iosc2wave == 1 then
    aosc2L vco2 kenv2 * iosc2level, ifreqL * iosc2ratio, 0
  elseif iosc2wave == 2 then
    aosc2L vco2 kenv2 * iosc2level, ifreqL * iosc2ratio, 10
  else
    aosc2L vco2 kenv2 * iosc2level, ifreqL * iosc2ratio, 12
  endif

  ; Oscillator 2 - Right
  if iosc2wave == 0 then
    aosc2R oscili kenv2 * iosc2level, ifreqR * iosc2ratio
  elseif iosc2wave == 1 then
    aosc2R vco2 kenv2 * iosc2level, ifreqR * iosc2ratio, 0
  elseif iosc2wave == 2 then
    aosc2R vco2 kenv2 * iosc2level, ifreqR * iosc2ratio, 10
  else
    aosc2R vco2 kenv2 * iosc2level, ifreqR * iosc2ratio, 12
  endif

  ; Oscillator 3 envelopes
  kenv3 madsr iosc3att, iosc3dec, iosc3sustain, iosc3rel

  ; Oscillator 3 - Left
  if iosc3wave == 0 then
    aosc3L oscili kenv3 * iosc3level, ifreqL * iosc3ratio
  elseif iosc3wave == 1 then
    aosc3L vco2 kenv3 * iosc3level, ifreqL * iosc3ratio, 0
  elseif iosc3wave == 2 then
    aosc3L vco2 kenv3 * iosc3level, ifreqL * iosc3ratio, 10
  else
    aosc3L vco2 kenv3 * iosc3level, ifreqL * iosc3ratio, 12
  endif

  ; Oscillator 3 - Right
  if iosc3wave == 0 then
    aosc3R oscili kenv3 * iosc3level, ifreqR * iosc3ratio
  elseif iosc3wave == 1 then
    aosc3R vco2 kenv3 * iosc3level, ifreqR * iosc3ratio, 0
  elseif iosc3wave == 2 then
    aosc3R vco2 kenv3 * iosc3level, ifreqR * iosc3ratio, 10
  else
    aosc3R vco2 kenv3 * iosc3level, ifreqR * iosc3ratio, 12
  endif

  ; Mix oscillators
  amixL = aosc1L + aosc2L + aosc3L
  amixR = aosc1R + aosc2R + aosc3R

  ; Filter envelope
  kfiltenv madsr ifiltatt, ifiltdec, ifiltsustain, ifiltrel
  kcutoff = ifiltcutoff + (kfiltenv * ifiltenvamt * 10000)
  kcutoff = limit(kcutoff, 20, 20000)

  ; Apply moogladder filter
  afiltL moogladder amixL, kcutoff, ifiltres
  afiltR moogladder amixR, kcutoff, ifiltres

  outs afiltL, afiltR
endin
`;
  }

  protected getParametersForCsound(params: SubtractiveThreeOscParams): CsoundParameter[] {
    return [
      { channelName: 'basefreq', value: params.baseFreq },
      { channelName: 'stereowidth', value: params.stereoWidth },

      { channelName: 'osc1_wave', value: params.osc1.waveform },
      { channelName: 'osc1_ratio', value: params.osc1.ratio },
      { channelName: 'osc1_level', value: params.osc1.level },
      { channelName: 'osc1_attack', value: params.osc1.attack },
      { channelName: 'osc1_decay', value: params.osc1.decay },
      { channelName: 'osc1_sustain', value: params.osc1.sustain },
      { channelName: 'osc1_release', value: params.osc1.release },

      { channelName: 'osc2_wave', value: params.osc2.waveform },
      { channelName: 'osc2_ratio', value: params.osc2.ratio },
      { channelName: 'osc2_level', value: params.osc2.level },
      { channelName: 'osc2_attack', value: params.osc2.attack },
      { channelName: 'osc2_decay', value: params.osc2.decay },
      { channelName: 'osc2_sustain', value: params.osc2.sustain },
      { channelName: 'osc2_release', value: params.osc2.release },

      { channelName: 'osc3_wave', value: params.osc3.waveform },
      { channelName: 'osc3_ratio', value: params.osc3.ratio },
      { channelName: 'osc3_level', value: params.osc3.level },
      { channelName: 'osc3_attack', value: params.osc3.attack },
      { channelName: 'osc3_decay', value: params.osc3.decay },
      { channelName: 'osc3_sustain', value: params.osc3.sustain },
      { channelName: 'osc3_release', value: params.osc3.release },

      { channelName: 'filt_cutoff', value: params.filter.cutoff },
      { channelName: 'filt_resonance', value: params.filter.resonance },
      { channelName: 'filt_envamt', value: params.filter.envAmount },
      { channelName: 'filt_attack', value: params.filter.attack },
      { channelName: 'filt_decay', value: params.filter.decay },
      { channelName: 'filt_sustain', value: params.filter.sustain },
      { channelName: 'filt_release', value: params.filter.release },
    ];
  }

  randomParams(pitchLock?: PitchLock): SubtractiveThreeOscParams {
    let baseFreq: number;
    if (pitchLock?.enabled) {
      baseFreq = pitchLock.frequency;
    } else {
      const baseFreqChoices = [55, 82.4, 110, 146.8, 220, 293.7, 440];
      baseFreq = this.randomChoice(baseFreqChoices) * this.randomRange(0.98, 1.02);
    }

    const harmonicRatios = [0.5, 1, 2, 3, 4];
    const detuneRatios = [0.99, 1.0, 1.01, 1.02, 0.98];

    return {
      baseFreq,
      osc1: this.randomOscillator(harmonicRatios),
      osc2: this.randomOscillator(detuneRatios),
      osc3: this.randomOscillator(harmonicRatios),
      filter: this.randomFilter(),
      stereoWidth: this.randomRange(0.2, 0.8),
    };
  }

  private randomOscillator(ratios: number[]): OscillatorParams {
    return {
      waveform: this.randomInt(0, 3) as Waveform,
      ratio: this.randomChoice(ratios),
      level: this.randomRange(0.2, 0.5),
      attack: this.randomRange(0.001, 0.15),
      decay: this.randomRange(0.02, 0.25),
      sustain: this.randomRange(0.3, 0.8),
      release: this.randomRange(0.05, 0.4),
    };
  }

  private randomFilter(): FilterParams {
    return {
      cutoff: this.randomRange(200, 5000),
      resonance: this.randomRange(0.1, 0.8),
      envAmount: this.randomRange(0.2, 1.2),
      attack: this.randomRange(0.001, 0.15),
      decay: this.randomRange(0.05, 0.3),
      sustain: this.randomRange(0.2, 0.7),
      release: this.randomRange(0.05, 0.4),
    };
  }

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

    return {
      baseFreq,
      osc1: this.mutateOscillator(params.osc1, mutationAmount),
      osc2: this.mutateOscillator(params.osc2, mutationAmount),
      osc3: this.mutateOscillator(params.osc3, mutationAmount),
      filter: this.mutateFilter(params.filter, mutationAmount),
      stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 1),
    };
  }

  private mutateOscillator(osc: OscillatorParams, amount: number): OscillatorParams {
    return {
      waveform: Math.random() < 0.1 ? (this.randomInt(0, 3) as Waveform) : osc.waveform,
      ratio: Math.random() < 0.1 ? this.randomChoice([0.5, 0.98, 0.99, 1, 1.01, 1.02, 2, 3, 4]) : osc.ratio,
      level: this.mutateValue(osc.level, amount, 0.1, 0.7),
      attack: this.mutateValue(osc.attack, amount, 0.001, 0.3),
      decay: this.mutateValue(osc.decay, amount, 0.01, 0.4),
      sustain: this.mutateValue(osc.sustain, amount, 0.1, 0.9),
      release: this.mutateValue(osc.release, amount, 0.02, 0.6),
    };
  }

  private mutateFilter(filter: FilterParams, amount: number): FilterParams {
    return {
      cutoff: this.mutateValue(filter.cutoff, amount, 100, 8000),
      resonance: this.mutateValue(filter.resonance, amount, 0, 0.95),
      envAmount: this.mutateValue(filter.envAmount, amount, 0, 1.5),
      attack: this.mutateValue(filter.attack, amount, 0.001, 0.3),
      decay: this.mutateValue(filter.decay, amount, 0.01, 0.4),
      sustain: this.mutateValue(filter.sustain, amount, 0.1, 0.9),
      release: this.mutateValue(filter.release, amount, 0.02, 0.6),
    };
  }
}
