use std::fmt;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Clone, PartialEq)]
pub enum Error {
    InvalidRange { min: f64, max: f64 },
    InvalidBitWidth(u8),
    InvalidCanId(u32),
    PayloadTooLong { len: usize },
    PayloadTooShort { expected: usize, actual: usize },
    InvalidRefreshRate(f64),
    DuplicateMotorId(u8),
    UnknownMotorId(u8),
    UnknownMotorLabel(String),
    InvalidTraceLine { line: usize, reason: String },
    Transport(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidRange { min, max } => {
                write!(
                    f,
                    "invalid range: max ({max}) must be greater than min ({min})"
                )
            }
            Self::InvalidBitWidth(bits) => write!(f, "invalid bit width: {bits}"),
            Self::InvalidCanId(id) => write!(f, "invalid CAN id: 0x{id:X}"),
            Self::PayloadTooLong { len } => write!(f, "CAN payload too long: {len} bytes"),
            Self::PayloadTooShort { expected, actual } => {
                write!(
                    f,
                    "payload too short: expected {expected} bytes, got {actual}"
                )
            }
            Self::InvalidRefreshRate(rate) => write!(f, "invalid refresh rate: {rate} Hz"),
            Self::DuplicateMotorId(id) => write!(f, "duplicate motor id: 0x{id:02X}"),
            Self::UnknownMotorId(id) => write!(f, "unknown motor id: 0x{id:02X}"),
            Self::UnknownMotorLabel(label) => write!(f, "unknown motor label: {label}"),
            Self::InvalidTraceLine { line, reason } => {
                write!(f, "invalid CAN trace line {line}: {reason}")
            }
            Self::Transport(message) => write!(f, "CAN transport error: {message}"),
        }
    }
}

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