/**
 * Audio Processing Pipeline for Real-Time Analysis
 * Coordinates WebRTC stream capture with VoicePreprocess for continuous analysis
 */

import { WebRTCStreamCapture, createWebRTCCapture } from './stream-capture';

// Type definitions for real-time processing
export interface AudioChunk {
  id: string;
  timestamp: number;
  data: Float32Array;
  duration: number;
}

export interface VoiceprintResult {
  chunkId: string;
  timestamp: number;
  features: AudioFeatures;
  embedding: number[];
  confidence: number;
  status: 'complete' | 'error';
}

// Audio chunk configuration
export interface AudioChunkConfig {
  chunkDuration: number;
  overlapDuration: number;
  sampleRate: number;
}

// Preprocessor interfaces (copied from AudioPreprocessor for standalone usage)
export interface PreprocessedAudio {
  audio: Buffer;
  sampleRate: number;
  channels: number;
  durationSec: number;
}

export interface AudioFeatures {
  mfccs: number[][];
  zeroCrossingRate: number;
  spectralCentroid: number;
  spectralRollOff: number;
  durationSec: number;
}

interface PipelineConfig {
  chunkDuration: number;     // 5000ms
  overlapDuration: number;   // 1000ms
  sampleRate: number;        // 16000 Hz
  onAnalysisComplete?: (result: VoiceprintResult) => void;
  onStreamError?: (error: Error) => void;
  onError?: (error: Error) => void;
}

const DEFAULT_CONFIG: PipelineConfig = {
  chunkDuration: 5000,
  overlapDuration: 1000,
  sampleRate: 16000
};

export class AudioPipeline {
  private streamCapture: WebRTCStreamCapture | null = null;
  private isRunning: boolean = false;
  private chunkBuffer: AudioChunk[] = [];
  private maxBufferLength: number = 10;
  
  private onChunkReady?: (chunk: AudioChunk) => void;
  private onAnalysisComplete?: (result: VoiceprintResult) => void;
  private onPipelineError?: (error: Error) => void;
  
  constructor(private config: PipelineConfig = DEFAULT_CONFIG) {}

  /**
   * Initialize pipeline components
   */
  async initialize(): Promise<void> {
    // Initialize WebRTC stream capture
    this.streamCapture = createWebRTCCapture({
      chunkDuration: this.config.chunkDuration,
      overlapDuration: this.config.overlapDuration,
      sampleRate: this.config.sampleRate
    });

    // Connect WebRTC chunk processing
    this.streamCapture.onChunkReady = (rawAudio, timestamp) => {
      this.handleRawChunk(rawAudio, timestamp);
    };

    // Connect stream error handling
    this.streamCapture.onStreamError = (error) => {
      this.onPipelineError?.(error);
    };

    console.log('[Pipeline] Initialized');
  }

  /**
   * Process raw audio chunk from WebRTC
   */
  private async handleRawChunk(rawAudio: Float32Array, timestamp: number): Promise<void> {
    try {
      // Create audio chunk
      const chunk: AudioChunk = {
        id: `chunk-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        timestamp,
        data: rawAudio,
        duration: this.config.chunkDuration / 1000
      };

      // Add to buffer
      this.chunkBuffer.push(chunk);

      // Maintain buffer size
      if (this.chunkBuffer.length > this.maxBufferLength) {
        const removed = this.chunkBuffer.shift();
        if (removed) {
          // Process removed chunk with overlap
          await this.processChunk(removed);
        }
      }

      // Process current chunk
      await this.processChunk(chunk);

    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      console.error('[Pipeline] Error processing chunk:', err);
      this.onPipelineError?.(err);
    }
  }

  /**
   * Convert Float32Array to 16-bit PCM Buffer
   */
  private float32ArrayToBuffer(floatData: Float32Array): Buffer {
    const int16Array = new Int16Array(floatData.length);
    for (let i = 0; i < floatData.length; i++) {
      // Clamp and scale to 16-bit range
      const clamped = Math.max(-1, Math.min(1, floatData[i]));
      int16Array[i] = Math.round(clamped * 32767);
    }
    return Buffer.from(int16Array.buffer);
  }

  /**
   * Process a single audio chunk (mock implementation)
   */
  private async processChunk(chunk: AudioChunk): Promise<VoiceprintResult> {
    try {
      // Generate mock features
      // Convert Float32Array to Buffer properly
      const int16Array = new Int16Array(chunk.data.length);
      for (let i = 0; i < chunk.data.length; i++) {
        const clamped = Math.max(-1, Math.min(1, chunk.data[i]));
        int16Array[i] = Math.round(clamped * 32767);
      }
      const dataBuffer = Buffer.from(int16Array.buffer);
      const features: AudioFeatures = await extractMockFeatures(dataBuffer, chunk.duration);

      // Generate embedding (placeholder - would use actual embedding service)
      const embedding = this.generatePlaceholderEmbedding(features);

      // Create result
      const result: VoiceprintResult = {
        chunkId: chunk.id,
        timestamp: chunk.timestamp,
        features,
        embedding,
        confidence: 0.95, // Placeholder - would come from actual analysis
        status: 'complete'
      };

      // Emit result
      this.onAnalysisComplete?.(result);

      return result;

    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      console.error('[Pipeline] Preprocessing error:', err);
      this.onPipelineError?.(err);
      throw err;
    }
  }

  /**
   * Generate placeholder embedding (would use actual embedding service)
   */
  private generatePlaceholderEmbedding(features: AudioFeatures): number[] {
    // Placeholder embedding - would be replaced with actual embedding generation
    const embedding: number[] = [];
    
    // Use spectral features as proxy for embedding
    embedding.push(features.spectralCentroid);
    embedding.push(features.spectralRollOff);
    embedding.push(features.zeroCrossingRate);
    
    // MFCC summary (first few coefficients)
    if (features.mfccs && features.mfccs.length > 0) {
      for (let i = 0; i < Math.min(5, features.mfccs[0].length); i++) {
        embedding.push(features.mfccs[0][i]);
      }
    }
    
    // Normalize and pad
    while (embedding.length < 128) {
      embedding.push(0);
    }
    
    return embedding;
  }

  /**
   * Start the real-time analysis pipeline
   */
  async start(): Promise<void> {
    if (this.isRunning) {
      console.log('[Pipeline] Already running');
      return;
    }

    try {
      await this.initialize();
      if (this.streamCapture) {
        await this.streamCapture.start();
      }
      this.isRunning = true;

      console.log('[Pipeline] Real-time analysis started');

    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      console.error('[Pipeline] Failed to start:', err);
      this.onPipelineError?.(err);
      throw err;
    }
  }

  /**
   * Stop the pipeline
   */
  async stop(): Promise<void> {
    if (!this.isRunning) {
      return;
    }

    try {
      // Drain remaining buffer
      while (this.chunkBuffer.length > 0) {
        const chunk = this.chunkBuffer.shift();
        if (chunk) {
          await this.processChunk(chunk);
        }
      }

      // Stop WebRTC capture
      if (this.streamCapture) {
        this.streamCapture.stop();
      }

      this.isRunning = false;
      console.log('[Pipeline] Stopped');

    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      console.error('[Pipeline] Error stopping:', err);
      this.onPipelineError?.(err);
      throw err;
    }
  }

  /**
   * Get pipeline status
   */
  getStatus(): {
    isRunning: boolean;
    bufferLength: number;
    streamActive: boolean;
  } {
    return {
      isRunning: this.isRunning,
      bufferLength: this.chunkBuffer.length,
      streamActive: this.streamCapture?.isRecording || false
    };
  }
}

/**
 * Extract mock features from audio buffer
 */
async function extractMockFeatures(buffer: Buffer, duration: number): Promise<AudioFeatures> {
    // Simple mock feature extraction for demonstration
    // In production, this would use actual audio processing
    const numMfccs = 13;
    const mfccs: number[][] = [];
    
    // Generate mock MFCCs based on buffer hash
    const bufferHash = buffer.reduce((acc, byte) => acc + byte, 0);
    for (let i = 0; i < numMfccs; i++) {
      const coefficients: number[] = [];
      for (let j = 0; j < 20; j++) {
        coefficients.push(Math.abs(Math.sin((i * j + bufferHash) * 0.1)) * 0.5 + 0.25);
      }
      mfccs.push(coefficients);
    }
    
    return {
      mfccs,
      zeroCrossingRate: 0.02 + Math.random() * 0.03,
      spectralCentroid: 1000 + Math.random() * 2000,
      spectralRollOff: 3000 + Math.random() * 1000,
      durationSec: duration
    };
  }
