// Copyright (C) 2025 Christian Mauduit <ufoot@ufoot.org>

use std::fmt;
use std::fmt::{Display, Formatter};

#[derive(Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Error {
    InvalidIndex(usize),
    InvalidXY((usize, usize)),
    InvalidXYZ((usize, usize, usize)),
    ReportBug(String),
    NotImplemented,
}

/// URL to report bugs.
///
/// This is used internally to provide context when something unexpected happens,
/// so that users can find find out which piece of software fails,
/// and how to contact author(s).
///
/// Alternatively, send a direct email to <ufoot@ufoot.org>.
pub const BUG_REPORT_URL: &str = "https://gitlab.com/ufoot/shortestpath/-/issues";

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

impl Error {
    pub fn invalid_index(index: usize) -> Error {
        Error::InvalidIndex(index)
    }

    pub fn invalid_xy(x: usize, y: usize) -> Error {
        Error::InvalidXY((x, y))
    }

    pub fn invalid_xyz(x: usize, y: usize, z: usize) -> Error {
        Error::InvalidXYZ((x, y, z))
    }

    pub fn report_bug(why: &str) -> Error {
        Error::ReportBug(why.to_string())
    }

    pub fn not_implemented() -> Error {
        Error::NotImplemented
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), fmt::Error> {
        match self {
            Error::InvalidIndex(e) => write!(f, "invalid index: {e}"),
            Error::InvalidXY(e) => write!(f, "invalid (x,y): ({},{})", e.0, e.1),
            Error::InvalidXYZ(e) => write!(f, "invalid (x,y,z): ({},{},{})", e.0, e.1, e.2),
            Error::ReportBug(e) => write!(
                f,
                "unexpected bug, please report issue on <{BUG_REPORT_URL}>: {e}"
            ),
            Error::NotImplemented => write!(f, "not implemented"),
        }
    }
}

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

    #[test]
    #[cfg(feature = "serde")]
    fn test_serde() {
        let error = Error::invalid_index(42);
        let json = serde_json::to_string(&error).unwrap();
        let deserialized: Error = serde_json::from_str(&json).unwrap();
        assert_eq!(error, deserialized);
    }
}
