//! Canonical P1 voice frame helpers.
//!
//! The network contract remains 48 kHz mono, 20 ms Opus frames. P1
//! processing works internally on 10 ms f32 frames so VAD and future
//! processors can share a stable frame size without changing the
//! transport layer.

/// P1 sample rate in Hz.
pub const SAMPLE_RATE_HZ: u32 = 48_000;
/// Network frame duration in milliseconds.
pub const NETWORK_FRAME_MS: u32 = 20;
/// Processing frame duration in milliseconds.
pub const PROCESSING_FRAME_MS: u32 = 10;
/// Samples in one 10 ms mono frame at 48 kHz.
pub const FRAME_10MS_SAMPLES: usize = 480;
/// Samples in one 20 ms mono frame at 48 kHz.
pub const FRAME_20MS_SAMPLES: usize = 960;

/// 10 ms, 48 kHz, mono f32 processing frame.
#[derive(Debug, Clone, PartialEq)]
pub struct AudioFrame10ms {
    /// Samples normalized to `[-1.0, 1.0]`.
    pub samples: [f32; FRAME_10MS_SAMPLES],
}

/// 20 ms, 48 kHz, mono f32 network-frame-sized buffer.
#[derive(Debug, Clone, PartialEq)]
pub struct AudioFrame20ms {
    /// Samples normalized to `[-1.0, 1.0]`.
    pub samples: [f32; FRAME_20MS_SAMPLES],
}

impl AudioFrame20ms {
    /// Convert one 20 ms frame into two 10 ms processing frames.
    pub fn split(&self) -> (AudioFrame10ms, AudioFrame10ms) {
        let mut first = [0.0; FRAME_10MS_SAMPLES];
        let mut second = [0.0; FRAME_10MS_SAMPLES];
        first.copy_from_slice(&self.samples[..FRAME_10MS_SAMPLES]);
        second.copy_from_slice(&self.samples[FRAME_10MS_SAMPLES..]);
        (
            AudioFrame10ms { samples: first },
            AudioFrame10ms { samples: second },
        )
    }
}

impl AudioFrame10ms {
    /// Merge two 10 ms processing frames back into the 20 ms network
    /// cadence used by the existing Opus path.
    pub fn merge(first: &Self, second: &Self) -> AudioFrame20ms {
        let mut samples = [0.0; FRAME_20MS_SAMPLES];
        samples[..FRAME_10MS_SAMPLES].copy_from_slice(&first.samples);
        samples[FRAME_10MS_SAMPLES..].copy_from_slice(&second.samples);
        AudioFrame20ms { samples }
    }

    /// Compute RMS dBFS for diagnostics and fallback VAD.
    pub fn dbfs(&self) -> f32 {
        dbfs(&self.samples)
    }
}

/// Convert i16 PCM to normalized f32 PCM.
pub fn i16_to_f32(sample: i16) -> f32 {
    sample as f32 / i16::MAX as f32
}

/// Convert normalized f32 PCM to saturated i16 PCM.
pub fn f32_to_i16(sample: f32) -> i16 {
    (sample.clamp(-1.0, 1.0) * i16::MAX as f32) as i16
}

/// RMS dBFS for a normalized f32 slice. Silence returns `-120.0`.
pub fn dbfs(samples: &[f32]) -> f32 {
    if samples.is_empty() {
        return -120.0;
    }
    let sum = samples.iter().map(|s| s * s).sum::<f32>();
    let rms = (sum / samples.len() as f32).sqrt();
    if rms <= 0.000_001 {
        -120.0
    } else {
        20.0 * rms.log10()
    }
}

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

    #[test]
    fn split_merge_preserves_samples() {
        let mut samples = [0.0; FRAME_20MS_SAMPLES];
        for (i, s) in samples.iter_mut().enumerate() {
            *s = i as f32 / FRAME_20MS_SAMPLES as f32;
        }
        let original = AudioFrame20ms { samples };
        let (a, b) = original.split();
        assert_eq!(AudioFrame10ms::merge(&a, &b), original);
    }
}
