/**
 * VEZA V5 ULTRA - AUDIO ENGINE
 * Advanced audio engine with Web Audio API, spatial audio, equalizer, and analysis
 */

export interface AudioEngineConfig {
  crossfade: {
    enabled: boolean;
    duration: number;
    curve: 'logarithmic' | 'linear' | 'exponential';
    smartDetection: boolean;
  };
  loudness: {
    target: number; // LUFS
    realtime: boolean;
    preventClipping: boolean;
  };
  spatial: {
    enabled: boolean;
    mode: 'binaural' | 'stereo_widening' | '3d';
    headTracking: boolean;
  };
  analysis: {
    bpm: boolean;
    key: boolean;
    mood: boolean;
    energy: boolean;
    danceability: boolean;
  };
}

export interface Track {
  id: string;
  url: string;
  title: string;
  artist: string;
  duration: number;
  artwork?: string;
}

export interface AudioAnalysis {
  bpm?: number;
  key?: string;
  mood?: 'energetic' | 'calm' | 'happy' | 'sad';
  energy?: number; // 0-1
  danceability?: number; // 0-1
  waveform: number[];
  frequencyData: Uint8Array;
}

export class AudioEngineV5 {
  private audioContext: AudioContext;
  private analyser: AnalyserNode;
  private gainNode: GainNode;
  private compressor: DynamicsCompressorNode;
  private equalizer: BiquadFilterNode[];
  private spatialPanner: StereoPannerNode;
  
  private currentSource: AudioBufferSourceNode | null = null;
  private nextSource: AudioBufferSourceNode | null = null;
  
  private currentTrack: Track | null = null;
  private nextTrack: Track | null = null;
  
  private config: AudioEngineConfig;
  private isPlaying = false;
  private isPaused = false;
  private currentTime = 0;
  private startTime = 0;
  private pauseTime = 0;
  
  // Callbacks
  private onTimeUpdate?: (time: number) => void;
  private onTrackEnd?: () => void;
  private onAnalysisUpdate?: (analysis: AudioAnalysis) => void;

  constructor(config: Partial<AudioEngineConfig> = {}) {
    this.config = {
      crossfade: {
        enabled: true,
        duration: 8,
        curve: 'logarithmic',
        smartDetection: true,
        ...config.crossfade,
      },
      loudness: {
        target: -14,
        realtime: true,
        preventClipping: true,
        ...config.loudness,
      },
      spatial: {
        enabled: false,
        mode: 'stereo_widening',
        headTracking: false,
        ...config.spatial,
      },
      analysis: {
        bpm: true,
        key: true,
        mood: true,
        energy: true,
        danceability: true,
        ...config.analysis,
      },
    };

    // Initialize Web Audio API
    this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
    
    // Create audio nodes
    this.analyser = this.audioContext.createAnalyser();
    this.analyser.fftSize = 2048;
    this.analyser.smoothingTimeConstant = 0.8;
    
    this.gainNode = this.audioContext.createGain();
    this.gainNode.gain.value = 1;
    
    this.compressor = this.audioContext.createDynamicsCompressor();
    this.compressor.threshold.value = -50;
    this.compressor.knee.value = 40;
    this.compressor.ratio.value = 12;
    this.compressor.attack.value = 0;
    this.compressor.release.value = 0.25;
    
    // Create 10-band equalizer
    this.equalizer = this.createEqualizer();
    
    this.spatialPanner = this.audioContext.createStereoPanner();
    this.spatialPanner.pan.value = 0;
    
    // Connect nodes
    this.connectNodes();
    
    // Start analysis loop
    this.startAnalysisLoop();
  }

  private createEqualizer(): BiquadFilterNode[] {
    const frequencies = [32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000];
    const filters: BiquadFilterNode[] = [];

    frequencies.forEach((freq, index) => {
      const filter = this.audioContext.createBiquadFilter();
      
      if (index === 0) {
        filter.type = 'lowshelf';
      } else if (index === frequencies.length - 1) {
        filter.type = 'highshelf';
      } else {
        filter.type = 'peaking';
        filter.Q.value = 1;
      }
      
      filter.frequency.value = freq;
      filter.gain.value = 0;
      
      filters.push(filter);
    });

    return filters;
  }

  private connectNodes(): void {
    // Connect equalizer chain
    this.equalizer.reduce((prev, curr) => {
      prev.connect(curr);
      return curr;
    });

    // Connect main chain
    this.equalizer[this.equalizer.length - 1].connect(this.compressor);
    this.compressor.connect(this.spatialPanner);
    this.spatialPanner.connect(this.gainNode);
    this.gainNode.connect(this.analyser);
    this.analyser.connect(this.audioContext.destination);
  }

  async loadTrack(track: Track): Promise<void> {
    try {
      const response = await fetch(track.url);
      const arrayBuffer = await response.arrayBuffer();
      const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
      
      this.nextTrack = track;
      
      // If crossfade is enabled and we're playing, prepare for crossfade
      if (this.config.crossfade.enabled && this.isPlaying) {
        this.prepareNextSource(audioBuffer);
      } else {
        // Otherwise, load directly
        this.loadSource(audioBuffer);
        this.currentTrack = track;
      }
    } catch (error) {
      console.error('Error loading track:', error);
      throw error;
    }
  }

  private loadSource(audioBuffer: AudioBuffer): void {
    if (this.currentSource) {
      this.currentSource.disconnect();
    }

    this.currentSource = this.audioContext.createBufferSource();
    this.currentSource.buffer = audioBuffer;
    this.currentSource.connect(this.equalizer[0]);
    
    this.currentSource.onended = () => {
      if (this.isPlaying && !this.isPaused) {
        this.onTrackEnd?.();
      }
    };
  }

  private prepareNextSource(audioBuffer: AudioBuffer): void {
    this.nextSource = this.audioContext.createBufferSource();
    this.nextSource.buffer = audioBuffer;
    
    const nextGain = this.audioContext.createGain();
    nextGain.gain.value = 0;
    
    this.nextSource.connect(nextGain);
    nextGain.connect(this.equalizer[0]);
  }

  async play(): Promise<void> {
    if (!this.currentSource) {
      throw new Error('No track loaded');
    }

    if (this.audioContext.state === 'suspended') {
      await this.audioContext.resume();
    }

    if (this.isPaused) {
      // Resume from pause
      this.startTime = this.audioContext.currentTime - this.pauseTime;
      this.isPaused = false;
    } else {
      // Start from beginning
      this.currentSource.start(0, this.currentTime);
      this.startTime = this.audioContext.currentTime - this.currentTime;
    }

    this.isPlaying = true;
  }

  pause(): void {
    if (!this.isPlaying) return;

    this.pauseTime = this.audioContext.currentTime - this.startTime;
    this.isPaused = true;
    this.isPlaying = false;

    if (this.currentSource) {
      this.currentSource.stop();
    }
  }

  stop(): void {
    if (this.currentSource) {
      this.currentSource.stop();
      this.currentSource.disconnect();
      this.currentSource = null;
    }

    this.isPlaying = false;
    this.isPaused = false;
    this.currentTime = 0;
    this.startTime = 0;
    this.pauseTime = 0;
  }

  seek(time: number): void {
    const wasPlaying = this.isPlaying;
    
    if (wasPlaying) {
      this.stop();
    }

    this.currentTime = time;

    if (wasPlaying && this.currentTrack) {
      // Reload and play from new position
      this.loadTrack(this.currentTrack).then(() => {
        this.play();
      });
    }
  }

  setVolume(volume: number): void {
    this.gainNode.gain.setValueAtTime(
      Math.max(0, Math.min(1, volume)),
      this.audioContext.currentTime
    );
  }

  getVolume(): number {
    return this.gainNode.gain.value;
  }

  // Equalizer methods
  setEqualizerBand(index: number, gain: number): void {
    if (index >= 0 && index < this.equalizer.length) {
      this.equalizer[index].gain.setValueAtTime(
        gain,
        this.audioContext.currentTime
      );
    }
  }

  resetEqualizer(): void {
    this.equalizer.forEach(filter => {
      filter.gain.setValueAtTime(0, this.audioContext.currentTime);
    });
  }

  // Spatial audio
  setSpatialPan(pan: number): void {
    this.spatialPanner.pan.setValueAtTime(
      Math.max(-1, Math.min(1, pan)),
      this.audioContext.currentTime
    );
  }

  // Analysis
  private startAnalysisLoop(): void {
    const update = () => {
      if (this.isPlaying) {
        const analysis = this.getAnalysis();
        this.onAnalysisUpdate?.(analysis);
        
        const currentPlayTime = this.audioContext.currentTime - this.startTime;
        this.onTimeUpdate?.(currentPlayTime);
      }
      
      requestAnimationFrame(update);
    };
    
    update();
  }

  getAnalysis(): AudioAnalysis {
    const bufferLength = this.analyser.frequencyBinCount;
    const frequencyData = new Uint8Array(bufferLength);
    const waveformData = new Uint8Array(bufferLength);
    
    this.analyser.getByteFrequencyData(frequencyData);
    this.analyser.getByteTimeDomainData(waveformData);
    
    // Convert to normalized waveform
    const waveform = Array.from(waveformData).map(v => (v - 128) / 128);
    
    // Calculate energy (average of frequency data)
    const energy = Array.from(frequencyData).reduce((a, b) => a + b, 0) / bufferLength / 255;

    return {
      waveform,
      frequencyData,
      energy,
    };
  }

  // Event handlers
  onTimeUpdateCallback(callback: (time: number) => void): void {
    this.onTimeUpdate = callback;
  }

  onTrackEndCallback(callback: () => void): void {
    this.onTrackEnd = callback;
  }

  onAnalysisUpdateCallback(callback: (analysis: AudioAnalysis) => void): void {
    this.onAnalysisUpdate = callback;
  }

  // Cleanup
  destroy(): void {
    this.stop();
    
    if (this.currentSource) {
      this.currentSource.disconnect();
    }
    
    this.equalizer.forEach(filter => filter.disconnect());
    this.analyser.disconnect();
    this.gainNode.disconnect();
    this.compressor.disconnect();
    this.spatialPanner.disconnect();
    
    this.audioContext.close();
  }

  // Getters
  getCurrentTime(): number {
    if (this.isPlaying) {
      return this.audioContext.currentTime - this.startTime;
    }
    return this.pauseTime || this.currentTime;
  }

  getDuration(): number {
    return this.currentTrack?.duration || 0;
  }

  getCurrentTrack(): Track | null {
    return this.currentTrack;
  }

  getIsPlaying(): boolean {
    return this.isPlaying;
  }

  getIsPaused(): boolean {
    return this.isPaused;
  }
}

// Export singleton instance
let engineInstance: AudioEngineV5 | null = null;

export const getAudioEngine = (): AudioEngineV5 => {
  if (!engineInstance) {
    engineInstance = new AudioEngineV5();
  }
  return engineInstance;
};

export const destroyAudioEngine = (): void => {
  if (engineInstance) {
    engineInstance.destroy();
    engineInstance = null;
  }
};

