//! DSP and Voice Activity Detection (VAD) thread.
//!
//! Pulls audio from the lock-free ringbuffer, applies WebRTC noise suppression
//! and echo cancellation, then checks for voice activity before signalling
//! the UI via `tokio::sync::watch` channels.
//!
//! This thread is a dedicated `std::thread` (not a Tokio task) because
//! real-time audio processing must never be at the mercy of a cooperative
//! async scheduler.

use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;

use ringbuf::{HeapCons, HeapProd};
use ringbuf::traits::{Consumer, Observer, Producer};
use tokio::sync::watch;
use tracing::info;
#[cfg(feature = "webrtc")]
use webrtc_audio_processing::Processor;
#[cfg(feature = "webrtc")]
use webrtc_audio_processing_config::{
    Config, EchoCanceller, NoiseSuppression, NoiseSuppressionLevel,
};

use super::SAMPLE_RATE;

/// Mock processor for when WebRTC is disabled.
#[cfg(not(feature = "webrtc"))]
struct Processor;
#[cfg(not(feature = "webrtc"))]
impl Processor {
    fn new(_rate: i32) -> Result<Self, String> { Ok(Self) }
    fn set_config(&self, _config: ()) {}
    fn process_capture_frame(&self, _frame: &mut Vec<Vec<f32>>) -> Result<(), String> { Ok(()) }
}

/// RMS threshold below which a frame is considered silence.
const VAD_RMS_THRESHOLD: f32 = 0.01;

/// WebRTC strictly requires 10ms frames (480 samples at 48kHz).
const DSP_FRAME_SIZE: usize = 480;

/// Spawns the dedicated background DSP thread.
pub fn spawn_dsp_thread(
    mut consumer: HeapCons<f32>,
    mut loopback_prod: HeapProd<f32>,
    ptt_flag: Arc<AtomicBool>,
    mute_flag: Arc<AtomicBool>,
    audio_dumper_flag: Arc<AtomicBool>,
    active_speaker_tx: watch::Sender<bool>,
    mic_level_tx: watch::Sender<f32>,
) {
    thread::spawn(move || {
        info!("DSP thread started (WebRTC: {}).", cfg!(feature = "webrtc"));

        let ap = match Processor::new(SAMPLE_RATE as i32) {
            Ok(ap) => ap,
            Err(e) => {
                tracing::error!("Failed to initialize Processor: {:?}", e);
                return;
            }
        };

        #[cfg(feature = "webrtc")]
        {
            let config = Config {
                echo_canceller: Some(EchoCanceller::default()),
                noise_suppression: Some(NoiseSuppression {
                    level: NoiseSuppressionLevel::High,
                    analyze_linear_aec_output: false,
                }),
                ..Default::default()
            };
            ap.set_config(config);
        }

        let mut frame_buf = vec![vec![0.0f32; DSP_FRAME_SIZE]];

        let wav_spec = hound::WavSpec {
            channels: 1,
            sample_rate: SAMPLE_RATE,
            bits_per_sample: 32,
            sample_format: hound::SampleFormat::Float,
        };
        let mut raw_writer: Option<hound::WavWriter<std::io::BufWriter<std::fs::File>>> = None;
        let mut dsp_writer: Option<hound::WavWriter<std::io::BufWriter<std::fs::File>>> = None;

        loop {
            // Wait until we have a full 10ms frame (480 samples at 48kHz).
            if consumer.occupied_len() >= DSP_FRAME_SIZE {
                let _ = consumer.pop_slice(&mut frame_buf[0]);

                let is_ptt_active = ptt_flag.load(Ordering::Relaxed);
                let is_muted = mute_flag.load(Ordering::Relaxed);
                let dumper_enabled = audio_dumper_flag.load(Ordering::Relaxed);

                // Snapshot the raw frame before DSP (for the audio dumper).
                let mut raw_frame = None;
                if dumper_enabled {
                    raw_frame = Some(frame_buf.clone());
                }

                // Run the WebRTC DSP pipeline on the capture frame.
                if let Err(e) = ap.process_capture_frame(&mut frame_buf) {
                    tracing::warn!("APM processing failed: {:?}", e);
                }

                // Push to loopback ringbuffer (non-blocking).
                // We push even if loopback is disabled in the playback thread
                // to keep the pipeline moving if needed.
                let _ = loopback_prod.push_slice(&frame_buf[0]);

                // ── Audio Dumper ──
                if dumper_enabled {
                    if raw_writer.is_none() {
                        raw_writer = hound::WavWriter::create("raw_mic.wav", wav_spec).ok();
                        dsp_writer = hound::WavWriter::create("post_dsp.wav", wav_spec).ok();
                    }
                    if let (Some(writer), Some(raw)) = (&mut raw_writer, &raw_frame) {
                        for &sample in &raw[0] {
                            let _ = writer.write_sample(sample);
                        }
                    }
                    if let Some(writer) = &mut dsp_writer {
                        for &sample in &frame_buf[0] {
                            let _ = writer.write_sample(sample);
                        }
                    }
                } else if raw_writer.is_some() {
                    // Close writers when disabled.
                    if let Some(writer) = raw_writer.take() {
                        let _ = writer.finalize();
                    }
                    if let Some(writer) = dsp_writer.take() {
                        let _ = writer.finalize();
                    }
                }

                // ── VAD + mic level ──
                let rms = compute_rms(&frame_buf[0]);
                let _ = mic_level_tx.send(rms);

                let has_voice = rms > VAD_RMS_THRESHOLD;
                let should_transmit = is_ptt_active && has_voice && !is_muted;

                // Only update the watch channel when the state actually changes
                // to avoid unnecessary UI repaints.
                if *active_speaker_tx.borrow() != should_transmit {
                    let _ = active_speaker_tx.send(should_transmit);
                }
            } else {
                thread::sleep(Duration::from_millis(2));
            }
        }
    });
}

/// Computes the Root Mean Square (RMS) of a sample buffer.
///
/// Used as a lightweight VAD: if the RMS is below a threshold,
/// the frame is considered silence.
fn compute_rms(samples: &[f32]) -> f32 {
    if samples.is_empty() {
        return 0.0;
    }
    let sum_sq: f32 = samples.iter().map(|s| s * s).sum();
    #[allow(clippy::cast_precision_loss)]
    // DSP_FRAME_SIZE (480) is well within f32's 23-bit mantissa.
    let divisor = samples.len() as f32;
    (sum_sq / divisor).sqrt()
}
