use std::fmt;
use std::io;

#[derive(Debug)]
pub enum SKError {
    Io(io::Error),
    Compression(niffler::Error),
    /// Binary file has unrecognized magic bytes.
    BadMagic { expected: &'static str, got: [u8; 4] },
    /// File is too short to contain a required field.
    Truncated { context: &'static str },
    /// A field value is outside its valid range or cannot be parsed.
    InvalidData { context: &'static str, detail: String },
}

pub type SKResult<T> = Result<T, SKError>;

impl fmt::Display for SKError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SKError::Io(e) => write!(f, "I/O error: {e}"),
            SKError::Compression(e) => write!(f, "compression error: {e}"),
            SKError::BadMagic { expected, got } => {
                write!(f, "bad magic: expected {:?}, got {:?}", expected.as_bytes(), got)
            }
            SKError::Truncated { context } => write!(f, "truncated file: {context}"),
            SKError::InvalidData { context, detail } => {
                write!(f, "invalid data in {context}: {detail}")
            }
        }
    }
}

impl std::error::Error for SKError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            SKError::Io(e) => Some(e),
            SKError::Compression(e) => Some(e),
            _ => None,
        }
    }
}

impl From<io::Error> for SKError {
    fn from(e: io::Error) -> Self {
        SKError::Io(e)
    }
}

impl From<niffler::Error> for SKError {
    fn from(e: niffler::Error) -> Self {
        SKError::Compression(e)
    }
}
