use std::fmt;

#[derive(Debug, Clone, PartialEq)]
pub enum InferenceError {
    /// Expected and actual lengths of some array-shaped input differ.
    MismatchedShape {
        kind: &'static str,
        expected: usize,
        got: usize,
    },
    /// A probability value is outside `[0, 1]`.
    InvalidProbability { value: f64 },
    /// A scalar parameter is outside its valid range.
    InvalidParameter { name: &'static str, value: f64 },
    /// Convergence exceeded `max_iter` without falling below `epsilon`.
    ConvergenceFailed {
        last_step: (f64, f64),
        iterations: usize,
    },
    /// Negative precision: a Gaussian with `pi < 0` slipped into an API call.
    NegativePrecision { pi: f64 },
}

impl fmt::Display for InferenceError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::MismatchedShape {
                kind,
                expected,
                got,
            } => {
                write!(f, "{kind}: expected length {expected}, got {got}")
            }
            Self::InvalidProbability { value } => {
                write!(f, "probability must be in [0, 1]; got {value}")
            }
            Self::InvalidParameter { name, value } => {
                write!(f, "{name} is invalid: {value}")
            }
            Self::ConvergenceFailed {
                last_step,
                iterations,
            } => {
                write!(
                    f,
                    "convergence failed after {iterations} iterations; last step = {last_step:?}"
                )
            }
            Self::NegativePrecision { pi } => {
                write!(f, "precision must be non-negative; got {pi}")
            }
        }
    }
}

impl std::error::Error for InferenceError {}
