import type { SynthEngine, PitchLock } from './base/SynthEngine';
// @ts-ignore
import { ZZFX } from 'zzfx';

interface ZzfxParams {
  volume: number;
  randomness: number;
  frequency: number;
  attack: number;
  sustain: number;
  release: number;
  shape: number;
  shapeCurve: number;
  slide: number;
  deltaSlide: number;
  pitchJump: number;
  pitchJumpTime: number;
  repeatTime: number;
  noise: number;
  modulation: number;
  bitCrush: number;
  delay: number;
  sustainVolume: number;
  decay: number;
  tremolo: number;
}

export class ZzfxEngine implements SynthEngine<ZzfxParams> {
  getName(): string {
    return 'ZzFX';
  }

  getDescription(): string {
    return 'Retro 8-bit sound effects generator with pitch bending, noise, and bit crushing';
  }

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

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

  generate(params: ZzfxParams, sampleRate: number, duration: number): [Float32Array, Float32Array] {
    // ZZFX uses 44100 sample rate internally
    const zzfxSampleRate = 44100;

    // Generate samples using ZZFX.buildSamples
    // Scale time parameters to seconds for ZZFX (it expects seconds, not ratios)
    const samples = ZZFX.buildSamples(
      params.volume,
      params.randomness,
      params.frequency,
      params.attack * duration,
      params.sustain * duration,
      params.release * duration,
      params.shape,
      params.shapeCurve,
      params.slide,
      params.deltaSlide,
      params.pitchJump,
      params.pitchJumpTime * duration,
      params.repeatTime * duration,
      params.noise,
      params.modulation,
      params.bitCrush,
      params.delay * duration,
      params.sustainVolume,
      params.decay * duration,
      params.tremolo
    );

    // Calculate the exact number of samples we need
    const numSamples = Math.floor(sampleRate * duration);
    const leftBuffer = new Float32Array(numSamples);
    const rightBuffer = new Float32Array(numSamples);

    // Handle sample rate conversion if needed
    const resampleRatio = sampleRate / zzfxSampleRate;

    // Fill buffers with resampled/stretched samples to match exact duration
    for (let i = 0; i < numSamples; i++) {
      // Calculate the source index with resampling
      const sourceIndex = Math.floor(i / resampleRatio);

      if (sourceIndex < samples.length) {
        // Linear interpolation for smoother resampling
        const fraction = (i / resampleRatio) - sourceIndex;
        const nextIndex = Math.min(sourceIndex + 1, samples.length - 1);

        const interpolatedSample = samples[sourceIndex] * (1 - fraction) +
                                   samples[nextIndex] * fraction;

        // Left channel
        leftBuffer[i] = interpolatedSample;

        // Right channel with slight delay for stereo width
        const stereoDelay = Math.floor(sampleRate * 0.001); // 1ms delay
        const delayedIndex = Math.max(0, sourceIndex - stereoDelay);

        if (delayedIndex < samples.length) {
          const delayedSample = samples[delayedIndex];
          rightBuffer[i] = interpolatedSample * 0.8 + delayedSample * 0.2;
        } else {
          rightBuffer[i] = interpolatedSample;
        }
      } else {
        // Fill with silence if we've run out of samples
        leftBuffer[i] = 0;
        rightBuffer[i] = 0;
      }
    }

    // Normalize the output with moderate gain
    let peak = 0;
    for (let i = 0; i < numSamples; i++) {
      peak = Math.max(peak, Math.abs(leftBuffer[i]), Math.abs(rightBuffer[i]));
    }

    if (peak > 0.001) {
      const normalizationGain = 0.7 / peak;
      for (let i = 0; i < numSamples; i++) {
        leftBuffer[i] *= normalizationGain;
        rightBuffer[i] *= normalizationGain;
      }
    }

    return [leftBuffer, rightBuffer];
  }

  randomParams(pitchLock?: PitchLock): ZzfxParams {
    const preset = Math.floor(Math.random() * 20);

    const getFrequency = (min: number, max: number) =>
      pitchLock?.enabled ? pitchLock.frequency : this.randomRange(min, max);

    switch (preset) {
      case 0: // Clean tones
        return {
          volume: 1,
          randomness: this.randomRange(0, 0.05),
          frequency: getFrequency(200, 800),
          attack: this.randomRange(0, 0.02),
          sustain: this.randomRange(0.2, 0.5),
          release: this.randomRange(0.1, 0.3),
          shape: this.randomInt(0, 2),
          shapeCurve: this.randomRange(0.8, 1.2),
          slide: this.randomRange(-0.1, 0.1),
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(0, 2),
          bitCrush: 0,
          delay: this.randomRange(0, 0.05),
          sustainVolume: 1,
          decay: this.randomRange(0, 0.05),
          tremolo: this.randomRange(0, 0.1),
        };

      case 1: // Pitched slides
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(400, 1200),
          attack: this.randomRange(0, 0.01),
          sustain: this.randomRange(0.15, 0.35),
          release: this.randomRange(0.05, 0.2),
          shape: this.randomInt(0, 3),
          shapeCurve: this.randomRange(0.7, 1.5),
          slide: this.randomRange(-0.4, 0.4),
          deltaSlide: this.randomRange(-0.05, 0.05),
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(0, 3),
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: this.randomRange(0, 0.08),
          tremolo: 0,
        };

      case 2: // Pitch jumps
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(300, 1000),
          attack: 0,
          sustain: this.randomRange(0.2, 0.4),
          release: this.randomRange(0.05, 0.15),
          shape: this.randomInt(0, 3),
          shapeCurve: 1,
          slide: 0,
          deltaSlide: 0,
          pitchJump: this.randomRange(-200, 200),
          pitchJumpTime: this.randomRange(0.2, 0.8),
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(0, 2),
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 3: // Textured (light noise)
        return {
          volume: 1,
          randomness: this.randomRange(0, 0.1),
          frequency: getFrequency(150, 600),
          attack: this.randomRange(0, 0.03),
          sustain: this.randomRange(0.15, 0.35),
          release: this.randomRange(0.05, 0.2),
          shape: this.randomInt(0, 4),
          shapeCurve: this.randomRange(0.7, 1.5),
          slide: this.randomRange(-0.15, 0.15),
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: this.randomRange(0, 0.15),
          modulation: this.randomRange(0, 4),
          bitCrush: 0,
          delay: this.randomRange(0, 0.08),
          sustainVolume: 1,
          decay: this.randomRange(0, 0.1),
          tremolo: this.randomRange(0, 0.2),
        };

      case 4: // Retro game sounds
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(200, 1500),
          attack: 0,
          sustain: this.randomRange(0.1, 0.25),
          release: this.randomRange(0.02, 0.1),
          shape: this.randomInt(0, 4),
          shapeCurve: 1,
          slide: this.randomRange(-0.5, 0.3),
          deltaSlide: this.randomRange(-0.08, 0.05),
          pitchJump: Math.random() > 0.7 ? this.randomRange(-150, 150) : 0,
          pitchJumpTime: this.randomRange(0.3, 0.7),
          repeatTime: Math.random() > 0.6 ? this.randomRange(0.05, 0.15) : 0,
          noise: 0,
          modulation: this.randomRange(0, 5),
          bitCrush: Math.random() > 0.7 ? this.randomRange(1, 3) : 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 5: // Bass tones
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(40, 200),
          attack: this.randomRange(0, 0.02),
          sustain: this.randomRange(0.25, 0.5),
          release: this.randomRange(0.1, 0.25),
          shape: this.randomInt(0, 2),
          shapeCurve: this.randomRange(0.8, 1.3),
          slide: this.randomRange(-0.15, 0),
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(0, 1),
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: this.randomRange(0, 0.05),
          tremolo: 0,
        };

      case 6: // Melodic with vibrato
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(300, 1200),
          attack: this.randomRange(0, 0.03),
          sustain: this.randomRange(0.2, 0.4),
          release: this.randomRange(0.08, 0.2),
          shape: this.randomInt(0, 3),
          shapeCurve: this.randomRange(0.8, 1.2),
          slide: 0,
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(5, 10),
          bitCrush: 0,
          delay: this.randomRange(0, 0.06),
          sustainVolume: 1,
          decay: this.randomRange(0, 0.06),
          tremolo: this.randomRange(0.1, 0.3),
        };

      case 7: // Laser/zap sounds
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(800, 2000),
          attack: 0,
          sustain: this.randomRange(0.05, 0.15),
          release: this.randomRange(0.02, 0.08),
          shape: this.randomInt(1, 3),
          shapeCurve: 1,
          slide: this.randomRange(-0.6, -0.3),
          deltaSlide: this.randomRange(-0.05, 0),
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(0, 2),
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 8: // Explosion/impact
        return {
          volume: 1,
          randomness: this.randomRange(0.1, 0.15),
          frequency: getFrequency(50, 150),
          attack: 0,
          sustain: this.randomRange(0.15, 0.3),
          release: this.randomRange(0.15, 0.35),
          shape: this.randomInt(0, 1),
          shapeCurve: this.randomRange(0.5, 0.8),
          slide: this.randomRange(-0.4, -0.2),
          deltaSlide: this.randomRange(-0.05, 0),
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: this.randomRange(0.1, 0.2),
          modulation: 0,
          bitCrush: Math.random() > 0.5 ? this.randomRange(1, 2) : 0,
          delay: 0,
          sustainVolume: 1,
          decay: this.randomRange(0.05, 0.1),
          tremolo: 0,
        };

      case 9: // Coin/pickup
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(500, 1000),
          attack: 0,
          sustain: this.randomRange(0.05, 0.1),
          release: this.randomRange(0.1, 0.2),
          shape: this.randomInt(0, 2),
          shapeCurve: 1.2,
          slide: this.randomRange(0.2, 0.4),
          deltaSlide: 0,
          pitchJump: this.randomRange(100, 200),
          pitchJumpTime: this.randomRange(0.3, 0.6),
          repeatTime: 0,
          noise: 0,
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 10: // Blip/beep (UI)
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(600, 1400),
          attack: 0,
          sustain: this.randomRange(0.03, 0.08),
          release: this.randomRange(0.02, 0.05),
          shape: this.randomInt(0, 2),
          shapeCurve: 1,
          slide: 0,
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 11: // Power-up
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(200, 500),
          attack: 0,
          sustain: this.randomRange(0.2, 0.35),
          release: this.randomRange(0.1, 0.2),
          shape: this.randomInt(0, 3),
          shapeCurve: 1.5,
          slide: this.randomRange(0.3, 0.6),
          deltaSlide: this.randomRange(0.02, 0.05),
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(3, 6),
          bitCrush: 0,
          delay: this.randomRange(0.02, 0.05),
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 12: // Jump
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(300, 600),
          attack: 0,
          sustain: this.randomRange(0.08, 0.15),
          release: this.randomRange(0.05, 0.12),
          shape: this.randomInt(0, 2),
          shapeCurve: 1,
          slide: this.randomRange(0.15, 0.35),
          deltaSlide: this.randomRange(-0.03, -0.01),
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 13: // Alarm/warning
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(400, 800),
          attack: 0,
          sustain: this.randomRange(0.3, 0.5),
          release: this.randomRange(0.05, 0.1),
          shape: 1,
          shapeCurve: 1,
          slide: 0,
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: this.randomRange(0.1, 0.15),
          noise: 0,
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: this.randomRange(0.2, 0.4),
        };

      case 14: // Wobble bass
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(60, 150),
          attack: this.randomRange(0, 0.02),
          sustain: this.randomRange(0.3, 0.5),
          release: this.randomRange(0.1, 0.2),
          shape: this.randomInt(0, 2),
          shapeCurve: this.randomRange(0.8, 1.2),
          slide: 0,
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(8, 12),
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 15: // Wind-down
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(400, 800),
          attack: 0,
          sustain: this.randomRange(0.25, 0.4),
          release: this.randomRange(0.15, 0.3),
          shape: this.randomInt(1, 3),
          shapeCurve: 0.7,
          slide: this.randomRange(-0.5, -0.3),
          deltaSlide: this.randomRange(-0.08, -0.03),
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: 0,
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: this.randomRange(0.05, 0.1),
          tremolo: 0,
        };

      case 16: // Hit/damage
        return {
          volume: 1,
          randomness: this.randomRange(0.05, 0.1),
          frequency: getFrequency(200, 400),
          attack: 0,
          sustain: this.randomRange(0.05, 0.12),
          release: this.randomRange(0.05, 0.15),
          shape: this.randomInt(0, 3),
          shapeCurve: 0.8,
          slide: this.randomRange(-0.4, -0.2),
          deltaSlide: 0,
          pitchJump: this.randomRange(-100, -50),
          pitchJumpTime: this.randomRange(0.4, 0.7),
          repeatTime: 0,
          noise: this.randomRange(0.05, 0.12),
          modulation: 0,
          bitCrush: Math.random() > 0.6 ? this.randomRange(1, 2) : 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 17: // Arpeggio
        return {
          volume: 1,
          randomness: 0,
          frequency: getFrequency(400, 1000),
          attack: 0,
          sustain: this.randomRange(0.2, 0.35),
          release: this.randomRange(0.05, 0.12),
          shape: this.randomInt(0, 2),
          shapeCurve: 1,
          slide: 0,
          deltaSlide: 0,
          pitchJump: this.randomRange(100, 300),
          pitchJumpTime: this.randomRange(0.15, 0.4),
          repeatTime: this.randomRange(0.08, 0.12),
          noise: 0,
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 18: // Percussion/drums
        return {
          volume: 1,
          randomness: this.randomRange(0.08, 0.15),
          frequency: getFrequency(80, 250),
          attack: 0,
          sustain: this.randomRange(0.03, 0.08),
          release: this.randomRange(0.05, 0.15),
          shape: this.randomInt(0, 1),
          shapeCurve: 0.6,
          slide: this.randomRange(-0.3, -0.1),
          deltaSlide: 0,
          pitchJump: 0,
          pitchJumpTime: 0,
          repeatTime: 0,
          noise: this.randomRange(0.08, 0.15),
          modulation: 0,
          bitCrush: 0,
          delay: 0,
          sustainVolume: 1,
          decay: 0,
          tremolo: 0,
        };

      case 19: // Sparkle/magic
        return {
          volume: 1,
          randomness: this.randomRange(0, 0.05),
          frequency: getFrequency(1000, 2000),
          attack: 0,
          sustain: this.randomRange(0.15, 0.3),
          release: this.randomRange(0.15, 0.3),
          shape: this.randomInt(0, 2),
          shapeCurve: 1.5,
          slide: this.randomRange(-0.2, 0.2),
          deltaSlide: this.randomRange(-0.03, 0.03),
          pitchJump: Math.random() > 0.5 ? this.randomRange(50, 150) : 0,
          pitchJumpTime: this.randomRange(0.3, 0.6),
          repeatTime: 0,
          noise: 0,
          modulation: this.randomRange(3, 7),
          bitCrush: 0,
          delay: this.randomRange(0.03, 0.08),
          sustainVolume: 1,
          decay: this.randomRange(0.03, 0.08),
          tremolo: this.randomRange(0.05, 0.15),
        };

      default: // Balanced mix
        return {
          volume: 1,
          randomness: this.randomRange(0, 0.05),
          frequency: getFrequency(200, 1200),
          attack: this.randomRange(0, 0.03),
          sustain: this.randomRange(0.15, 0.4),
          release: this.randomRange(0.05, 0.2),
          shape: this.randomInt(0, 4),
          shapeCurve: this.randomRange(0.7, 1.5),
          slide: this.randomRange(-0.2, 0.2),
          deltaSlide: this.randomRange(-0.05, 0.05),
          pitchJump: Math.random() > 0.8 ? this.randomRange(-100, 100) : 0,
          pitchJumpTime: this.randomRange(0, 0.5),
          repeatTime: 0,
          noise: this.randomRange(0, 0.1),
          modulation: this.randomRange(0, 4),
          bitCrush: Math.random() > 0.85 ? this.randomRange(1, 2) : 0,
          delay: this.randomRange(0, 0.05),
          sustainVolume: 1,
          decay: this.randomRange(0, 0.08),
          tremolo: this.randomRange(0, 0.15),
        };
    }
  }

  mutateParams(params: ZzfxParams, mutationAmount: number = 0.15, pitchLock?: PitchLock): ZzfxParams {
    const frequency = pitchLock?.enabled
      ? pitchLock.frequency
      : this.mutateValue(params.frequency, mutationAmount * 2, 40, 1500);

    return {
      volume: 1,
      randomness: this.mutateValue(params.randomness, mutationAmount, 0, 0.1),
      frequency,
      attack: this.mutateValue(params.attack, mutationAmount, 0, 0.03),
      sustain: this.mutateValue(params.sustain, mutationAmount, 0.1, 0.5),
      release: this.mutateValue(params.release, mutationAmount, 0.02, 0.3),
      shape: Math.random() < 0.1 ? this.randomInt(0, 4) : params.shape,
      shapeCurve: this.mutateValue(params.shapeCurve, mutationAmount, 0.7, 1.5),
      slide: this.mutateValue(params.slide, mutationAmount, -0.5, 0.4),
      deltaSlide: this.mutateValue(params.deltaSlide, mutationAmount, -0.08, 0.05),
      pitchJump: this.mutateValue(params.pitchJump, mutationAmount * 3, -200, 200),
      pitchJumpTime: this.mutateValue(params.pitchJumpTime, mutationAmount, 0, 1),
      repeatTime: this.mutateValue(params.repeatTime, mutationAmount, 0, 0.15),
      noise: this.mutateValue(params.noise, mutationAmount, 0, 0.15),
      modulation: this.mutateValue(params.modulation, mutationAmount, 0, 10),
      bitCrush: this.mutateValue(params.bitCrush, mutationAmount, 0, 3),
      delay: this.mutateValue(params.delay, mutationAmount, 0, 0.08),
      sustainVolume: 1,
      decay: this.mutateValue(params.decay, mutationAmount, 0, 0.1),
      tremolo: this.mutateValue(params.tremolo, mutationAmount, 0, 0.3),
    };
  }

  private randomRange(min: number, max: number): number {
    return min + Math.random() * (max - min);
  }

  private randomInt(min: number, max: number): number {
    return Math.floor(this.randomRange(min, max + 1));
  }

  private mutateValue(value: number, amount: number, min: number, max: number): number {
    const variation = (max - min) * amount * (Math.random() * 2 - 1);
    return Math.max(min, Math.min(max, value + variation));
  }
}