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

enum VowelType {
  A,
  E,
  I,
  O,
  U,
  AE,
  OE,
  UE,
}

enum ModulationType {
  SimpleFM,
  DoubleFM,
  RingMod,
  CrossFM,
}

interface FormantBand {
  frequency: number;
  bandwidth: number;
  amplitude: number;
}

interface FormantFMParams {
  baseFreq: number;
  vowel: VowelType;
  vowelMorph: number;
  modulationType: ModulationType;
  modIndex: number;
  modRatio: number;
  attack: number;
  decay: number;
  sustain: number;
  release: number;
  brightness: number;
  vibrato: number;
  vibratoRate: number;
  detune: number;
  noise: number;
  formantLFORate: number;
  formantLFODepth: number;
  modIndexLFORate: number;
  modIndexLFODepth: number;
  chaos: number;
}

export class FormantFM extends CsoundEngine<FormantFMParams> {
  getName(): string {
    return 'Formant FM';
  }

  getDescription(): string {
    return 'FM synthesis with formant filters creating vowel-like sounds and vocal textures';
  }

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

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

  protected getOrchestra(): string {
    return `
instr 1
  iBaseFreq chnget "baseFreq"
  iVowel chnget "vowel"
  iVowelMorph chnget "vowelMorph"
  iModType chnget "modulationType"
  iModIndex chnget "modIndex"
  iModRatio chnget "modRatio"
  iAttack chnget "attack"
  iDecay chnget "decay"
  iSustain chnget "sustain"
  iRelease chnget "release"
  iBrightness chnget "brightness"
  iVibrato chnget "vibrato"
  iVibratoRate chnget "vibratoRate"
  iDetune chnget "detune"
  iNoise chnget "noise"
  iFormantLFORate chnget "formantLFORate"
  iFormantLFODepth chnget "formantLFODepth"
  iModIndexLFORate chnget "modIndexLFORate"
  iModIndexLFODepth chnget "modIndexLFODepth"
  iChaos chnget "chaos"

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

  ; Envelope
  kEnv madsr iAttackTime, iDecayTime, iSustain, iReleaseTime

  ; Vibrato LFO with chaos
  kVib oscili iVibrato * iBaseFreq * 0.02, iVibratoRate
  kChaosLFO1 oscili iChaos * iBaseFreq * 0.01, iVibratoRate * 1.618
  kChaosLFO2 oscili iChaos * iBaseFreq * 0.005, iVibratoRate * 2.414
  kFreq = iBaseFreq + kVib + kChaosLFO1 + kChaosLFO2

  ; Get formant parameters based on vowel type
  if iVowel == 0 then
    ; A (as in "father")
    iF1 = 730
    iF2 = 1090
    iF3 = 2440
    iA1 = 1.0
    iA2 = 0.5
    iA3 = 0.25
    iBW1 = 80
    iBW2 = 90
    iBW3 = 120
  elseif iVowel == 1 then
    ; E (as in "bet")
    iF1 = 530
    iF2 = 1840
    iF3 = 2480
    iA1 = 1.0
    iA2 = 0.6
    iA3 = 0.3
    iBW1 = 60
    iBW2 = 100
    iBW3 = 120
  elseif iVowel == 2 then
    ; I (as in "bit")
    iF1 = 390
    iF2 = 1990
    iF3 = 2550
    iA1 = 1.0
    iA2 = 0.7
    iA3 = 0.2
    iBW1 = 50
    iBW2 = 100
    iBW3 = 120
  elseif iVowel == 3 then
    ; O (as in "boat")
    iF1 = 570
    iF2 = 840
    iF3 = 2410
    iA1 = 1.0
    iA2 = 0.45
    iA3 = 0.28
    iBW1 = 70
    iBW2 = 80
    iBW3 = 100
  elseif iVowel == 4 then
    ; U (as in "boot")
    iF1 = 440
    iF2 = 1020
    iF3 = 2240
    iA1 = 1.0
    iA2 = 0.4
    iA3 = 0.2
    iBW1 = 70
    iBW2 = 80
    iBW3 = 100
  elseif iVowel == 5 then
    ; AE (as in "bat")
    iF1 = 660
    iF2 = 1720
    iF3 = 2410
    iA1 = 1.0
    iA2 = 0.55
    iA3 = 0.3
    iBW1 = 80
    iBW2 = 90
    iBW3 = 120
  elseif iVowel == 6 then
    ; OE (as in "bird")
    iF1 = 490
    iF2 = 1350
    iF3 = 1690
    iA1 = 1.0
    iA2 = 0.5
    iA3 = 0.4
    iBW1 = 70
    iBW2 = 80
    iBW3 = 100
  else
    ; UE (as in "about")
    iF1 = 520
    iF2 = 1190
    iF3 = 2390
    iA1 = 1.0
    iA2 = 0.45
    iA3 = 0.25
    iBW1 = 70
    iBW2 = 80
    iBW3 = 110
  endif

  ; Modulate formant frequencies with multiple LFOs
  kFormantShift = 1 + (iVowelMorph - 0.5) * 0.4
  kFormantLFO oscili iFormantLFODepth * 0.3, iFormantLFORate
  kFormantShiftModulated = kFormantShift + kFormantLFO
  kF1 = iF1 * kFormantShiftModulated
  kF2 = iF2 * kFormantShiftModulated
  kF3 = iF3 * kFormantShiftModulated

  ; Modulation index LFO
  kModIndexLFO oscili iModIndexLFODepth * iModIndex, iModIndexLFORate
  kModIndexDynamic = iModIndex + kModIndexLFO

  ; Generate carrier and modulator based on modulation type
  kModFreq = kFreq * iModRatio

  if iModType == 0 then
    ; Simple FM with dynamic modulation
    aMod oscili kModIndexDynamic * kModFreq, kModFreq
    aCarrier oscili 0.5, kFreq + aMod

  elseif iModType == 1 then
    ; Double FM (modulator modulates itself) with chaos
    kChaosModDepth = 1 + (iChaos * 0.5)
    aMod1 oscili kModIndexDynamic * 0.3 * kModFreq * kChaosModDepth, kModFreq * 0.5
    aMod2 oscili kModIndexDynamic * kModFreq, kModFreq + aMod1
    aCarrier oscili 0.5, kFreq + aMod2

  elseif iModType == 2 then
    ; Ring modulation with frequency wobble
    kRingModWobble oscili iChaos * 0.2, iFormantLFORate * 0.7
    aMod oscili 0.5, kModFreq * (1 + kRingModWobble)
    aCarrierTemp oscili 0.5, kFreq
    aCarrier = aCarrierTemp * aMod

  else
    ; Cross FM with multiple carriers
    aMod oscili kModIndexDynamic * kModFreq, kModFreq
    aCarrier1 oscili 0.4, kFreq + aMod
    aCarrier2 oscili 0.3, kFreq * 0.5 + aMod * 0.5
    kThirdCarrierFreq = kFreq * (1.5 + iChaos * 0.3)
    aCarrier3 oscili 0.2 * iChaos, kThirdCarrierFreq + aMod * 0.3
    aCarrier = aCarrier1 + aCarrier2 + aCarrier3
  endif

  ; Add brightness via high-frequency content
  aCarrierBright oscili 0.15 * iBrightness, kFreq * 2
  aCarrierMix = aCarrier + aCarrierBright

  ; Add subtle noise for breathiness
  aNoise noise 0.08 * iNoise, 0
  aCarrierFinal = aCarrierMix + aNoise

  ; Apply formant filters (bandpass filters at formant frequencies)
  aFormant1 butterbp aCarrierFinal, kF1, iBW1
  aFormant1Scaled = aFormant1 * iA1

  aFormant2 butterbp aCarrierFinal, kF2, iBW2
  aFormant2Scaled = aFormant2 * iA2

  aFormant3 butterbp aCarrierFinal, kF3, iBW3
  aFormant3Scaled = aFormant3 * iA3

  ; Mix formants
  aMix = (aFormant1Scaled + aFormant2Scaled + aFormant3Scaled) * 0.6

  ; Apply envelope
  aOut = aMix * kEnv

  ; Stereo - slightly different phase for right channel
  iDetuneFactor = 1 + (iDetune * 0.5)
  kFreqR = iBaseFreq * iDetuneFactor + kVib

  ; Regenerate right channel with detuned frequency
  kModFreqR = kFreqR * iModRatio

  if iModType == 0 then
    aModR oscili iModIndex * kModFreqR, kModFreqR
    aCarrierR oscili 0.5, kFreqR + aModR
  elseif iModType == 1 then
    aMod1R oscili iModIndex * 0.3 * kModFreqR, kModFreqR * 0.5
    aMod2R oscili iModIndex * kModFreqR, kModFreqR + aMod1R
    aCarrierR oscili 0.5, kFreqR + aMod2R
  elseif iModType == 2 then
    aModR oscili 0.5, kModFreqR
    aCarrierTempR oscili 0.5, kFreqR
    aCarrierR = aCarrierTempR * aModR
  else
    aModR oscili iModIndex * kModFreqR, kModFreqR
    aCarrier1R oscili 0.4, kFreqR + aModR
    aCarrier2R oscili 0.3, kFreqR * 0.5 + aModR * 0.5
    aCarrierR = aCarrier1R + aCarrier2R
  endif

  aCarrierBrightR oscili 0.15 * iBrightness, kFreqR * 2
  aCarrierMixR = aCarrierR + aCarrierBrightR
  aNoiseR noise 0.08 * iNoise, 0
  aCarrierFinalR = aCarrierMixR + aNoiseR

  kF1R = iF1 * kFormantShift
  kF2R = iF2 * kFormantShift
  kF3R = iF3 * kFormantShift

  aFormant1R butterbp aCarrierFinalR, kF1R, iBW1
  aFormant1ScaledR = aFormant1R * iA1
  aFormant2R butterbp aCarrierFinalR, kF2R, iBW2
  aFormant2ScaledR = aFormant2R * iA2
  aFormant3R butterbp aCarrierFinalR, kF3R, iBW3
  aFormant3ScaledR = aFormant3R * iA3

  aMixR = (aFormant1ScaledR + aFormant2ScaledR + aFormant3ScaledR) * 0.6
  aOutR = aMixR * kEnv

  outs aOut, aOutR
endin
`;
  }

  protected getParametersForCsound(params: FormantFMParams): CsoundParameter[] {
    return [
      { channelName: 'baseFreq', value: params.baseFreq },
      { channelName: 'vowel', value: params.vowel },
      { channelName: 'vowelMorph', value: params.vowelMorph },
      { channelName: 'modulationType', value: params.modulationType },
      { channelName: 'modIndex', value: params.modIndex },
      { channelName: 'modRatio', value: params.modRatio },
      { channelName: 'attack', value: params.attack },
      { channelName: 'decay', value: params.decay },
      { channelName: 'sustain', value: params.sustain },
      { channelName: 'release', value: params.release },
      { channelName: 'brightness', value: params.brightness },
      { channelName: 'vibrato', value: params.vibrato },
      { channelName: 'vibratoRate', value: params.vibratoRate },
      { channelName: 'detune', value: params.detune },
      { channelName: 'noise', value: params.noise },
      { channelName: 'formantLFORate', value: params.formantLFORate },
      { channelName: 'formantLFODepth', value: params.formantLFODepth },
      { channelName: 'modIndexLFORate', value: params.modIndexLFORate },
      { channelName: 'modIndexLFODepth', value: params.modIndexLFODepth },
      { channelName: 'chaos', value: params.chaos },
    ];
  }

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

    const modulationRatios = [0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 7, 9];

    return {
      baseFreq,
      vowel: this.randomInt(0, 7) as VowelType,
      vowelMorph: this.randomRange(0.3, 0.7),
      modulationType: this.randomInt(0, 3) as ModulationType,
      modIndex: this.randomRange(1, 10),
      modRatio: this.randomChoice(modulationRatios),
      attack: this.randomRange(0.001, 0.15),
      decay: this.randomRange(0.05, 0.25),
      sustain: this.randomRange(0.3, 0.8),
      release: this.randomRange(0.1, 0.4),
      brightness: this.randomRange(0, 0.6),
      vibrato: this.randomRange(0, 0.5),
      vibratoRate: this.randomRange(3, 8),
      detune: this.randomRange(0.001, 0.01),
      noise: this.randomRange(0, 0.3),
      formantLFORate: this.randomRange(0.2, 6),
      formantLFODepth: this.randomRange(0, 0.8),
      modIndexLFORate: this.randomRange(0.5, 12),
      modIndexLFODepth: this.randomRange(0, 0.7),
      chaos: this.randomRange(0, 0.8),
    };
  }

  mutateParams(
    params: FormantFMParams,
    mutationAmount: number = 0.15,
    pitchLock?: PitchLock
  ): FormantFMParams {
    const baseFreq = pitchLock?.enabled ? pitchLock.frequency : params.baseFreq;
    const modulationRatios = [0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 7, 9];

    return {
      baseFreq,
      vowel: Math.random() < 0.1 ? (this.randomInt(0, 7) as VowelType) : params.vowel,
      vowelMorph: this.mutateValue(params.vowelMorph, mutationAmount, 0, 1),
      modulationType:
        Math.random() < 0.08
          ? (this.randomInt(0, 3) as ModulationType)
          : params.modulationType,
      modIndex: this.mutateValue(params.modIndex, mutationAmount, 0.5, 15),
      modRatio:
        Math.random() < 0.12
          ? this.randomChoice(modulationRatios)
          : params.modRatio,
      attack: this.mutateValue(params.attack, mutationAmount, 0.001, 0.25),
      decay: this.mutateValue(params.decay, mutationAmount, 0.02, 0.4),
      sustain: this.mutateValue(params.sustain, mutationAmount, 0.1, 0.9),
      release: this.mutateValue(params.release, mutationAmount, 0.05, 0.6),
      brightness: this.mutateValue(params.brightness, mutationAmount, 0, 1),
      vibrato: this.mutateValue(params.vibrato, mutationAmount, 0, 0.6),
      vibratoRate: this.mutateValue(params.vibratoRate, mutationAmount, 2, 10),
      detune: this.mutateValue(params.detune, mutationAmount, 0.0005, 0.02),
      noise: this.mutateValue(params.noise, mutationAmount, 0, 0.5),
      formantLFORate: this.mutateValue(params.formantLFORate, mutationAmount, 0.1, 8),
      formantLFODepth: this.mutateValue(params.formantLFODepth, mutationAmount, 0, 1),
      modIndexLFORate: this.mutateValue(params.modIndexLFORate, mutationAmount, 0.3, 15),
      modIndexLFODepth: this.mutateValue(params.modIndexLFODepth, mutationAmount, 0, 1),
      chaos: this.mutateValue(params.chaos, mutationAmount, 0, 1),
    };
  }
}
