use core::{
    error::Error as StdError,
    fmt::{Debug as FmtDebug, Formatter, Result as FmtResult},
};
use std::io::Error as IoError;

#[derive(thiserror::Error)]
pub enum Error {
    #[error("unable to create snapshot")]
    Image(#[from] crate::image::Error),

    #[error("unable to read memory")]
    Memory(#[from] crate::snapshot::Error),

    #[error("unable to parse /proc/iomem")]
    Iomem(#[from] crate::iomem::Error),

    #[cfg(feature = "put")]
    #[error("unable to upload file via PUT")]
    Upload(#[from] crate::upload::http::Error),

    #[cfg(feature = "blobstore")]
    #[error("unable to upload file to Azure Storage")]
    Blob(#[from] crate::upload::blobstore::Error),

    #[error("io error: {context}")]
    Io {
        context: &'static str,
        #[source]
        source: IoError,
    },

    #[error("no conversion required")]
    NoConversionRequired,
}

pub(crate) fn format_error(e: &impl StdError, f: &mut Formatter) -> FmtResult {
    write!(f, "error: {e}")?;

    let Some(first) = e.source() else {
        return Ok(());
    };

    writeln!(f, "\ncaused by:")?;
    let mut source: Option<&dyn StdError> = Some(first);
    let mut i: usize = 0;
    while let Some(inner) = source {
        writeln!(f, "{i: >5}: {inner}")?;
        source = inner.source();
        i = i.saturating_add(1);
    }

    Ok(())
}

impl FmtDebug for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        format_error(self, f)
    }
}
