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

interface InputParams {
  recorded: boolean;
}

export class Input implements SynthEngine<InputParams> {
  private leftChannel: Float32Array | null = null;
  private rightChannel: Float32Array | null = null;
  private recordedSampleRate: number = 44100;

  getName(): string {
    return 'Input';
  }

  getDescription(): string {
    return 'Record audio from your microphone or default input device';
  }

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

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

  async record(duration: number): Promise<void> {
    const stream = await navigator.mediaDevices.getUserMedia({
      audio: {
        echoCancellation: false,
        noiseSuppression: false,
        autoGainControl: false
      }
    });

    const audioContext = new AudioContext();
    this.recordedSampleRate = audioContext.sampleRate;

    const source = audioContext.createMediaStreamSource(stream);
    const destination = audioContext.createMediaStreamDestination();
    source.connect(destination);

    const mediaRecorder = new MediaRecorder(destination.stream);
    const chunks: Blob[] = [];

    mediaRecorder.ondataavailable = (e) => {
      if (e.data.size > 0) {
        chunks.push(e.data);
      }
    };

    const recordingComplete = new Promise<void>((resolve) => {
      mediaRecorder.onstop = async () => {
        const blob = new Blob(chunks, { type: 'audio/webm' });
        const arrayBuffer = await blob.arrayBuffer();

        try {
          const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

          if (audioBuffer.numberOfChannels === 1) {
            this.leftChannel = audioBuffer.getChannelData(0);
            this.rightChannel = audioBuffer.getChannelData(0);
          } else {
            this.leftChannel = audioBuffer.getChannelData(0);
            this.rightChannel = audioBuffer.getChannelData(1);
          }
        } catch (error) {
          throw new Error(`Failed to decode recorded audio: ${error}`);
        } finally {
          stream.getTracks().forEach(track => track.stop());
          await audioContext.close();
        }

        resolve();
      };
    });

    mediaRecorder.start();

    setTimeout(() => {
      if (mediaRecorder.state === 'recording') {
        mediaRecorder.stop();
      }
    }, duration * 1000);

    await recordingComplete;
  }

  generate(_params: InputParams, sampleRate: number, _duration: number): [Float32Array, Float32Array] {
    if (!this.leftChannel || !this.rightChannel) {
      throw new Error('No audio recorded. Please record audio first.');
    }

    if (this.recordedSampleRate === sampleRate) {
      return [this.leftChannel.slice(), this.rightChannel.slice()];
    }

    const resampleRatio = sampleRate / this.recordedSampleRate;
    const newLength = Math.floor(this.leftChannel.length * resampleRatio);
    const leftResampled = new Float32Array(newLength);
    const rightResampled = new Float32Array(newLength);

    for (let i = 0; i < newLength; i++) {
      const sourceIndex = i / resampleRatio;
      const index0 = Math.floor(sourceIndex);
      const index1 = Math.min(index0 + 1, this.leftChannel.length - 1);
      const fraction = sourceIndex - index0;

      leftResampled[i] = this.leftChannel[index0] * (1 - fraction) + this.leftChannel[index1] * fraction;
      rightResampled[i] = this.rightChannel[index0] * (1 - fraction) + this.rightChannel[index1] * fraction;
    }

    return [leftResampled, rightResampled];
  }

  randomParams(_pitchLock?: PitchLock): InputParams {
    return { recorded: this.leftChannel !== null && this.rightChannel !== null };
  }

  mutateParams(params: InputParams, _mutationAmount?: number, _pitchLock?: PitchLock): InputParams {
    return params;
  }
}
