//! WebRTC Audio Processing Module backend.
//!
//! This backend delegates capture-side voice processing to WebRTC APM
//! instead of Chanora's experimental Rust DSP chain. Frames are 10 ms,
//! mono, 48 kHz f32, matching the rest of the voice pipeline.

use sonora::config::{
    AdaptiveDigital, EchoCanceller, GainController2, HighPassFilter, NoiseSuppression,
    NoiseSuppressionLevel, Pipeline,
};
use sonora::{AudioProcessing, Config, StreamConfig};

use super::{AudioProcessor, FRAME_SAMPLES};

/// Per-module WebRTC APM enable flags.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WebRtcApmConfig {
    /// Acoustic echo cancellation.
    pub aec: bool,
    /// Automatic gain control.
    pub agc: bool,
    /// High-pass filter.
    pub hpf: bool,
    /// Noise suppression.
    pub ns: bool,
    /// WebRTC VAD is available for transmit gating.
    pub vad: bool,
}

impl WebRtcApmConfig {
    /// Resolve WebRTC APM flags from the shared audio-processing config.
    pub fn from_audio_config(config: &crate::AudioProcessingConfig) -> Self {
        Self {
            aec: config.aec == crate::EffectOwner::WebrtcApm,
            agc: matches!(
                config.agc,
                crate::EffectOwner::WebrtcApm | crate::EffectOwner::Conservative
            ),
            hpf: config.hpf_enabled,
            ns: matches!(
                config.ns,
                crate::EffectOwner::WebrtcApm | crate::EffectOwner::Conservative
            ),
            vad: config.vad_backend == crate::VadBackend::WebrtcVad,
        }
    }

    fn to_webrtc_config(self) -> Config {
        Config {
            pipeline: Pipeline {
                maximum_internal_processing_rate: sonora::config::MaxProcessingRate::Rate48kHz,
                ..Default::default()
            },
            high_pass_filter: self.hpf.then_some(HighPassFilter::default()),
            echo_canceller: self.aec.then_some(EchoCanceller::default()),
            noise_suppression: self.ns.then_some(NoiseSuppression {
                level: NoiseSuppressionLevel::Moderate,
                analyze_linear_aec_output_when_available: false,
            }),
            gain_controller2: self.agc.then_some(GainController2 {
                input_volume_controller: false,
                adaptive_digital: Some(AdaptiveDigital::default()),
                fixed_digital: Default::default(),
            }),
            ..Default::default()
        }
    }

    /// Stable runtime-log summary.
    pub fn summary(self) -> String {
        format!(
            "aec={} agc={} hpf={} ns={} vad={}",
            self.aec, self.agc, self.hpf, self.ns, self.vad
        )
    }
}

impl Default for WebRtcApmConfig {
    fn default() -> Self {
        Self {
            aec: true,
            agc: true,
            hpf: true,
            ns: true,
            vad: true,
        }
    }
}

/// WebRTC APM processor with preallocated render scratch.
pub struct WebRtcApmProcessor {
    processor: AudioProcessing,
    config: WebRtcApmConfig,
    capture_scratch: [f32; FRAME_SAMPLES],
    render_scratch: [f32; FRAME_SAMPLES],
}

impl WebRtcApmProcessor {
    /// Construct with a specific module configuration.
    pub fn with_config(config: WebRtcApmConfig) -> Result<Self, crate::AudioError> {
        let stream_config = StreamConfig::new(crate::frame::SAMPLE_RATE_HZ, 1);
        let processor = AudioProcessing::builder()
            .config(config.to_webrtc_config())
            .capture_config(stream_config)
            .render_config(stream_config)
            .echo_detector(config.aec)
            .build();
        tracing::info!(
            target: "chanora_audio",
            modules = %config.summary(),
            "WebRTC APM processor active"
        );
        Ok(Self {
            processor,
            config,
            capture_scratch: [0.0; FRAME_SAMPLES],
            render_scratch: [0.0; FRAME_SAMPLES],
        })
    }

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

    /// Apply updated module flags outside the realtime callback.
    pub fn apply_config(&mut self, config: WebRtcApmConfig) {
        if config == self.config {
            return;
        }
        self.processor.apply_config(config.to_webrtc_config());
        self.config = config;
        tracing::info!(
            target: "chanora_audio",
            modules = %config.summary(),
            "WebRTC APM processor reconfigured"
        );
    }
}

impl AudioProcessor for WebRtcApmProcessor {
    fn process_capture(&mut self, frame: &mut [f32; FRAME_SAMPLES]) {
        let channels = [&frame[..]];
        let mut out = [&mut self.capture_scratch[..]];
        if let Err(error) = self.processor.process_capture_f32(&channels, &mut out) {
            tracing::trace!(target: "chanora_audio", %error, "WebRTC APM capture frame skipped");
        } else {
            frame.copy_from_slice(&self.capture_scratch);
        }
    }

    fn process_render(&mut self, frame: &[f32; FRAME_SAMPLES]) {
        let channels = [&frame[..]];
        let mut out = [&mut self.render_scratch[..]];
        if let Err(error) = self.processor.process_render_f32(&channels, &mut out) {
            tracing::trace!(target: "chanora_audio", %error, "WebRTC APM render frame skipped");
        }
    }

    fn has_aec(&self) -> bool {
        self.config.aec
    }
}

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

    #[test]
    fn default_config_enables_all_modules() {
        let cfg = WebRtcApmConfig::default();
        assert!(cfg.aec && cfg.agc && cfg.hpf && cfg.ns && cfg.vad);
    }

    #[test]
    fn process_silence_is_stable() {
        let mut processor = WebRtcApmProcessor::with_config(WebRtcApmConfig {
            aec: false,
            agc: true,
            hpf: true,
            ns: true,
            vad: true,
        })
        .expect("webrtc apm init");
        let render = [0.0_f32; FRAME_SAMPLES];
        let mut capture = [0.0_f32; FRAME_SAMPLES];

        processor.process_render(&render);
        processor.process_capture(&mut capture);

        assert!(capture.iter().all(|s| s.is_finite()));
    }
}
