use std::fmt;

#[derive(Debug)]
pub struct ContextError {
    pub context: String,
    pub source: Box<dyn std::error::Error + Send + Sync>,
}

impl fmt::Display for ContextError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.context, self.source)
    }
}

impl std::error::Error for ContextError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(self.source.as_ref())
    }
}

pub trait ErrorContext<T> {
    fn context<C: Into<String>>(self, ctx: C) -> Result<T, ContextError>;
}

impl<T, E: std::error::Error + Send + Sync + 'static> ErrorContext<T> for Result<T, E> {
    fn context<C: Into<String>>(self, ctx: C) -> Result<T, ContextError> {
        self.map_err(|e| ContextError {
            context: ctx.into(),
            source: Box::new(e),
        })
    }
}

pub fn with_context<T, E, F, C>(result: Result<T, E>, f: F) -> Result<T, ContextError>
where
    E: std::error::Error + Send + Sync + 'static,
    F: FnOnce(&E) -> C,
    C: Into<String>,
{
    result.map_err(|e| ContextError {
        context: f(&e).into(),
        source: Box::new(e),
    })
}