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

interface MassiveAdditiveParams {
  baseFreq: number;
  numPartials: number;
  spectrumShape: number;
  harmonicSpread: number;
  inharmonicity: number;
  attack: number;
  decay: number;
  sustain: number;
  release: number;
  ampLFORate: number;
  ampLFODepth: number;
  ampLFOPhaseSpread: number;
  freqLFORate: number;
  freqLFODepth: number;
  partialDecayRate: number;
  partialAttackSpread: number;
  oddEvenBalance: number;
  shimmer: number;
  shimmerRate: number;
  stereoSpread: number;
  brightness: number;
  chaos: number;
}

export class MassiveAdditive extends CsoundEngine<MassiveAdditiveParams> {
  getName(): string {
    return 'Spectral Add';
  }

  getDescription(): string {
    return 'Spectral additive synthesis with octave doubling, micro-detuned beating, and evolving harmonic content';
  }

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

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

  protected getOrchestra(): string {
    return `
; Function tables for sine wave
gisine ftgen 0, 0, 16384, 10, 1

instr 1
  iBaseFreq chnget "baseFreq"
  iNumPartials chnget "numPartials"
  iSpectrumShape chnget "spectrumShape"
  iHarmonicSpread chnget "harmonicSpread"
  iInharmonicity chnget "inharmonicity"
  iAttack chnget "attack"
  iDecay chnget "decay"
  iSustain chnget "sustain"
  iRelease chnget "release"
  iAmpLFORate chnget "ampLFORate"
  iAmpLFODepth chnget "ampLFODepth"
  iAmpLFOPhaseSpread chnget "ampLFOPhaseSpread"
  iFreqLFORate chnget "freqLFORate"
  iFreqLFODepth chnget "freqLFODepth"
  iPartialDecayRate chnget "partialDecayRate"
  iPartialAttackSpread chnget "partialAttackSpread"
  iOddEvenBalance chnget "oddEvenBalance"
  iShimmer chnget "shimmer"
  iShimmerRate chnget "shimmerRate"
  iStereoSpread chnget "stereoSpread"
  iBrightness chnget "brightness"
  iChaos chnget "chaos"

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

  ; Main envelope
  kEnv madsr iAttackTime, iDecayTime, iSustain, iReleaseTime

  ; Global modulation LFOs
  kGlobalAmpLFO oscili 1, iAmpLFORate * 0.5
  kShimmerLFO oscili 1, iShimmerRate
  kFreqLFO oscili iFreqLFODepth * 0.02, iFreqLFORate

  ; Chaos LFOs
  kChaosLFO1 oscili 1, iAmpLFORate * 1.618
  kChaosLFO2 oscili 1, iAmpLFORate * 2.414

  ; Create frequency and amplitude tables dynamically
  iPartialCount = min(iNumPartials, 64)

  ; Generate tables for partial frequencies and amplitudes
  gifreq ftgen 0, 0, 128, -2, 0
  giamp ftgen 0, 0, 128, -2, 0
  gifreq2 ftgen 0, 0, 128, -2, 0
  giamp2 ftgen 0, 0, 128, -2, 0

  iPartialIndex = 0
  loop_setup:
    iN = iPartialIndex + 1

    ; Calculate frequency ratio with harmonicity and inharmonicity
    iHarmonic = iN * iHarmonicSpread
    iInharmonicShift = iInharmonicity * iN * iN * 0.001
    iFreqRatio = iHarmonic * (1 + iInharmonicShift)

    ; Calculate amplitude based on spectrum shape
    iAmpFalloff = 1 / pow(iN, 1 + iSpectrumShape * 2)

    ; Brightness boost for higher partials
    iBrightnessBoost = 1 + (iBrightness * (iN / iPartialCount) * 2)

    ; Odd/even balance
    iOddEvenFactor = 1
    if (iN % 2) == 0 then
      iOddEvenFactor = iOddEvenBalance
    else
      iOddEvenFactor = 2 - iOddEvenBalance
    endif

    iAmp = iAmpFalloff * iBrightnessBoost * iOddEvenFactor

    ; Create alternate partial distribution for morphing
    iFreqRatio2 = iFreqRatio * (1 + (iChaos * 0.05 * sin(iN * 0.7)))
    iAmp2 = iAmp * (1 - (iN / iPartialCount) * 0.3)

    ; Write to tables
    tableiw iFreqRatio, iPartialIndex, gifreq
    tableiw iAmp, iPartialIndex, giamp
    tableiw iFreqRatio2, iPartialIndex, gifreq2
    tableiw iAmp2, iPartialIndex, giamp2

    iPartialIndex = iPartialIndex + 1
    if iPartialIndex < iPartialCount goto loop_setup

  ; Generate additive synthesis with heavy modulation and octave doubling

  ; Slow modulation LFOs for spectral evolution
  kLFO1 oscili 1, iAmpLFORate * 0.53
  kLFO2 oscili 1, iAmpLFORate * 0.89
  kLFO3 oscili 1, iAmpLFORate * 1.37
  kShimmerLFO1 oscili 1, iShimmerRate * 0.67
  kShimmerLFO2 oscili 1, iShimmerRate * 1.13

  ; Very slow spectral morphing
  kSpectralMorph oscili 1, iAmpLFORate * 0.19
  kMorphAmount = (kSpectralMorph + 1) * 0.5

  ; Minimal frequency wobble for organic feel
  kFreqWobble = kFreqLFO * 0.5

  ; Micro-detuning for beating (0.1 to 0.5 Hz beating)
  iMicroDetune = 0.0003 + (iChaos * 0.0005)

  ; === FUNDAMENTAL OCTAVE ===
  ; Main voice at fundamental frequency
  kcps = iBaseFreq * (1 + kFreqWobble)
  kAmpMain = kEnv * (0.6 + kLFO1 * iAmpLFODepth * 0.2)
  aMainVoice adsynt2 kAmpMain, kcps, gisine, gifreq, giamp, iPartialCount

  ; Slightly detuned fundamental for beating
  kcpsDetune = iBaseFreq * (1 + iMicroDetune) * (1 + kFreqWobble * 0.93)
  kAmpDetune = kEnv * (0.5 + kLFO2 * iAmpLFODepth * 0.25)
  aDetuneVoice adsynt2 kAmpDetune, kcpsDetune, gisine, gifreq, giamp, iPartialCount, 0.25

  ; Morphing spectral variant
  kAmpMorph = kEnv * kMorphAmount * (0.4 + kShimmerLFO1 * iShimmer * 0.3)
  aMorphVoice adsynt2 kAmpMorph, kcps, gisine, gifreq2, giamp2, iPartialCount, 0.5

  ; === OCTAVE UP ===
  ; One octave higher with micro-detune for beating
  kcpsOctUp = (iBaseFreq * 2) * (1 + kFreqWobble * 1.07)
  kAmpOctUp = kEnv * (0.35 + kLFO3 * iAmpLFODepth * 0.2 + kShimmerLFO2 * iShimmer * 0.25)
  aOctaveUp adsynt2 kAmpOctUp, kcpsOctUp, gisine, gifreq, giamp, iPartialCount, 0.33

  ; Detuned octave up for complex beating
  kcpsOctUpDetune = (iBaseFreq * 2) * (1 - iMicroDetune * 0.8) * (1 + kFreqWobble * 1.11)
  kAmpOctUpDetune = kEnv * (0.3 + kLFO1 * iAmpLFODepth * 0.25)
  aOctaveUpDetune adsynt2 kAmpOctUpDetune, kcpsOctUpDetune, gisine, gifreq, giamp, iPartialCount, 0.67

  ; === OCTAVE DOWN ===
  ; One octave lower with micro-detune
  kcpsOctDown = (iBaseFreq * 0.5) * (1 + kFreqWobble * 0.97)
  kAmpOctDown = kEnv * (0.4 + kShimmerLFO1 * iShimmer * 0.3)
  aOctaveDown adsynt2 kAmpOctDown, kcpsOctDown, gisine, gifreq, giamp, iPartialCount, 0.17

  ; Detuned octave down for sub-harmonic beating
  kcpsOctDownDetune = (iBaseFreq * 0.5) * (1 + iMicroDetune * 1.2) * (1 + kFreqWobble * 1.03)
  kAmpOctDownDetune = kEnv * (0.35 + kLFO2 * iAmpLFODepth * 0.2)
  aOctaveDownDetune adsynt2 kAmpOctDownDetune, kcpsOctDownDetune, gisine, gifreq2, giamp, iPartialCount, 0.83

  ; === STEREO MIXING ===
  ; Left channel: emphasize fundamental and octave down
  aOutL = aMainVoice * 0.7 + aDetuneVoice * 0.6 + aMorphVoice * 0.5
  aOutL = aOutL + aOctaveUp * 0.4 + aOctaveUpDetune * 0.35
  aOutL = aOutL + aOctaveDown * 0.55 + aOctaveDownDetune * 0.45

  ; Right channel: emphasize octaves with different balance
  aOutR = aMainVoice * 0.65 + aDetuneVoice * 0.55 + aMorphVoice * 0.6
  aOutR = aOutR + aOctaveUp * 0.5 + aOctaveUpDetune * 0.4
  aOutR = aOutR + aOctaveDown * 0.45 + aOctaveDownDetune * 0.5

  ; Subtle stereo width from chaos
  kStereoMod = kChaosLFO1 * iChaos * 0.1
  aOutL = aOutL * (1 - kStereoMod * iStereoSpread)
  aOutR = aOutR * (1 + kStereoMod * iStereoSpread)

  ; Normalize to prevent clipping
  aOutL = aOutL * 0.28
  aOutR = aOutR * 0.28

  outs aOutL, aOutR
endin
`;
  }

  protected getParametersForCsound(params: MassiveAdditiveParams): CsoundParameter[] {
    return [
      { channelName: 'baseFreq', value: params.baseFreq },
      { channelName: 'numPartials', value: params.numPartials },
      { channelName: 'spectrumShape', value: params.spectrumShape },
      { channelName: 'harmonicSpread', value: params.harmonicSpread },
      { channelName: 'inharmonicity', value: params.inharmonicity },
      { channelName: 'attack', value: params.attack },
      { channelName: 'decay', value: params.decay },
      { channelName: 'sustain', value: params.sustain },
      { channelName: 'release', value: params.release },
      { channelName: 'ampLFORate', value: params.ampLFORate },
      { channelName: 'ampLFODepth', value: params.ampLFODepth },
      { channelName: 'ampLFOPhaseSpread', value: params.ampLFOPhaseSpread },
      { channelName: 'freqLFORate', value: params.freqLFORate },
      { channelName: 'freqLFODepth', value: params.freqLFODepth },
      { channelName: 'partialDecayRate', value: params.partialDecayRate },
      { channelName: 'partialAttackSpread', value: params.partialAttackSpread },
      { channelName: 'oddEvenBalance', value: params.oddEvenBalance },
      { channelName: 'shimmer', value: params.shimmer },
      { channelName: 'shimmerRate', value: params.shimmerRate },
      { channelName: 'stereoSpread', value: params.stereoSpread },
      { channelName: 'brightness', value: params.brightness },
      { channelName: 'chaos', value: params.chaos },
    ];
  }

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

    return {
      baseFreq,
      numPartials: this.randomInt(32, 64),
      spectrumShape: this.randomRange(0.3, 0.7),
      harmonicSpread: this.randomChoice([1, 1.5, 2, 3]),
      inharmonicity: this.randomRange(0, 0.5),
      attack: this.randomRange(0.05, 0.3),
      decay: this.randomRange(0.15, 0.5),
      sustain: this.randomRange(0.5, 0.9),
      release: this.randomRange(0.2, 0.6),
      ampLFORate: this.randomRange(0.2, 2),
      ampLFODepth: this.randomRange(0.3, 0.8),
      ampLFOPhaseSpread: this.randomRange(0.5, 1),
      freqLFORate: this.randomRange(0.1, 1.5),
      freqLFODepth: this.randomRange(0.05, 0.3),
      partialDecayRate: this.randomRange(0.3, 0.8),
      partialAttackSpread: this.randomRange(0.1, 0.5),
      oddEvenBalance: this.randomRange(0.5, 1.5),
      shimmer: this.randomRange(0.3, 0.9),
      shimmerRate: this.randomRange(0.05, 0.8),
      stereoSpread: this.randomRange(0.4, 1),
      brightness: this.randomRange(0.3, 0.9),
      chaos: this.randomRange(0.1, 0.7),
    };
  }

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

    return {
      baseFreq,
      numPartials:
        Math.random() < 0.15
          ? this.randomInt(16, 64)
          : Math.round(this.mutateValue(params.numPartials, mutationAmount, 16, 64)),
      spectrumShape: this.mutateValue(params.spectrumShape, mutationAmount, 0, 1),
      harmonicSpread:
        Math.random() < 0.1
          ? this.randomChoice([0.5, 1, 1.5, 2])
          : params.harmonicSpread,
      inharmonicity: this.mutateValue(params.inharmonicity, mutationAmount, 0, 0.5),
      attack: this.mutateValue(params.attack, mutationAmount, 0.001, 0.4),
      decay: this.mutateValue(params.decay, mutationAmount, 0.05, 0.6),
      sustain: this.mutateValue(params.sustain, mutationAmount, 0.2, 0.9),
      release: this.mutateValue(params.release, mutationAmount, 0.05, 0.8),
      ampLFORate: this.mutateValue(params.ampLFORate, mutationAmount, 0.1, 6),
      ampLFODepth: this.mutateValue(params.ampLFODepth, mutationAmount, 0, 1),
      ampLFOPhaseSpread: this.mutateValue(params.ampLFOPhaseSpread, mutationAmount, 0, 1),
      freqLFORate: this.mutateValue(params.freqLFORate, mutationAmount, 0.1, 5),
      freqLFODepth: this.mutateValue(params.freqLFODepth, mutationAmount, 0, 0.8),
      partialDecayRate: this.mutateValue(params.partialDecayRate, mutationAmount, 0, 1),
      partialAttackSpread: this.mutateValue(params.partialAttackSpread, mutationAmount, 0, 0.8),
      oddEvenBalance: this.mutateValue(params.oddEvenBalance, mutationAmount, 0.2, 1.8),
      shimmer: this.mutateValue(params.shimmer, mutationAmount, 0, 1),
      shimmerRate: this.mutateValue(params.shimmerRate, mutationAmount, 0.05, 2),
      stereoSpread: this.mutateValue(params.stereoSpread, mutationAmount, 0, 1),
      brightness: this.mutateValue(params.brightness, mutationAmount, 0, 1),
      chaos: this.mutateValue(params.chaos, mutationAmount, 0, 0.8),
    };
  }
}
