//! Observer trait for progress reporting during convergence.
//!
//! Replaces the old `verbose: bool` + `println!` path. Callers wire in any
//! observer that implements the trait; default methods are no-ops so users
//! override only what they need.

use crate::time::Time;

/// Receives progress callbacks during `History::converge`.
///
/// All methods have default no-op implementations; implement only what's
/// interesting.
pub trait Observer<T: Time>: Send + Sync {
    /// Called after each convergence iteration across the whole history.
    fn on_iteration_end(&self, _iter: usize, _max_step: (f64, f64)) {}

    /// Called after each time slice is processed within an iteration.
    fn on_batch_processed(&self, _time: &T, _slice_idx: usize, _n_events: usize) {}

    /// Called once when convergence completes (or max iters is reached).
    fn on_converged(&self, _iters: usize, _final_step: (f64, f64), _converged: bool) {}
}

/// ZST no-op observer; the default when none is configured.
#[derive(Copy, Clone, Debug, Default)]
pub struct NullObserver;

impl<T: Time> Observer<T> for NullObserver {}

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

    #[test]
    fn null_observer_compiles_for_i64() {
        let o = NullObserver;
        <NullObserver as Observer<i64>>::on_iteration_end(&o, 1, (0.0, 0.0));
        <NullObserver as Observer<i64>>::on_converged(&o, 5, (1e-6, 1e-6), true);
    }

    #[test]
    fn null_observer_compiles_for_untimed() {
        use crate::Untimed;
        let o = NullObserver;
        <NullObserver as Observer<Untimed>>::on_iteration_end(&o, 1, (0.0, 0.0));
    }
}
