use std::{
    hash::{Hash, Hasher},
    str::FromStr,
};

/// Metadata for a diagnostic category
///
/// This type cannot be instantiated outside of the `biome_diagnostics_categories`
/// crate, which serves as a registry for all known diagnostic categories
/// (currently this registry is fully static and generated at compile time)
#[derive(Debug)]
pub struct Category {
    name: &'static str,
    link: Option<&'static str>,
}

impl Category {
    /// Return the name of this category
    pub fn name(&self) -> &'static str {
        self.name
    }

    /// Return the hyperlink associated with this category if it has one
    ///
    /// This will generally be a link to the documentation page for diagnostics
    /// with this category
    pub fn link(&self) -> Option<&'static str> {
        self.link
    }
}

impl Eq for Category {}

impl PartialEq for Category {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

impl Hash for Category {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

#[cfg(feature = "serde")]
impl serde::Serialize for &'static Category {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.name().serialize(serializer)
    }
}

#[cfg(feature = "serde")]
struct CategoryVisitor;

#[cfg(feature = "serde")]
fn deserialize_parse<E: serde::de::Error>(code: &str) -> Result<&'static Category, E> {
    code.parse().map_err(|()| {
        serde::de::Error::custom(format_args!("failed to deserialize category from {code}"))
    })
}

#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for CategoryVisitor {
    type Value = &'static Category;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("a borrowed string")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        deserialize_parse(v)
    }

    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        deserialize_parse(v)
    }

    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        deserialize_parse(&v)
    }
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for &'static Category {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_str(CategoryVisitor)
    }
}

// Import the code generated by the build script from the content of `src/categories.rs`
include!(concat!(env!("OUT_DIR"), "/categories.rs"));
