//! Core types for Microformats2 parsing.
//!
//! This crate provides the fundamental data structures for representing
//! Microformats2 parsed documents, items, properties, and values.
//!
//! # Features
//!
//! - `metaformats` - Enable metaformats backcompat support for Open Graph and Twitter Cards
//! - `debug_flow` - Enable source tracking for debugging parsed values
//! - `per_element_lang` - Enable per-element language tracking for TextValue and UrlValue

pub mod class;
#[cfg(feature = "debug_flow")]
mod debug_types;
pub mod document;
pub mod error;
pub mod item;
pub mod property_value;
/// Link relation types.
pub mod relation;
pub mod temporal;
pub mod traits;

pub use class::{Class, KnownClass};
pub use document::Document;
pub use error::Error;
pub use item::{Item, Items, ValueKind};
pub use property_value::{
    Fragment, Image, NodeList, Properties, PropertyValue, PropertyWithMetadata,
};
pub use relation::{Relation, Relations};
pub use traits::{FindItemById, FindItemByProperty, FindItemByUrl, LanguageFilter};

#[cfg(feature = "debug_flow")]
pub use debug_types::*;

/// Alias for [`Item`], representing a parsed microformat.
pub type Microformat = Item;

pub use url::Url;

#[cfg(test)]
mod test;

/// A text value with optional language information.
///
/// When the `per_element_lang` feature is enabled, this struct can carry
/// a language tag for the text. Without the feature, it serializes
/// transparently as a plain string.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TextValue {
    value: String,
    #[cfg(feature = "per_element_lang")]
    lang: Option<String>,
}

impl TextValue {
    /// Creates a new TextValue from a string.
    pub fn new(value: String) -> Self {
        Self {
            value,
            #[cfg(feature = "per_element_lang")]
            lang: None,
        }
    }

    /// Creates a new TextValue with a language tag.
    #[cfg(feature = "per_element_lang")]
    pub fn with_lang(value: String, lang: impl Into<String>) -> Self {
        Self {
            value,
            lang: Some(lang.into()),
        }
    }

    /// Returns the language tag if present.
    #[cfg(feature = "per_element_lang")]
    pub fn lang(&self) -> Option<&str> {
        self.lang.as_deref()
    }
}

impl std::ops::Deref for TextValue {
    type Target = String;
    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl From<String> for TextValue {
    fn from(value: String) -> Self {
        Self::new(value)
    }
}

impl From<&str> for TextValue {
    fn from(value: &str) -> Self {
        Self::new(value.to_string())
    }
}

impl std::fmt::Display for TextValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.value.fmt(f)
    }
}

// Serialize TextValue transparently as a string when per_element_lang is NOT enabled
#[cfg(not(feature = "per_element_lang"))]
impl serde::Serialize for TextValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.value)
    }
}

// Serialize TextValue - as plain string when no lang, as object when lang is present
#[cfg(feature = "per_element_lang")]
impl serde::Serialize for TextValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        if let Some(ref lang) = self.lang {
            // Has language: serialize as object
            use serde::ser::SerializeStruct;
            let mut s = serializer.serialize_struct("TextValue", 2)?;
            s.serialize_field("value", &self.value)?;
            s.serialize_field("lang", lang)?;
            s.end()
        } else {
            // No language: serialize as plain string for backward compatibility
            serializer.serialize_str(&self.value)
        }
    }
}

// Deserialize TextValue from a string when per_element_lang is NOT enabled
#[cfg(not(feature = "per_element_lang"))]
impl<'de> serde::Deserialize<'de> for TextValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Ok(TextValue::new(s))
    }
}

// Deserialize TextValue from either a string or an object when per_element_lang IS enabled
#[cfg(feature = "per_element_lang")]
impl<'de> serde::Deserialize<'de> for TextValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::{self, MapAccess, Visitor};
        use std::fmt;

        struct TextValueVisitor;

        impl<'de> Visitor<'de> for TextValueVisitor {
            type Value = TextValue;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter
                    .write_str("a string or an object with \"value\" and optional \"lang\" fields")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(TextValue::new(value.to_string()))
            }

            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(TextValue::new(value))
            }

            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
            where
                M: MapAccess<'de>,
            {
                let mut value: Option<String> = None;
                let mut lang: Option<String> = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        "value" => {
                            if value.is_some() {
                                return Err(de::Error::duplicate_field("value"));
                            }
                            value = Some(map.next_value()?);
                        }
                        "lang" => {
                            if lang.is_some() {
                                return Err(de::Error::duplicate_field("lang"));
                            }
                            lang = Some(map.next_value()?);
                        }
                        _ => {
                            return Err(de::Error::unknown_field(key, &["value", "lang"]));
                        }
                    }
                }

                let value = value.ok_or_else(|| de::Error::missing_field("value"))?;
                Ok(TextValue { value, lang })
            }
        }

        deserializer.deserialize_any(TextValueVisitor)
    }
}

/// A URL value with optional language information.
///
/// When the `per_element_lang` feature is enabled, this struct can carry
/// a language tag for the URL. Without the feature, it serializes
/// transparently as a plain URL string.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct UrlValue {
    value: url::Url,
    #[cfg(feature = "per_element_lang")]
    lang: Option<String>,
}

impl UrlValue {
    /// Creates a new UrlValue from a URL.
    pub fn new(value: url::Url) -> Self {
        Self {
            value,
            #[cfg(feature = "per_element_lang")]
            lang: None,
        }
    }

    /// Creates a new UrlValue with a language tag.
    #[cfg(feature = "per_element_lang")]
    pub fn with_lang(value: url::Url, lang: impl Into<String>) -> Self {
        Self {
            value,
            lang: Some(lang.into()),
        }
    }

    /// Returns the language tag if present.
    #[cfg(feature = "per_element_lang")]
    pub fn lang(&self) -> Option<&str> {
        self.lang.as_deref()
    }
}

impl std::ops::Deref for UrlValue {
    type Target = url::Url;
    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl From<url::Url> for UrlValue {
    fn from(value: url::Url) -> Self {
        Self::new(value)
    }
}

impl std::fmt::Display for UrlValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.value.fmt(f)
    }
}

// Serialize UrlValue transparently as a URL string when per_element_lang is NOT enabled
#[cfg(not(feature = "per_element_lang"))]
impl serde::Serialize for UrlValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(self.value.as_str())
    }
}

// Serialize UrlValue - as plain string when no lang, as object when lang is present
#[cfg(feature = "per_element_lang")]
impl serde::Serialize for UrlValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        if let Some(ref lang) = self.lang {
            // Has language: serialize as object
            use serde::ser::SerializeStruct;
            let mut s = serializer.serialize_struct("UrlValue", 2)?;
            s.serialize_field("value", self.value.as_str())?;
            s.serialize_field("lang", lang)?;
            s.end()
        } else {
            // No language: serialize as plain string for backward compatibility
            serializer.serialize_str(self.value.as_str())
        }
    }
}

// Deserialize UrlValue from a URL string when per_element_lang is NOT enabled
#[cfg(not(feature = "per_element_lang"))]
impl<'de> serde::Deserialize<'de> for UrlValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        let url = url::Url::parse(&s).map_err(serde::de::Error::custom)?;
        Ok(UrlValue::new(url))
    }
}

// Deserialize UrlValue from either a URL string or an object when per_element_lang IS enabled
#[cfg(feature = "per_element_lang")]
impl<'de> serde::Deserialize<'de> for UrlValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::{self, MapAccess, Visitor};
        use std::fmt;

        struct UrlValueVisitor;

        impl<'de> Visitor<'de> for UrlValueVisitor {
            type Value = UrlValue;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str(
                    "a URL string or an object with \"value\" and optional \"lang\" fields",
                )
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                let url = url::Url::parse(value).map_err(de::Error::custom)?;
                Ok(UrlValue::new(url))
            }

            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                let url = url::Url::parse(&value).map_err(de::Error::custom)?;
                Ok(UrlValue::new(url))
            }

            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
            where
                M: MapAccess<'de>,
            {
                let mut value: Option<String> = None;
                let mut lang: Option<String> = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        "value" => {
                            if value.is_some() {
                                return Err(de::Error::duplicate_field("value"));
                            }
                            value = Some(map.next_value()?);
                        }
                        "lang" => {
                            if lang.is_some() {
                                return Err(de::Error::duplicate_field("lang"));
                            }
                            lang = Some(map.next_value()?);
                        }
                        _ => {
                            return Err(de::Error::unknown_field(key, &["value", "lang"]));
                        }
                    }
                }

                let value_str = value.ok_or_else(|| de::Error::missing_field("value"))?;
                let url = url::Url::parse(&value_str).map_err(de::Error::custom)?;
                Ok(UrlValue { value: url, lang })
            }
        }

        deserializer.deserialize_any(UrlValueVisitor)
    }
}

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

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn text_value_without_lang_serializes_as_string() {
        let tv = TextValue::new("hello".to_string());
        // Without lang, it should serialize as a plain string for backward compatibility
        assert_eq!(serde_json::to_string(&tv).unwrap(), r#""hello""#);
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn text_value_with_lang_serializes_as_object() {
        let tv = TextValue::with_lang("hello".to_string(), "en");
        let json = serde_json::to_string(&tv).unwrap();
        assert!(json.contains(r#""value":"hello""#));
        assert!(json.contains(r#""lang":"en""#));
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn text_value_deserializes_string() {
        let tv: TextValue = serde_json::from_str("\"hello\"").unwrap();
        assert_eq!(&*tv, "hello");
        assert_eq!(tv.lang(), None);
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn text_value_deserializes_object() {
        let tv: TextValue = serde_json::from_str("{\"value\":\"hello\",\"lang\":\"en\"}").unwrap();
        assert_eq!(&*tv, "hello");
        assert_eq!(tv.lang(), Some("en"));
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn url_value_without_lang_serializes_as_string() {
        let uv = UrlValue::new("https://example.com".parse().unwrap());
        // Without lang, it should serialize as a plain string for backward compatibility
        assert_eq!(
            serde_json::to_string(&uv).unwrap(),
            r#""https://example.com/""#
        );
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn url_value_with_lang_serializes_as_object() {
        let uv = UrlValue::with_lang("https://example.com".parse().unwrap(), "en");
        let json = serde_json::to_string(&uv).unwrap();
        assert!(json.contains(r#""value":"https://example.com/""#));
        assert!(json.contains(r#""lang":"en""#));
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn text_value_deref_works() {
        let tv = TextValue::new("hello".to_string());
        assert_eq!(tv.len(), 5); // Deref to String
        assert_eq!(&tv[..], "hello");
    }

    #[test]
    #[cfg(feature = "per_element_lang")]
    fn url_value_deref_works() {
        let uv = UrlValue::new("https://example.com/path".parse().unwrap());
        assert_eq!(uv.path(), "/path"); // Deref to Url
    }
}
