//! Streaming primitives for live capture: VAD-gated chunking,
//! agreement-based commit policy, and bounded buffer management.
//!
//! These types are tested at the unit level. Integration into
//! `src-tauri/src/commands/live.rs` lands in follow-up commits so
//! threshold tuning can be validated against real microphone captures
//! rather than synthetic fixtures (brief items #21, #24, #25).

pub mod buffer_trim;
pub mod commit_policy;
pub mod rms_vad;

pub use buffer_trim::{sample_index_for_seconds, trim_buffer_to_commit_point};
pub use commit_policy::{CommitDecision, CommitPolicy, LocalAgreement, Token};
pub use rms_vad::RmsVadChunker;

/// A span of audio the VAD considers worth transcribing. `start_sample`
/// is an absolute index into the stream the `VadChunker` has been fed
/// since its last `reset`; `samples` is f32 PCM at the chunker's
/// configured sample rate.
#[derive(Debug, Clone)]
pub struct VadChunk {
    pub start_sample: u64,
    pub samples: Vec<f32>,
}

/// A streaming VAD-gated chunker.
///
/// Implementations accumulate incoming samples, decide whether the
/// current segment is speech using a score + hysteresis (brief item
/// #21), and emit `VadChunk`s when a speech region ends — or when an
/// in-progress speech region exceeds the configured max length so
/// Whisper is not fed a 30-second monolith.
///
/// `push` returns any chunks ready to dispatch; typical usage is
/// `for chunk in chunker.push(&samples) { dispatch(chunk); }` inside
/// the capture loop.
///
/// `flush` is called at end-of-session to emit any in-flight speech as
/// a final chunk (even if silence hasn't closed it).
///
/// `Send` because a chunker is owned by the live-session worker thread
/// and moved into `spawn_blocking`.
pub trait VadChunker: Send {
    /// Feed new samples. Returns any chunks the chunker has decided to
    /// emit as a result. An empty Vec means "still gathering".
    fn push(&mut self, samples: &[f32]) -> Vec<VadChunk>;

    /// End-of-session: emit any in-progress speech as chunks even
    /// though silence has not closed them. Returns an empty Vec if
    /// there is nothing buffered (or only sub-threshold samples).
    ///
    /// Returns `Vec<VadChunk>` rather than `Option<VadChunk>` because
    /// the zero-padded final frame can legitimately trigger both a
    /// mid-flush emission (end-of-utterance or `max_chunk_samples`)
    /// AND a closing emission if the backend stays in-speech after
    /// the mid-flush cut. The previous `Option` signature silently
    /// dropped the mid-flush chunk.
    fn flush(&mut self) -> Vec<VadChunk>;

    /// Drop accumulated state. Used between sessions on the same
    /// chunker instance (or after a context-window reset from the
    /// repetition detector — brief item #26).
    fn reset(&mut self);

    /// Absolute sample index of the next sample that will be fed via
    /// `push`. Exposed so the commit policy (#24) can compute sample
    /// offsets for its agreement window.
    fn next_sample_index(&self) -> u64;
}

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

    #[test]
    fn vad_chunker_trait_is_object_safe() {
        // Compile-time witness: keep the trait dyn-compatible so the
        // live-session worker can hold `Box<dyn VadChunker>` and swap
        // between RMS and Silero backends at runtime.
        let _: Option<Box<dyn VadChunker>> = None;
    }
}
