// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
// SPDX-License-Identifier: Apache-2.0

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

#[derive(Debug)]
pub enum Error {
    Io(std::io::Error),
    NotFound(String),
    Library(libloading::Error),
    JpegEncoding(jpeg_encoder::EncodingError),
    JpegDecoding(zune_jpeg::errors::DecodeErrors),
    PngDecoding(zune_png::error::PngDecodeErrors),
    ResizeImageBuffer(fast_image_resize::ImageBufferError),
    Resize(fast_image_resize::ResizeError),
    Yuv(yuv::YuvError),
    #[cfg(target_os = "linux")]
    G2D(g2d_sys::Error),
    Tensor(edgefirst_tensor::Error),
    NotImplemented(String),
    NotSupported(String),
    InvalidShape(String),
    /// Two tensor arguments that must be distinct reference the same
    /// underlying buffer (e.g. `dst` and `background` in a draw call).
    AliasedBuffers(String),
    #[cfg(target_os = "linux")]
    #[cfg(feature = "opengl")]
    EGL(khronos_egl::Error),
    GLVersion(String),
    #[cfg(target_os = "linux")]
    #[cfg(feature = "opengl")]
    EGLLoad(khronos_egl::LoadError<libloading::Error>),
    #[cfg(target_os = "linux")]
    #[cfg(feature = "opengl")]
    GbmInvalidFd(gbm::InvalidFdError),
    #[cfg(target_os = "linux")]
    #[cfg(feature = "opengl")]
    GbmFrontBuffer(gbm::FrontBufferError),
    OpenGl(String),
    Internal(String),
    CropInvalid(String),
    ForcedBackendUnavailable(String),
    NoConverter,
    NotAnImage,
    UnsupportedFormat(String),
}

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

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

impl From<jpeg_encoder::EncodingError> for Error {
    fn from(err: jpeg_encoder::EncodingError) -> Self {
        Error::JpegEncoding(err)
    }
}

impl From<zune_jpeg::errors::DecodeErrors> for Error {
    fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
        Error::JpegDecoding(err)
    }
}

impl From<zune_png::error::PngDecodeErrors> for Error {
    fn from(err: zune_png::error::PngDecodeErrors) -> Self {
        Error::PngDecoding(err)
    }
}

impl From<fast_image_resize::ImageBufferError> for Error {
    fn from(err: fast_image_resize::ImageBufferError) -> Self {
        Error::ResizeImageBuffer(err)
    }
}

impl From<fast_image_resize::ResizeError> for Error {
    fn from(err: fast_image_resize::ResizeError) -> Self {
        Error::Resize(err)
    }
}

impl From<yuv::YuvError> for Error {
    fn from(err: yuv::YuvError) -> Self {
        Error::Yuv(err)
    }
}

#[cfg(target_os = "linux")]
impl From<g2d_sys::Error> for Error {
    fn from(err: g2d_sys::Error) -> Self {
        Error::G2D(err)
    }
}

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

#[cfg(target_os = "linux")]
#[cfg(feature = "opengl")]
impl From<khronos_egl::Error> for Error {
    fn from(err: khronos_egl::Error) -> Self {
        Error::EGL(err)
    }
}

#[cfg(target_os = "linux")]
#[cfg(feature = "opengl")]
impl From<khronos_egl::LoadError<libloading::Error>> for Error {
    fn from(err: khronos_egl::LoadError<libloading::Error>) -> Self {
        Error::EGLLoad(err)
    }
}

#[cfg(target_os = "linux")]
#[cfg(feature = "opengl")]
impl From<gbm::InvalidFdError> for Error {
    fn from(err: gbm::InvalidFdError) -> Self {
        Error::GbmInvalidFd(err)
    }
}

#[cfg(target_os = "linux")]
#[cfg(feature = "opengl")]
impl From<gbm::FrontBufferError> for Error {
    fn from(err: gbm::FrontBufferError) -> Self {
        Error::GbmFrontBuffer(err)
    }
}

#[cfg(target_os = "linux")]
#[cfg(feature = "opengl")]
impl From<gls::Error> for Error {
    fn from(err: gls::Error) -> Self {
        Error::OpenGl(err.to_string())
    }
}

impl From<ndarray::ShapeError> for Error {
    fn from(err: ndarray::ShapeError) -> Self {
        Error::Internal(format!("{err}"))
    }
}

impl From<fast_image_resize::CropBoxError> for Error {
    fn from(err: fast_image_resize::CropBoxError) -> Self {
        Error::Internal(format!("{err}"))
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_image_error_display() {
        let e = Error::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            "file missing",
        ));
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("Io") && msg.contains("file missing"),
            "unexpected Io message: {msg}"
        );

        let e = Error::NotFound("texture.png".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("NotFound") && msg.contains("texture.png"),
            "unexpected NotFound message: {msg}"
        );

        let e = Error::Tensor(edgefirst_tensor::Error::InvalidSize(42));
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("Tensor") && msg.contains("InvalidSize"),
            "unexpected Tensor message: {msg}"
        );

        let e = Error::NotImplemented("webp".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("NotImplemented") && msg.contains("webp"),
            "unexpected NotImplemented message: {msg}"
        );

        let e = Error::NotSupported("bmp format".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("NotSupported") && msg.contains("bmp format"),
            "unexpected NotSupported message: {msg}"
        );

        let e = Error::InvalidShape("wrong dims".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("InvalidShape") && msg.contains("wrong dims"),
            "unexpected InvalidShape message: {msg}"
        );

        let e = Error::GLVersion("4.5 required".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("GLVersion") && msg.contains("4.5 required"),
            "unexpected GLVersion message: {msg}"
        );

        let e = Error::OpenGl("shader compile failed".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("OpenGl") && msg.contains("shader compile failed"),
            "unexpected OpenGl message: {msg}"
        );

        let e = Error::Internal("unexpected state".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("Internal") && msg.contains("unexpected state"),
            "unexpected Internal message: {msg}"
        );

        let e = Error::CropInvalid("out of bounds".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("CropInvalid") && msg.contains("out of bounds"),
            "unexpected CropInvalid message: {msg}"
        );

        let e = Error::ForcedBackendUnavailable("g2d".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("ForcedBackendUnavailable") && msg.contains("g2d"),
            "unexpected ForcedBackendUnavailable message: {msg}"
        );

        let e = Error::NoConverter;
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("NoConverter"),
            "unexpected NoConverter message: {msg}"
        );

        let e = Error::NotAnImage;
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("NotAnImage"),
            "unexpected NotAnImage message: {msg}"
        );

        let e = Error::UnsupportedFormat("tiff".to_string());
        let msg = e.to_string();
        assert!(!msg.is_empty());
        assert!(
            msg.contains("UnsupportedFormat") && msg.contains("tiff"),
            "unexpected UnsupportedFormat message: {msg}"
        );
    }
}
