import { Injectable, signal } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AudioEngineService {
  public isListening = signal<boolean>(false);
  public wordsDetected = signal<number>(0);
  public linesDetected = signal<number>(0);
  public energyLevel = signal<number>(0); // For visual feedback
  public searchTranscript = signal<string>('');
  public isSearching = signal<boolean>(false);
  public sensitivity = signal<number>(75); // Default sensitivity (50-100)
  public speechRecognitionActive = signal<boolean>(false);
  
  // Voice/Guitar detection signals
  public voiceDetected = signal<boolean>(false);
  public guitarDetected = signal<boolean>(false);
  
  /** Soglia del punteggio voce: valori più bassi = più sensibile alla voce (range 0.05 – 0.50) */
  public voiceThreshold = signal<number>(0.15);
  
  /** Debug: punteggio voce corrente (0-1) per feedback visivo */
  public voiceScore = signal<number>(0);
  
  // Real-time background continuous transcript and word buffer for reliability checks
  public backgroundTranscript = signal<string>('');
  private backgroundRecognition: any = null;
  private recentWordsBuffer: { word: string, timestamp: number }[] = [];
  
  public clearSearchTranscript() {
    this.searchTranscript.set('');
  }
  
  private audioContext: AudioContext | null = null;
  private analyser: AnalyserNode | null = null;
  private stream: MediaStream | null = null;
  private animationFrame: number | null = null;
  private recognition: any = null;

  // Detection logic state
  private isSpeaking: boolean = false;
  private lastSilenceTime: number = Date.now();
  private lastWordTime: number = 0;
  private speakingStartTime: number = 0;
  
  // Constants for tuning - Optimized for close proximity (singer/guitarist)
  private readonly SILENCE_GAP = 100; // ms
  private readonly LINE_SILENCE_GAP = 600; // ms
  private readonly COOLDOWN = 1000; // ms
  private peakEnergy: number = 0;
  private lineStarted: boolean = false;
  private noiseFloor: number = 30; // Adaptive noise floor starting point

  // --- Advanced Voice Detection State ---
  
  // Smoothing buffer (16 frames ~260ms a 60fps)
  private readonly SMOOTHING_FRAMES = 16;
  private voiceScoreBuffer: number[] = [];
  
  // Spectral flux: previous frame spectrum for change detection
  private previousSpectrum: Float32Array | null = null;
  
  // Hysteresis: once voice is detected, it stays on for at least this many frames
  private readonly VOICE_HOLD_FRAMES = 12; // ~200ms
  private voiceHoldCounter: number = 0;
  
  // Sub-band definitions (Hz) - 8 fine-grained bands
  private readonly BANDS = [
    { name: 'sub_bass',    start: 80,   end: 150  },  // Fondamentali basse chitarra (Mi2=82, La2=110)
    { name: 'bass',        start: 150,  end: 300  },  // Fondamentali voce maschile/femminile + chitarra
    { name: 'low_mid',     start: 300,  end: 600  },  // Armoniche chitarra dominanti
    { name: 'mid',         start: 600,  end: 1000 },  // Zona transizione
    { name: 'formant_f1',  start: 1000, end: 1500 },  // Formante F1 voce (vocali aperte)
    { name: 'formant_f2',  start: 1500, end: 2500 },  // Formante F2 voce (KEY: quasi assente in chitarra)
    { name: 'formant_f3',  start: 2500, end: 3500 },  // Formante F3 voce (sibilanti morbide)
    { name: 'sibilants',   start: 3500, end: 6000 },  // Consonanti sibilanti (s, t, f, sh) - SOLO voce
  ];

  constructor() {
    this.initSpeechRecognition();
  }

  private initBackgroundRecognition() {
    // Disattivato per utilizzare esclusivamente l'analizzatore FFT acustico locale
  }

  private startBackgroundRecognition() {
    // Disattivato
  }

  private stopBackgroundRecognition() {
    // Disattivato
  }

  private updateRecentWords(transcript: string) {
    // Disattivato
  }

  public getRecentWords(): string[] {
    return [];
  }

  public getRecentWordsSince(timestamp: number): string[] {
    return [];
  }

  public clearRecentWords() {
    this.recentWordsBuffer = [];
    this.backgroundTranscript.set('');
  }

  private initSpeechRecognition() {
    const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
    if (SpeechRecognition) {
      this.recognition = new SpeechRecognition();
      this.recognition.continuous = false; // Stop after one phrase for search
      this.recognition.interimResults = true;
      this.recognition.lang = 'it-IT';

      this.recognition.onresult = (event: any) => {
        const text = event.results[0][0].transcript;
        this.searchTranscript.set(text);
      };

      this.recognition.onend = () => {
        this.isSearching.set(false);
      };

      this.recognition.onerror = (err: any) => {
        console.error('Search recognition error:', err);
        this.isSearching.set(false);
      };
    }
  }

  startSearchRecognition() {
    if (!this.recognition) {
      alert('Il riconoscimento vocale non è supportato in questo browser.');
      return;
    }
    this.searchTranscript.set('');
    this.isSearching.set(true);
    try {
      this.recognition.start();
    } catch (e) {
      console.warn('Recognition already started', e);
    }
  }

  stopSearchRecognition() {
    this.recognition?.stop();
    this.isSearching.set(false);
  }

  async startListening() {
    if (this.isListening()) return;

    this.isListening.set(true);
    this.voiceDetected.set(false);
    this.guitarDetected.set(false);
    this.voiceScoreBuffer = [];
    this.previousSpectrum = null;
    this.voiceHoldCounter = 0;
    this.clearRecentWords();

    console.log('[AudioEngine] Starting local FFT Audio Analyser');
    await this.startOfflineAnalyser();
  }

  public async startOfflineAnalyser() {
    if (this.analyser) return; // Già attivo
    try {
      this.stream = await navigator.mediaDevices.getUserMedia({ 
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: false // Impedisce il boost automatico del rumore di fondo nel silenzio
        } 
      });
      this.audioContext = new AudioContext();
      const source = this.audioContext.createMediaStreamSource(this.stream);
      
      // FFT a 4096 punti per risoluzione ~10.7 Hz/bin (a 44100 Hz)
      this.analyser = this.audioContext.createAnalyser();
      this.analyser.fftSize = 4096;
      this.analyser.smoothingTimeConstant = 0.4; // Smoothing moderato per stabilità spettrale
      
      source.connect(this.analyser);
      this.processAudio();
    } catch (err) {
      console.error('[AudioEngine] Error accessing microphone for offline analyser:', err);
      // Se fallisce anche il mic locale, proviamo a ripristinare lo stato
      this.stopListening();
      alert('Errore microfono: assicurati di usare HTTPS e di aver dato i permessi.');
    }
  }

  stopListening() {
    if (this.animationFrame) cancelAnimationFrame(this.animationFrame);
    
    // Rilascia le risorse del microfono locale
    this.stream?.getTracks().forEach(track => track.stop());
    this.audioContext?.close();
    this.stream = null;
    this.audioContext = null;
    this.analyser = null;

    this.isListening.set(false);
    this.energyLevel.set(0);
    this.voiceDetected.set(false);
    this.guitarDetected.set(false);
    this.voiceScore.set(0);
    this.voiceScoreBuffer = [];
    this.previousSpectrum = null;
    this.voiceHoldCounter = 0;
    this.stopBackgroundRecognition();
    this.clearRecentWords();
    this.speechRecognitionActive.set(false);
  }

  // =====================================================================
  // ANALISI SPETTRALE AVANZATA MULTI-FEATURE
  // =====================================================================

  /**
   * Calcola l'energia media in una sotto-banda dello spettro.
   */
  private getBandEnergy(dataArray: Uint8Array, binWidth: number, startHz: number, endHz: number): number {
    const startBin = Math.max(0, Math.floor(startHz / binWidth));
    const endBin = Math.min(dataArray.length - 1, Math.floor(endHz / binWidth));
    if (endBin <= startBin) return 0;
    let sum = 0;
    for (let i = startBin; i <= endBin; i++) {
      sum += dataArray[i];
    }
    return sum / (endBin - startBin + 1);
  }

  /**
   * FEATURE 1: Rapporto formanti vocali F2+F3 vs bande strumentali (low+mid)
   * 
   * La voce umana ha formanti F2 (1500-2500 Hz) e F3 (2500-3500 Hz) molto prominenti.
   * La chitarra acustica ha pochissima energia in queste bande.
   * Rapporto alto = voce, basso = strumento.
   */
  private calcFormantRatio(bandEnergies: number[]): number {
    const instrumentEnergy = bandEnergies[0] + bandEnergies[1] + bandEnergies[2] + bandEnergies[3]; // 80-1000 Hz
    const formantEnergy = bandEnergies[5] + bandEnergies[6]; // F2 (1500-2500) + F3 (2500-3500)
    return formantEnergy / (instrumentEnergy + 0.001);
  }

  /**
   * FEATURE 2: Energia sibilanti (3500-6000 Hz)
   * 
   * Solo la voce umana produce consonanti sibilanti come "s", "t", "f", "sh", "z"
   * che hanno energia significativa sopra 3500 Hz. Gli strumenti acustici (chitarra,
   * pianoforte, etc.) hanno energia trascurabile in questa banda.
   * 
   * Ritorna un valore normalizzato 0-1.
   */
  private calcSibilantScore(bandEnergies: number[], totalEnergy: number): number {
    if (totalEnergy < 1) return 0;
    const sibilantEnergy = bandEnergies[7]; // 3500-6000 Hz
    // Normalizza: una sibilante tipica ha 15-40% dell'energia totale
    return Math.min(1, sibilantEnergy / (totalEnergy * 0.15 + 0.001));
  }

  /**
   * FEATURE 3: Spectral Flatness (entropia di Wiener)
   * 
   * Misura quanto lo spettro è "piatto" (rumoroso) vs "piccato" (tonale).
   * - Chitarra: spettro molto tonale con picchi armonici netti → flatness BASSA
   * - Voce (consonanti): spettro più rumoroso → flatness ALTA  
   * - Voce (vocali): moderatamente tonale ma con formanti larghe → flatness MEDIA
   * 
   * Formula: media_geometrica / media_aritmetica (0=tono puro, 1=rumore bianco)
   */
  private calcSpectralFlatness(dataArray: Uint8Array, binWidth: number): number {
    // Calcola nella regione 300-6000 Hz (dove la discriminazione è più utile)
    const startBin = Math.max(1, Math.floor(300 / binWidth));
    const endBin = Math.min(dataArray.length - 1, Math.floor(6000 / binWidth));
    const n = endBin - startBin + 1;
    if (n <= 0) return 0;

    let logSum = 0;
    let arithmeticSum = 0;
    let zeroCount = 0;

    for (let i = startBin; i <= endBin; i++) {
      const val = Math.max(dataArray[i], 0.001); // Avoid log(0)
      logSum += Math.log(val);
      arithmeticSum += val;
      if (dataArray[i] === 0) zeroCount++;
    }

    if (zeroCount > n * 0.5) return 0; // Troppi zeri = silenzio
    
    const geometricMean = Math.exp(logSum / n);
    const arithmeticMean = arithmeticSum / n;
    
    if (arithmeticMean < 0.001) return 0;
    return Math.min(1, geometricMean / arithmeticMean);
  }

  /**
   * FEATURE 4: Spectral Flux (velocità di cambiamento spettrale)
   * 
   * Misura quanto rapidamente cambia la forma dello spettro tra frame successivi.
   * - Voce: alta flux (alternanza vocali/consonanti, prosodia)
   * - Chitarra: bassa flux (note sostenute, spettro stabile)
   * 
   * Ritorna un valore normalizzato 0-1.
   */
  private calcSpectralFlux(dataArray: Uint8Array, binWidth: number): number {
    const startBin = Math.max(0, Math.floor(200 / binWidth));
    const endBin = Math.min(dataArray.length - 1, Math.floor(5000 / binWidth));

    if (!this.previousSpectrum) {
      this.previousSpectrum = new Float32Array(dataArray.length);
      for (let i = 0; i < dataArray.length; i++) {
        this.previousSpectrum[i] = dataArray[i];
      }
      return 0;
    }

    let flux = 0;
    let maxFlux = 0;
    for (let i = startBin; i <= endBin; i++) {
      const diff = dataArray[i] - this.previousSpectrum[i];
      // Solo variazioni positive (onset) per evitare il decadimento naturale
      if (diff > 0) {
        flux += diff * diff;
      }
      maxFlux += 255 * 255; // Massimo teorico
    }

    // Aggiorna il buffer dello spettro precedente
    for (let i = 0; i < dataArray.length; i++) {
      this.previousSpectrum[i] = dataArray[i];
    }

    if (maxFlux === 0) return 0;
    // Normalizza e scala logaritmicamente per sensibilità
    const normalizedFlux = flux / maxFlux;
    return Math.min(1, Math.sqrt(normalizedFlux) * 10); // Amplifica le piccole variazioni
  }

  /**
   * FEATURE 5: Formant Peak Detection
   * 
   * Cerca picchi spettrali caratteristici nella regione delle formanti vocali (800-3500 Hz).
   * La voce umana produce 2-4 picchi prominenti (formanti F1-F4).
   * La chitarra ha uno spettro armonico regolare senza picchi formantici.
   * 
   * Ritorna il numero di picchi formantici rilevati (0-4), normalizzato 0-1.
   */
  private calcFormantPeaks(dataArray: Uint8Array, binWidth: number): number {
    const startBin = Math.max(0, Math.floor(800 / binWidth));
    const endBin = Math.min(dataArray.length - 1, Math.floor(3500 / binWidth));
    
    // Calcola la media locale per determinare la soglia di prominenza
    let sum = 0;
    for (let i = startBin; i <= endBin; i++) {
      sum += dataArray[i];
    }
    const avgEnergy = sum / Math.max(1, endBin - startBin + 1);
    
    if (avgEnergy < 5) return 0; // Silenzio
    
    // Cerca picchi: un bin è un picco se è maggiore dei 5 bin a sinistra e 5 a destra
    // e supera la media di almeno 30%
    const peakThreshold = avgEnergy * 1.3;
    const windowSize = Math.max(3, Math.floor(100 / binWidth)); // ~100 Hz di finestra
    let peakCount = 0;
    let lastPeakBin = -windowSize * 2; // Evita di contare picchi troppo vicini
    
    for (let i = startBin + windowSize; i <= endBin - windowSize; i++) {
      if (dataArray[i] < peakThreshold) continue;
      
      let isPeak = true;
      for (let j = 1; j <= windowSize; j++) {
        if (dataArray[i] < dataArray[i - j] || dataArray[i] < dataArray[i + j]) {
          isPeak = false;
          break;
        }
      }
      
      if (isPeak && (i - lastPeakBin) > windowSize) {
        peakCount++;
        lastPeakBin = i;
        if (peakCount >= 4) break; // Max 4 formanti
      }
    }
    
    return Math.min(1, peakCount / 3); // 3 formanti = punteggio pieno
  }

  /**
   * FEATURE 6: Rapporto energia alta/bassa (spectral tilt)
   * 
   * La voce umana (soprattutto da vicino al microfono) ha uno spettro più "piatto"
   * con energia significativa anche nelle alte frequenze.
   * La chitarra ha una forte caduta sopra 1-2 kHz.
   */
  private calcSpectralTilt(bandEnergies: number[]): number {
    const lowEnergy = bandEnergies[0] + bandEnergies[1] + bandEnergies[2]; // 80-600 Hz
    const highEnergy = bandEnergies[5] + bandEnergies[6] + bandEnergies[7]; // 1500-6000 Hz
    if (lowEnergy < 0.001) return 0;
    // Un rapporto alto indica più energia nelle alte frequenze (voce)
    return Math.min(1, highEnergy / (lowEnergy + 0.001));
  }

  /**
   * Calcola il punteggio voce combinato da tutte le feature.
   * 
   * Pesi delle feature (calibrati per massimizzare la discriminazione):
   * - Formant ratio: 25% (molto discriminante)
   * - Sibilant score: 20% (quasi esclusivo della voce)
   * - Spectral flatness: 15% (voce più rumorosa di chitarra)
   * - Spectral flux: 15% (voce cambia più rapidamente)
   * - Formant peaks: 15% (struttura formanti unica della voce)
   * - Spectral tilt: 10% (distribuzione energia)
   */
  private calcVoiceScore(
    dataArray: Uint8Array,
    bandEnergies: number[],
    totalEnergy: number,
    binWidth: number
  ): number {
    const formantRatio = this.calcFormantRatio(bandEnergies);
    const sibilantScore = this.calcSibilantScore(bandEnergies, totalEnergy);
    const flatness = this.calcSpectralFlatness(dataArray, binWidth);
    const flux = this.calcSpectralFlux(dataArray, binWidth);
    const formantPeaks = this.calcFormantPeaks(dataArray, binWidth);
    const spectralTilt = this.calcSpectralTilt(bandEnergies);

    // Normalizza formantRatio: tipicamente 0.05-0.5 per voce, 0-0.05 per chitarra
    const normalizedFormantRatio = Math.min(1, formantRatio / 0.4);

    // Punteggio pesato
    const score =
      normalizedFormantRatio * 0.25 +
      sibilantScore * 0.20 +
      flatness * 0.15 +
      flux * 0.15 +
      formantPeaks * 0.15 +
      spectralTilt * 0.10;

    return Math.min(1, Math.max(0, score));
  }

  // =====================================================================
  // MAIN AUDIO PROCESSING LOOP
  // =====================================================================

  private processAudio() {
    if (!this.analyser) return;

    const bufferLength = this.analyser.frequencyBinCount; // 2048 con fftSize=4096
    const dataArray = new Uint8Array(bufferLength);
    const sampleRate = this.audioContext ? this.audioContext.sampleRate : 44100;
    const binWidth = sampleRate / 4096; // ~10.7 Hz/bin
    
    // Energia minima per considerare che c'è suono (evita falsi positivi nel silenzio)
    const MIN_ENERGY = 5;
    
    const analyze = () => {
      if (!this.analyser) return;
      this.analyser.getByteFrequencyData(dataArray);
      
      // Calcola energia in ciascuna delle 8 sotto-bande
      const bandEnergies: number[] = this.BANDS.map(band => 
        this.getBandEnergy(dataArray, binWidth, band.start, band.end)
      );

      // Energia totale media
      const totalEnergy = bandEnergies.reduce((a, b) => a + b, 0) / bandEnergies.length;
      
      // Feedback visuale (barra energia) — usa la media delle bande vocali per coerenza
      const vocalDisplayEnergy = (bandEnergies[4] + bandEnergies[5] + bandEnergies[6]) / 3;
      this.energyLevel.set(vocalDisplayEnergy);

      const now = Date.now();
      const hasSound = totalEnergy > MIN_ENERGY;

      if (hasSound) {
        // Calcola il punteggio voce multi-feature
        const rawScore = this.calcVoiceScore(dataArray, bandEnergies, totalEnergy, binWidth);
        
        // Aggiungi al buffer di smoothing
        this.voiceScoreBuffer.push(rawScore);
        if (this.voiceScoreBuffer.length > this.SMOOTHING_FRAMES) {
          this.voiceScoreBuffer.shift();
        }
        
        // Media pesata: i frame più recenti contano di più
        let weightedSum = 0;
        let weightTotal = 0;
        for (let i = 0; i < this.voiceScoreBuffer.length; i++) {
          const weight = (i + 1); // Peso crescente
          weightedSum += this.voiceScoreBuffer[i] * weight;
          weightTotal += weight;
        }
        const smoothedScore = weightedSum / weightTotal;
        
        this.voiceScore.set(smoothedScore);
        
        // Determinazione con isteresi
        const threshold = this.voiceThreshold();
        
        if (smoothedScore > threshold) {
          // Voce rilevata
          this.voiceDetected.set(true);
          this.guitarDetected.set(false);
          this.voiceHoldCounter = this.VOICE_HOLD_FRAMES;
        } else if (this.voiceHoldCounter > 0) {
          // Isteresi: mantieni lo stato "voce" per evitare oscillazioni
          this.voiceHoldCounter--;
          // Lo stato resta quello precedente (voce)
        } else {
          // Strumento/rumore ambientale
          this.voiceDetected.set(false);
          this.guitarDetected.set(true);
        }
      } else {
        // Silenzio
        this.voiceDetected.set(false);
        this.guitarDetected.set(false);
        this.voiceScore.set(0);
        this.voiceHoldCounter = 0;
      }

      // --- Logica conteggio parole (solo se voce rilevata) ---
      
      // Tracciamento adattivo del rumore di fondo
      if (totalEnergy < this.noiseFloor) {
        this.noiseFloor = this.noiseFloor * 0.95 + totalEnergy * 0.05;
      } else {
        this.noiseFloor = this.noiseFloor * 0.998 + totalEnergy * 0.002;
      }

      // Soglia di sensibilità definita dall'utente
      const userThreshold = 220 - (this.sensitivity() * 2.0); 
      const currentThreshold = Math.max(userThreshold, this.noiseFloor + 12);

      // *** CONTEGGIO PAROLE SOLO QUANDO LA VOCE UMANA È DOMINANTE ***
      const isVoice = this.voiceDetected();
      
      if (isVoice && totalEnergy > currentThreshold) {
        if (totalEnergy > this.peakEnergy) {
          this.peakEnergy = totalEnergy;
        }

        if (!this.isSpeaking) {
          this.isSpeaking = true;
          this.speakingStartTime = now;
          this.peakEnergy = totalEnergy; 
          this.lastSilenceTime = now;
        }

        // Rilevamento della caduta di energia relativa per stacchi sillabici
        const dropRatio = (this.peakEnergy - totalEnergy) / this.peakEnergy;
        if (this.isSpeaking && dropRatio > 0.20 && this.peakEnergy > currentThreshold * 1.1) {
          const speakDuration = now - this.speakingStartTime;
          if (now - this.lastWordTime > this.COOLDOWN && speakDuration > 120) {
            this.linesDetected.update(v => v + 1);
            this.lastWordTime = now;
            this.peakEnergy = totalEnergy;
            this.isSpeaking = false; 
            console.log('[VoiceDetect] Word counted - voice energy drop', { 
              score: this.voiceScore().toFixed(3), 
              dropRatio: dropRatio.toFixed(2),
              duration: speakDuration
            });
          }
        }
        
        this.lastSilenceTime = now;
      } else {
        // Sotto soglia, chitarra, o silenzio
        if (this.isSpeaking && (now - this.lastSilenceTime > 180)) {
          // Calcola la durata reale del segnale vocale escludendo la finestra di silenzio (180ms)
          const actualSoundDuration = this.lastSilenceTime - this.speakingStartTime;
          if (isVoice && now - this.lastWordTime > this.COOLDOWN && actualSoundDuration > 100) {
            this.linesDetected.update(v => v + 1);
            this.lastWordTime = now;
            console.log('[VoiceDetect] Word counted - voice silence gap', { duration: actualSoundDuration });
          }
          this.isSpeaking = false;
          this.peakEnergy = 0;
        }
        
        // Se la chitarra è dominante, resetta lo stato di parlato
        if (!isVoice && this.isSpeaking) {
          this.isSpeaking = false;
          this.peakEnergy = 0;
        }
      }

      this.animationFrame = requestAnimationFrame(analyze);
    };

    analyze();
  }

  resetWordCount() {
    this.wordsDetected.set(0);
    this.linesDetected.set(0);
    this.lineStarted = false;
  }
}
