//! Sonora software voice processor — full DSP chain.
//!
//! Composes the four P1 DSP stages in the order mandated by the spec:
//!
//!   **HPF → AEC3 → NS → AGC2**
//!
//! Each stage is independently enable/disable-able via
//! [`SonoraConfig`]. The default configuration matches the P1 spec:
//! all stages enabled, AEC3 disabled when no render reference is
//! available (INV_011).
//!
//! ## Stage descriptions
//!
//! | Stage | Module | Description |
//! |-------|--------|-------------|
//! | HPF   | `dsp::hpf` | 80 Hz Butterworth biquad, removes DC and rumble |
//! | AEC3  | `dsp::aec3` | Adaptive filter echo canceller with delay estimation |
//! | NS    | `dsp::ns` | Wiener filter noise suppressor with min-statistics floor |
//! | AGC2  | `dsp::agc2` | RNN VAD-gated gain controller with look-ahead limiter |
//!
//! ## Realtime safety
//!
//! All state is pre-allocated. `process_capture` and `process_render`
//! never allocate, block, or perform I/O (INV_007).
//!
//! ## INV_009 / INV_010 enforcement
//!
//! `has_aec()` returns `true` when AEC3 is enabled. The engine uses
//! this to enforce the invariant that platform AEC and Rust AEC are
//! never active simultaneously.

use super::dsp::{aec3::Aec3, agc2::Agc2, hpf::HighPassFilter, ns::NoiseSuppressor};
use super::{AudioProcessor, FRAME_SAMPLES};

/// Per-stage enable flags for the Sonora processor.
#[derive(Debug, Clone, PartialEq)]
pub struct SonoraConfig {
    /// High-pass filter (80 Hz Butterworth). Default: enabled.
    pub hpf: bool,
    /// AEC3 adaptive echo canceller. Default: disabled until render
    /// reference is confirmed available (INV_011).
    pub aec3: bool,
    /// Wiener filter noise suppressor. Default: enabled.
    pub ns: bool,
    /// AGC2 gain controller + limiter. Default: enabled.
    pub agc2: bool,
}

impl Default for SonoraConfig {
    fn default() -> Self {
        Self {
            hpf: true,
            // AEC3 is disabled by default: it requires a render reference
            // (INV_011). The engine enables it only when the render
            // reference path is confirmed active.
            aec3: false,
            ns: true,
            agc2: true,
        }
    }
}

impl SonoraConfig {
    /// Configuration for the Sonora experimental mode with AEC3 enabled.
    /// Only valid when a render reference is available (INV_011).
    pub fn with_aec3() -> Self {
        Self {
            hpf: true,
            aec3: true,
            ns: true,
            agc2: true,
        }
    }

    /// Minimal configuration: HPF + AGC2 only (no AEC, no NS).
    /// Suitable for wired headset routes where AEC is not needed.
    pub fn headset() -> Self {
        Self {
            hpf: true,
            aec3: false,
            ns: false,
            agc2: true,
        }
    }
}

/// Full Sonora DSP chain: HPF → AEC3 → NS → AGC2.
pub struct SonoraProcessor {
    hpf: HighPassFilter,
    aec3: Aec3,
    ns: NoiseSuppressor,
    agc2: Agc2,
    config: SonoraConfig,
}

impl SonoraProcessor {
    /// Construct with the default configuration (AEC3 disabled).
    pub fn new() -> Self {
        let config = SonoraConfig::default();
        let mut aec3 = Aec3::new();
        aec3.set_enabled(config.aec3);
        let mut ns = NoiseSuppressor::new();
        ns.set_enabled(config.ns);
        let mut agc2 = Agc2::new();
        agc2.set_enabled(config.agc2);
        Self {
            hpf: HighPassFilter::new(),
            aec3,
            ns,
            agc2,
            config,
        }
    }

    /// Construct with a specific configuration.
    pub fn with_config(config: SonoraConfig) -> Self {
        let mut aec3 = Aec3::new();
        aec3.set_enabled(config.aec3);
        let mut ns = NoiseSuppressor::new();
        ns.set_enabled(config.ns);
        let mut agc2 = Agc2::new();
        agc2.set_enabled(config.agc2);
        Self {
            hpf: HighPassFilter::new(),
            aec3,
            ns,
            agc2,
            config,
        }
    }

    /// Apply a new configuration at runtime. Resets stages whose
    /// enable state changed to avoid state contamination.
    pub fn apply_config(&mut self, new_config: SonoraConfig) {
        if new_config.hpf != self.config.hpf {
            self.hpf.reset();
        }
        if new_config.aec3 != self.config.aec3 {
            self.aec3.reset();
            self.aec3.set_enabled(new_config.aec3);
        }
        if new_config.ns != self.config.ns {
            self.ns.reset();
            self.ns.set_enabled(new_config.ns);
        }
        if new_config.agc2 != self.config.agc2 {
            self.agc2.reset();
            self.agc2.set_enabled(new_config.agc2);
        }
        self.config = new_config;
    }

    /// Current configuration.
    pub fn config(&self) -> &SonoraConfig {
        &self.config
    }

    /// Reset all DSP state (call on route change or session restart).
    pub fn reset_all(&mut self) {
        self.hpf.reset();
        self.aec3.reset();
        self.ns.reset();
        self.agc2.reset();
    }

    /// Current AEC3 bulk delay estimate in blocks (1 block = 10 ms).
    pub fn aec3_bulk_delay_blocks(&self) -> usize {
        self.aec3.bulk_delay_blocks()
    }

    /// True if AEC3 has converged.
    pub fn aec3_converged(&self) -> bool {
        self.aec3.is_converged()
    }

    /// Current AGC2 speech probability from the RNN VAD.
    pub fn agc2_speech_probability(&self) -> f32 {
        self.agc2.speech_probability()
    }

    /// Current AGC2 gain in dB.
    pub fn agc2_gain_db(&self) -> f32 {
        self.agc2.gain_db()
    }
}

impl Default for SonoraProcessor {
    fn default() -> Self {
        Self::new()
    }
}

impl AudioProcessor for SonoraProcessor {
    /// Process one 10 ms capture frame in-place.
    ///
    /// Pipeline: HPF → AEC3 → NS → AGC2.
    fn process_capture(&mut self, frame: &mut [f32; FRAME_SAMPLES]) {
        // Stage 1: High-pass filter (DC removal, rumble suppression).
        if self.config.hpf {
            self.hpf.process(frame);
        }

        // Stage 2: AEC3 (echo cancellation).
        // AEC3 reads the render reference that was fed via process_render.
        // INV_011: only runs when aec3 is enabled (render reference available).
        self.aec3.process_capture(frame);

        // Stage 3: Noise suppression (Wiener filter).
        self.ns.process(frame);

        // Stage 4: AGC2 (gain control + limiter).
        self.agc2.process(frame);
    }

    /// Feed one 10 ms render-reference frame (decoded remote PCM
    /// before playout). Required by AEC3 (INV_012).
    fn process_render(&mut self, frame: &[f32; FRAME_SAMPLES]) {
        self.aec3.process_render(frame);
    }

    /// True when AEC3 is enabled (enforces INV_009 / INV_010).
    fn has_aec(&self) -> bool {
        self.config.aec3
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_config_has_aec3_disabled() {
        let p = SonoraProcessor::new();
        assert!(!p.has_aec(), "AEC3 must be disabled by default (INV_010)");
        assert!(p.config().hpf);
        assert!(p.config().ns);
        assert!(p.config().agc2);
    }

    #[test]
    fn with_aec3_config_enables_aec() {
        let p = SonoraProcessor::with_config(SonoraConfig::with_aec3());
        assert!(p.has_aec());
    }

    #[test]
    fn process_capture_does_not_panic_on_silence() {
        let mut p = SonoraProcessor::new();
        let mut frame = [0.0_f32; FRAME_SAMPLES];
        p.process_capture(&mut frame);
        assert!(frame.iter().all(|s| s.is_finite()));
    }

    #[test]
    fn process_capture_does_not_panic_on_loud_signal() {
        let mut p = SonoraProcessor::new();
        let mut frame = [1.0_f32; FRAME_SAMPLES];
        p.process_capture(&mut frame);
        assert!(frame.iter().all(|s| s.is_finite()));
    }

    #[test]
    fn hpf_removes_dc() {
        let mut p = SonoraProcessor::with_config(SonoraConfig {
            hpf: true,
            aec3: false,
            ns: false,
            agc2: false,
        });
        // Feed 200 frames of DC = 0.5.
        let mut frame = [0.5_f32; FRAME_SAMPLES];
        for _ in 0..200 {
            p.process_capture(&mut frame);
        }
        // After convergence, DC should be near zero.
        let mean: f32 = frame.iter().sum::<f32>() / FRAME_SAMPLES as f32;
        assert!(mean.abs() < 0.01, "DC not removed: mean={mean}");
    }

    #[test]
    fn apply_config_resets_changed_stages() {
        let mut p = SonoraProcessor::new();
        // Run some frames to build up state.
        let mut frame = [0.1_f32; FRAME_SAMPLES];
        for _ in 0..10 {
            p.process_capture(&mut frame);
        }
        // Enable AEC3 — should reset AEC3 state.
        p.apply_config(SonoraConfig::with_aec3());
        assert!(p.has_aec());
        assert!(!p.aec3_converged()); // reset clears convergence
    }

    #[test]
    fn suppresses_stationary_noise() {
        let mut p = SonoraProcessor::with_config(SonoraConfig {
            hpf: false,
            aec3: false,
            ns: true,
            agc2: false,
        });
        let mut rng: u32 = 0xABCD_1234;
        let noise_frame = |rng: &mut u32| -> [f32; FRAME_SAMPLES] {
            let mut f = [0.0_f32; FRAME_SAMPLES];
            for s in f.iter_mut() {
                *rng = rng.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
                *s = (*rng as i32 as f32) / i32::MAX as f32 * 0.05;
            }
            f
        };
        // Warm up noise floor.
        for _ in 0..30 {
            let mut frame = noise_frame(&mut rng);
            p.process_capture(&mut frame);
        }
        let mut frame = noise_frame(&mut rng);
        let before = rms(&frame);
        p.process_capture(&mut frame);
        let after = rms(&frame);
        assert!(
            after < before,
            "NS did not suppress noise: {before:.4} → {after:.4}"
        );
    }

    #[test]
    fn agc_amplifies_quiet_signal() {
        let mut p = SonoraProcessor::with_config(SonoraConfig {
            hpf: false,
            aec3: false,
            ns: false,
            agc2: true,
        });
        let mut frame = [0.0_f32; FRAME_SAMPLES];
        for (i, sample) in frame.iter_mut().enumerate() {
            *sample = (2.0 * std::f32::consts::PI * 300.0 * i as f32 / 48_000.0).sin() * 0.01;
        }
        let before = rms(&frame);
        for _ in 0..50 {
            p.process_capture(&mut frame);
        }
        let after = rms(&frame);
        assert!(
            after > before,
            "AGC did not amplify: {before:.4} → {after:.4}"
        );
    }

    fn rms(frame: &[f32]) -> f32 {
        let power = frame.iter().map(|s| s * s).sum::<f32>() / frame.len() as f32;
        power.sqrt()
    }
}
