/**
 * Audio Stream Capture Module
 * Captures and processes audio tracks from WebRTC peer connections
 * Provides real-time audio frames for downstream analysis
 */

import { EventEmitter } from 'events';

export type AudioCaptureState = 'idle' | 'capturing' | 'paused' | 'error';

export interface AudioFrame {
  timestamp: number;
  samples: Float32Array;
  sampleRate: number;
  channelCount: number;
  duration: number;
  rmsLevel: number;
}

export interface AudioCaptureOptions {
  sampleRate?: number;
  channelCount?: number;
  frameSize?: number;
  enableEchoCancellation?: boolean;
  enableNoiseSuppression?: boolean;
  enableAutoGainControl?: boolean;
}

export interface AudioMetrics {
  peakLevel: number;
  averageLevel: number;
  rmsLevel: number;
  silenceRatio: number;
  clipCount: number;
  totalSamples: number;
  duration: number;
}

export type AudioEventType =
  | 'frame'
  | 'state:changed'
  | 'metrics:update'
  | 'error'
  | 'silence:detected'
  | 'clip:detected';

export interface AudioEvents {
  'frame': (frame: AudioFrame) => void;
  'state:changed': (state: AudioCaptureState) => void;
  'metrics:update': (metrics: AudioMetrics) => void;
  'error': (error: Error) => void;
  'silence:detected': (duration: number) => void;
  'clip:detected': (peakLevel: number) => void;
}

const DEFAULT_OPTIONS: Required<AudioCaptureOptions> = {
  sampleRate: 16000,
  channelCount: 1,
  frameSize: 1024,
  enableEchoCancellation: true,
  enableNoiseSuppression: true,
  enableAutoGainControl: true,
};

const SILENCE_THRESHOLD = 0.01;
const CLIP_THRESHOLD = 0.95;

export class AudioStreamCapture extends EventEmitter {
  private state: AudioCaptureState = 'idle';
  private options: Required<AudioCaptureOptions>;
  private audioContext: AudioContext | null = null;
  private sourceNode: MediaStreamAudioSourceNode | null = null;
  private scriptProcessor: ScriptProcessorNode | null = null;
  private stream: MediaStream | null = null;
  private metrics: AudioMetrics;
  private startTime: number = 0;
  private silenceStart: number = 0;
  private frameCount: number = 0;

  constructor(options: AudioCaptureOptions = {}) {
    super();
    this.options = { ...DEFAULT_OPTIONS, ...options };
    this.metrics = {
      peakLevel: 0,
      averageLevel: 0,
      rmsLevel: 0,
      silenceRatio: 0,
      clipCount: 0,
      totalSamples: 0,
      duration: 0,
    };
  }

  async startFromMicrophone(): Promise<AudioFrame[]> {
    try {
      this.stream = await navigator.mediaDevices.getUserMedia({
        audio: {
          sampleRate: this.options.sampleRate,
          channelCount: this.options.channelCount,
          echoCancellation: this.options.enableEchoCancellation,
          noiseSuppression: this.options.enableNoiseSuppression,
          autoGainControl: this.options.enableAutoGainControl,
        },
      });

      return this.setupProcessingPipeline();
    } catch (error) {
      this.updateState('error');
      this.emit('error', error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async startFromStream(mediaStream: MediaStream): Promise<AudioFrame[]> {
    const audioTracks = mediaStream.getAudioTracks();
    if (audioTracks.length === 0) {
      throw new Error('No audio tracks found in MediaStream');
    }

    this.stream = new MediaStream(audioTracks);
    return this.setupProcessingPipeline();
  }

  private setupProcessingPipeline(): AudioFrame[] {
    this.audioContext = new AudioContext({
      sampleRate: this.options.sampleRate,
    });

    this.sourceNode = this.audioContext.createMediaStreamSource(this.stream!);
    this.scriptProcessor = this.audioContext.createScriptProcessor(
      this.options.frameSize,
      this.options.channelCount,
      this.options.channelCount
    );

    this.sourceNode.connect(this.scriptProcessor);
    this.scriptProcessor.connect(this.audioContext.destination);

    this.scriptProcessor.onaudioprocess = (event: AudioProcessingEvent) => {
      const inputBuffer = event.inputBuffer;
      const outputBuffer = event.outputBuffer;
      const now = Date.now();

      if (!this.startTime) {
        this.startTime = now;
      }

      const frames = this.processAudioBuffer(inputBuffer, now);
      frames.forEach(frame => this.emit('frame', frame));

      this.metrics = this.updateMetrics(inputBuffer);
      this.emit('metrics:update', this.metrics);

      this.detectSilence();
      this.detectClipping(inputBuffer);

      outputBuffer.getChannelData(0).set(inputBuffer.getChannelData(0));
    };

    this.updateState('capturing');
    return [];
  }

  private processAudioBuffer(
    buffer: AudioBuffer,
    timestamp: number
  ): AudioFrame[] {
    const channelData = buffer.getChannelData(0);
    const sampleRate = buffer.sampleRate;
    const duration = channelData.length / sampleRate;

    const rms = this.calculateRMS(channelData);

    const frame: AudioFrame = {
      timestamp,
      samples: new Float32Array(channelData),
      sampleRate,
      channelCount: buffer.numberOfChannels,
      duration,
      rmsLevel: rms,
    };

    this.frameCount++;
    this.metrics.totalSamples += channelData.length;
    this.metrics.duration = (Date.now() - this.startTime) / 1000;

    return [frame];
  }

  private calculateRMS(samples: Float32Array): number {
    let sum = 0;
    for (let i = 0; i < samples.length; i++) {
      const val = samples[i] ?? 0;
      sum += val * val;
    }
    return Math.sqrt(sum / samples.length);
  }

  private updateMetrics(buffer: AudioBuffer): AudioMetrics {
    const channelData = buffer.getChannelData(0);
    let peak = 0;
    let sum = 0;
    let clips = 0;
    let silentSamples = 0;

    for (let i = 0; i < channelData.length; i++) {
      const sample = channelData[i] ?? 0;
      const abs = Math.abs(sample);
      if (abs > peak) peak = abs;
      sum += abs;
      if (abs > CLIP_THRESHOLD) clips++;
      if (abs < SILENCE_THRESHOLD) silentSamples++;
    }

    const rms = this.calculateRMS(channelData);
    const total = channelData.length;

    this.metrics.peakLevel = Math.max(this.metrics.peakLevel, peak);
    this.metrics.averageLevel = sum / total;
    this.metrics.rmsLevel = rms;
    this.metrics.silenceRatio = silentSamples / total;
    this.metrics.clipCount += clips;
    this.metrics.totalSamples += total;
    this.metrics.duration = (Date.now() - this.startTime) / 1000;

    return { ...this.metrics };
  }

  private detectSilence(): void {
    if (this.metrics.rmsLevel < SILENCE_THRESHOLD) {
      if (!this.silenceStart) {
        this.silenceStart = Date.now();
      } else {
        const silenceDuration = (Date.now() - this.silenceStart) / 1000;
        if (silenceDuration >= 2) {
          this.emit('silence:detected', silenceDuration);
        }
      }
    } else {
      this.silenceStart = 0;
    }
  }

  private detectClipping(buffer: AudioBuffer): void {
    const channelData = buffer.getChannelData(0);
    for (let i = 0; i < channelData.length; i++) {
      const sample = channelData[i] ?? 0;
      const abs = Math.abs(sample);
      if (abs > CLIP_THRESHOLD) {
        this.emit('clip:detected', abs);
        break;
      }
    }
  }

  async pause(): Promise<void> {
    if (this.state === 'capturing') {
      if (this.audioContext) {
        await this.audioContext.suspend();
      }
      this.updateState('paused');
    }
  }

  async resume(): Promise<void> {
    if (this.state === 'paused') {
      if (this.audioContext) {
        await this.audioContext.resume();
      }
      this.updateState('capturing');
    }
  }

  async stop(): Promise<void> {
    if (this.scriptProcessor) {
      this.scriptProcessor.disconnect();
      this.scriptProcessor = null;
    }

    if (this.sourceNode) {
      this.sourceNode.disconnect();
      this.sourceNode = null;
    }

    if (this.audioContext) {
      await this.audioContext.close();
      this.audioContext = null;
    }

    if (this.stream) {
      this.stream.getTracks().forEach(track => track.stop());
      this.stream = null;
    }

    this.updateState('idle');
    this.frameCount = 0;
    this.startTime = 0;
    this.silenceStart = 0;
  }

  getMetrics(): AudioMetrics {
    return { ...this.metrics };
  }

  getState(): AudioCaptureState {
    return this.state;
  }

  getFrameCount(): number {
    return this.frameCount;
  }

  getSampleRate(): number {
    return this.options.sampleRate;
  }

  private updateState(newState: AudioCaptureState): void {
    if (this.state !== newState) {
      this.state = newState;
      this.emit('state:changed', newState);
    }
  }

  destroy(): void {
    this.stop().then(() => {
      this.removeAllListeners();
    });
  }
}

export default AudioStreamCapture;
