use proc_macro2::Span;

#[derive(Debug, Clone)]
pub enum Error {
    Syn(syn::Error),
    Custom {
        span: Span,
        error: String,
        help: Option<String>,
    },
}

impl Error {
    pub fn new<E>(error: E, span: Span, help: Option<String>) -> Self
    where
        E: Into<String>,
    {
        let error = error.into();
        Self::Custom { error, help, span }
    }

    pub fn message<E>(error: E) -> Self
    where
        E: Into<String>,
    {
        let error = error.into();

        Self::Custom {
            error,
            help: None,
            span: Span::call_site(),
        }
    }
}

impl From<syn::Error> for Error {
    fn from(err: syn::Error) -> Self {
        Error::Syn(err)
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::Syn(err) => err.fmt(f),
            Error::Custom { error, .. } => {
                write!(f, "a custom error: {error}")
            }
        }
    }
}

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

pub fn abort(err: Error) -> ! {
    match err {
        Error::Syn(err) => {
            proc_macro_error2::abort! {err.span(), "{}", err}
        }
        Error::Custom { span, error, help } => match help {
            Some(help) => {
                proc_macro_error2::abort! {span, "{}",  error; help="{}", help}
            }
            None => {
                proc_macro_error2::abort! {span, "{}",  error}
            }
        },
    }
}
