use std::sync::{LazyLock, RwLock};

use string_interner::{DefaultBackend, DefaultSymbol, StringInterner};

static STRING_INTERNER: LazyLock<RwLock<StringInterner<DefaultBackend>>> =
    LazyLock::new(|| RwLock::new(StringInterner::default()));

/// An interned string identifier for efficient storage and comparison.
///
/// Identifiers are stored in a global string interner, allowing fast equality
/// checks and reduced memory usage for frequently used strings.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Ident(DefaultSymbol);

impl Ident {
    /// Creates a new interned identifier from a string slice.
    ///
    /// If the string already exists in the interner, returns the existing identifier.
    pub fn new(s: &str) -> Self {
        Self(STRING_INTERNER.write().unwrap().get_or_intern(s))
    }

    /// Resolves the identifier to its string representation.
    ///
    /// Returns a new `String` with the identifier's content.
    pub fn as_str(&self) -> String {
        STRING_INTERNER.read().unwrap().resolve(self.0).unwrap().to_string()
    }

    /// Resolves the identifier and passes it to a callback function.
    ///
    /// This is more efficient than `as_str()` when you don't need to own the string,
    /// as it avoids allocating a new `String`.
    pub fn resolve_with<F, R>(&self, f: F) -> R
    where
        F: FnOnce(&str) -> R,
    {
        let interner = STRING_INTERNER.read().unwrap();
        let resolved = interner.resolve(self.0).unwrap();
        f(resolved)
    }
}

impl Default for Ident {
    fn default() -> Self {
        Ident::new("")
    }
}

impl From<&str> for Ident {
    fn from(s: &str) -> Self {
        Self::new(s)
    }
}

impl From<String> for Ident {
    fn from(s: String) -> Self {
        Self::new(&s)
    }
}

impl std::fmt::Display for Ident {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.resolve_with(|s| write!(f, "{}", s))
    }
}

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

#[cfg(feature = "ast-json")]
impl<'de> serde::Deserialize<'de> for Ident {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Ok(Ident::new(&s))
    }
}

/// Returns all interned strings currently in the global string interner.
pub fn all_symbols() -> Vec<String> {
    STRING_INTERNER
        .read()
        .unwrap()
        .iter()
        .map(|(_, s)| s.to_string())
        .collect()
}

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

    #[test]
    fn test_ident_new_and_as_str() {
        let ident = Ident::new("hello");
        assert_eq!(ident.as_str(), "hello");
    }

    #[test]
    fn test_ident_from_str_and_string() {
        let ident1: Ident = "world".into();
        let ident2: Ident = String::from("world").into();
        assert_eq!(ident1, ident2);
        assert_eq!(ident1.as_str(), "world");
    }

    #[test]
    fn test_ident_display_trait() {
        let ident = Ident::new("display_test");
        let s = format!("{}", ident);
        assert_eq!(s, "display_test");
    }

    #[test]
    fn test_ident_resolve_with() {
        let ident = Ident::new("resolve");
        let len = ident.resolve_with(|s| s.len());
        assert_eq!(len, "resolve".len());
    }

    #[cfg(feature = "ast-json")]
    #[test]
    fn test_ident_serde() {
        let ident = Ident::new("serde_test");
        let serialized = serde_json::to_string(&ident).unwrap();
        assert_eq!(serialized, "\"serde_test\"");
        let deserialized: Ident = serde_json::from_str(&serialized).unwrap();
        assert_eq!(deserialized, ident);
    }
}
