//! High-pass filter (HPF) — DC offset and low-frequency rumble removal.
//!
//! ## Design
//!
//! Second-order Butterworth high-pass biquad at 80 Hz / 48 kHz.
//! Coefficients computed with the bilinear transform:
//!
//!   fc = 80 Hz, fs = 48000 Hz, Q = 0.7071 (Butterworth)
//!   ω₀ = 2π·fc/fs = 0.010472
//!   α  = sin(ω₀)/(2Q) = 0.007396
//!
//!   b0 =  (1 + cos(ω₀))/2 =  0.994786
//!   b1 = -(1 + cos(ω₀))   = -1.989572
//!   b2 =  (1 + cos(ω₀))/2 =  0.994786
//!   a0 =   1 + α           =  1.007396
//!   a1 =  -2·cos(ω₀)       = -1.999890
//!   a2 =   1 - α           =  0.992604
//!
//! Normalised (divide by a0):
//!   b0n =  0.987449,  b1n = -1.974898,  b2n =  0.987449
//!   a1n = -1.985199,  a2n =  0.985299
//!
//! The filter is applied sample-by-sample using the Direct Form II
//! transposed structure, which is numerically stable for f32.
//!
//! ## Realtime safety
//!
//! No allocation, no I/O, no blocking. State is two f32 delay elements.

/// 80 Hz Butterworth HPF biquad coefficients (normalised, 48 kHz).
const B0: f32 = 0.987_449;
const B1: f32 = -1.974_898;
const B2: f32 = 0.987_449;
const A1: f32 = -1.985_199;
const A2: f32 = 0.985_299;

/// Second-order high-pass filter (80 Hz Butterworth, 48 kHz).
///
/// Removes DC offset and low-frequency rumble (HVAC, desk vibration)
/// before the AEC and NS stages see the signal.
#[derive(Debug, Clone)]
pub struct HighPassFilter {
    /// Direct Form II transposed delay element 1.
    w1: f32,
    /// Direct Form II transposed delay element 2.
    w2: f32,
}

impl Default for HighPassFilter {
    fn default() -> Self {
        Self { w1: 0.0, w2: 0.0 }
    }
}

impl HighPassFilter {
    /// Construct a new `HighPassFilter` with zeroed state.
    pub fn new() -> Self {
        Self::default()
    }

    /// Process one sample in-place. Realtime-safe.
    #[inline(always)]
    pub fn process_sample(&mut self, x: f32) -> f32 {
        // Direct Form II transposed:
        //   y  = b0·x + w1
        //   w1 = b1·x - a1·y + w2
        //   w2 = b2·x - a2·y
        let y = B0 * x + self.w1;
        self.w1 = B1 * x - A1 * y + self.w2;
        self.w2 = B2 * x - A2 * y;
        y
    }

    /// Process a frame in-place.
    pub fn process(&mut self, frame: &mut [f32]) {
        for s in frame.iter_mut() {
            *s = self.process_sample(*s);
        }
    }

    /// Reset filter state (call on session restart).
    pub fn reset(&mut self) {
        self.w1 = 0.0;
        self.w2 = 0.0;
    }
}

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

    #[test]
    fn dc_is_attenuated() {
        let mut hpf = HighPassFilter::new();
        // Feed 1000 samples of DC = 1.0 and check the output settles near 0.
        // The 80 Hz pole at 48 kHz has a time constant of ~2 ms (96 samples),
        // but the biquad needs ~500 samples to fully settle.
        let mut out = 0.0_f32;
        for _ in 0..1000 {
            out = hpf.process_sample(1.0);
        }
        assert!(
            out.abs() < 0.01,
            "DC not attenuated after 1000 samples: {out}"
        );
    }

    #[test]
    fn high_freq_passes() {
        let mut hpf = HighPassFilter::new();
        // 1 kHz sine at 48 kHz should pass with near-unity gain.
        let mut peak = 0.0_f32;
        for i in 0..480 {
            let x = (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / 48_000.0).sin();
            let y = hpf.process_sample(x);
            if i > 100 {
                // Skip transient
                peak = peak.max(y.abs());
            }
        }
        assert!(peak > 0.9, "1 kHz not passing: peak={peak}");
    }

    #[test]
    fn reset_clears_state() {
        let mut hpf = HighPassFilter::new();
        for _ in 0..100 {
            hpf.process_sample(1.0);
        }
        hpf.reset();
        assert_eq!(hpf.w1, 0.0);
        assert_eq!(hpf.w2, 0.0);
    }
}
