//! Microformat item types.

use crate::{Class, Fragment, LanguageFilter, Properties, PropertyValue};
use serde::de::{self, Visitor};
use serde::ser::{SerializeMap, SerializeSeq};
use std::collections::HashSet;
use std::iter::FromIterator;
use std::ops::Deref;
use std::ops::DerefMut;

/// The kind of value for an item's value property.
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged, rename_all = "kebab-case")]
pub enum ValueKind {
    /// A URL value.
    Url(url::Url),
    /// A plain text value.
    Plain(String),
}

impl Default for ValueKind {
    fn default() -> Self {
        Self::Plain(String::default())
    }
}

/// A parsed microformat item.
#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Eq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Item {
    /// The microformat types (e.g., h-entry, h-card).
    pub r#type: Vec<Class>,

    /// The properties of this item.
    #[serde(default, with = "referenced_properties")]
    pub properties: Properties,

    /// Child items nested within this item.
    #[serde(
        default,
        with = "referenced_children",
        skip_serializing_if = "referenced_children::is_empty"
    )]
    pub children: Items,

    /// The HTML id attribute of the element.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,

    /// The language of this item.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub lang: Option<String>,

    /// The value property for value-class-pattern items.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<ValueKind>,
}

impl std::fmt::Debug for Item {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Item")
            .field("type", &self.r#type)
            .field("id", &self.id)
            .field("value", &self.value)
            .field("lang", &self.lang)
            .finish()
    }
}

impl Item {
    /// Creates a new item with the given types.
    pub fn new(types: Vec<Class>) -> Self {
        Item {
            r#type: types,
            ..Default::default()
        }
    }

    /// Returns true if this item has no children and no types.
    pub fn is_empty(&self) -> bool {
        self.children.is_empty() && self.r#type.is_empty()
    }

    /// Removes all values for a property.
    pub fn remove_whole_property(&mut self, property_name: &str) {
        self.properties.remove(property_name);
    }

    /// Returns the content property values, if any.
    pub fn content(&self) -> Option<Vec<PropertyValue>> {
        self.properties.get("content").cloned()
    }

    /// Sets the content property to a fragment value.
    pub fn set_content(&mut self, fragment: Fragment) {
        self.properties.insert(
            "content".to_string(),
            vec![PropertyValue::Fragment(fragment)],
        );
    }

    /// Appends a value to a property.
    pub fn append_property(&mut self, property_name: &str, property_value: PropertyValue) {
        let mut new_values = if let Some(values) = self.properties.get(property_name) {
            values.to_vec()
        } else {
            Vec::default()
        };

        new_values.push(property_value);
        self.properties.insert(property_name.to_owned(), new_values);
    }

    /// Returns true if this item contains nested microformats.
    pub fn has_nested_microformats(&self) -> bool {
        let has_nested_value_microformats = self
            .properties
            .values()
            .flatten()
            .any(|v| matches!(v, PropertyValue::Item(_)));

        has_nested_value_microformats || !self.children.is_empty()
    }

    /// Returns nested items from property values.
    pub fn nested_children(&self) -> Vec<Item> {
        self.properties
            .values()
            .flatten()
            .filter_map(|value| {
                if let PropertyValue::Item(item) = value {
                    Some(item)
                } else {
                    None
                }
            })
            .cloned()
            .collect::<Vec<_>>()
    }

    /// Returns the values for a property.
    pub fn get_property(&self, arg: &str) -> Option<Vec<PropertyValue>> {
        self.properties.get(arg).cloned()
    }
}

impl TryFrom<serde_json::Map<String, serde_json::Value>> for Item {
    type Error = crate::Error;

    fn try_from(obj: serde_json::Map<String, serde_json::Value>) -> Result<Self, Self::Error> {
        if !obj.contains_key("type") {
            return Err(Self::Error::JsonObjectMissingProperty("type".to_string()));
        }
        if !obj.contains_key("properties") {
            return Err(Self::Error::JsonObjectMissingProperty(
                "properties".to_string(),
            ));
        }

        serde_json::from_value(serde_json::Value::Object(obj)).map_err(Self::Error::JSON)
    }
}

impl TryFrom<serde_json::Value> for Item {
    type Error = crate::Error;

    fn try_from(v: serde_json::Value) -> Result<Self, Self::Error> {
        if let serde_json::Value::Object(o) = v {
            Self::try_from(o)
        } else {
            Err(Self::Error::NotAnObject)
        }
    }
}

impl TryInto<serde_json::Value> for Item {
    type Error = crate::Error;

    fn try_into(self) -> Result<serde_json::Value, Self::Error> {
        serde_json::to_value(self).map_err(crate::Error::JSON)
    }
}

impl IntoIterator for Item {
    type Item = Item;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        let mut items = self
            .children
            .iter()
            .flat_map(|i| i.clone().into_iter())
            .collect::<Vec<Self::Item>>();
        items.push(self);
        items.into_iter()
    }
}

/// A collection of items.
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Items(Vec<Item>);

impl From<Vec<Item>> for Items {
    fn from(value: Vec<Item>) -> Self {
        Self(value)
    }
}

impl Items {
    /// Creates a new child item with the given types and adds it to the collection.
    pub fn create_child_item(&mut self, types: &[Class]) -> Item {
        let item = Item::new(types.to_vec());
        self.0.push(item.to_owned());
        item
    }

    /// Finds an item by ID.
    pub fn get_by_id(&self, id: &str) -> Option<Item> {
        self.iter()
            .flat_map(|item| item.clone().into_iter())
            .find(|item| item.id == Some(id.to_string()))
            .clone()
    }

    /// Finds an item by URL value.
    pub fn get_by_url(&self, url: &url::Url) -> Option<Item> {
        self.iter()
            .flat_map(|item| item.clone().into_iter())
            .find(|item| item.value == Some(ValueKind::Url(url.to_owned())))
            .clone()
    }

    /// Creates a new Items collection with pre-allocated capacity.
    pub fn with_capacity(size_hint: usize) -> Items {
        Items(Vec::with_capacity(size_hint))
    }
}

impl DerefMut for Items {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Deref for Items {
    type Target = Vec<Item>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

mod referenced_properties {
    use super::*;
    use crate::{NodeList, Properties};

    type Value = Properties;

    struct PropertyVisitor;

    #[derive(serde::Deserialize, Debug)]
    #[serde(untagged)]
    enum PotentialPropertyValue {
        List(NodeList),
        Value(PropertyValue),
    }

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

        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            formatter.write_str("a map of properties with values that could be null, a string, a list of either strings, maps or both")
        }

        fn visit_map<A>(self, mut map_visitor: A) -> Result<Self::Value, A::Error>
        where
            A: de::MapAccess<'de>,
        {
            let mut property_map = Properties::default();

            while let Some(key) = map_visitor.next_key()? {
                let concrete_value: NodeList =
                    match map_visitor.next_value::<PotentialPropertyValue>() {
                        Ok(PotentialPropertyValue::List(values)) => values,
                        Ok(PotentialPropertyValue::Value(node)) => vec![node],
                        Err(_) => vec![],
                    };

                if let Some(values) = property_map.get_mut(&key) {
                    values.extend(concrete_value);
                } else {
                    property_map.insert(key, concrete_value);
                }
            }

            Ok(property_map)
        }
    }

    pub fn serialize<S>(properties: &Value, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        let mut properties_seq = serializer.serialize_map(Some(properties.len()))?;

        for (key, value) in properties.iter() {
            properties_seq.serialize_entry(key, value)?;
        }

        properties_seq.end()
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_map(PropertyVisitor)
    }
}

mod referenced_children {
    use super::*;

    type Value = Items;

    struct ChildrenVisitor;

    impl<'de> Visitor<'de> for ChildrenVisitor {
        type Value = Value;
        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            formatter.write_str("expecting a list of children nodes, an empty list or null")
        }

        fn visit_seq<ChildrenSequenceAccessor>(
            self,
            mut seq: ChildrenSequenceAccessor,
        ) -> Result<Self::Value, ChildrenSequenceAccessor::Error>
        where
            ChildrenSequenceAccessor: de::SeqAccess<'de>,
        {
            let size_hint = seq.size_hint().unwrap_or(0);
            let mut children: Items = Items::with_capacity(size_hint);

            while let Some(item) = seq.next_element()? {
                children.push(item);
            }

            Ok(children)
        }
    }

    #[allow(clippy::ptr_arg)]
    pub fn serialize<S>(children: &Value, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        let mut seq = serializer.serialize_seq(Some(children.deref().len()))?;
        let safe_items = children
            .iter()
            .filter(|item| !item.is_empty())
            .cloned()
            .collect::<Vec<_>>();
        for concrete_item in safe_items {
            seq.serialize_element(&concrete_item)?;
        }
        seq.end()
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_seq(ChildrenVisitor)
    }

    pub fn is_empty(items: &Items) -> bool {
        items.is_empty()
    }
}

impl LanguageFilter for Item {
    fn matches_languages(&self, languages: &HashSet<&str>) -> bool {
        // Check if this item's language matches
        if let Some(ref lang) = self.lang {
            if languages.contains(lang.as_str()) {
                return true;
            }
        }
        // Check children recursively
        self.children
            .iter()
            .any(|child| child.matches_languages(languages))
    }

    fn filter_by_languages_set(&self, languages: &HashSet<&str>) -> Option<Self> {
        // Check if this item or its children match
        if !self.matches_languages(languages) {
            return None;
        }

        // Filter children recursively
        let filtered_children: Vec<Item> = self
            .children
            .iter()
            .filter_map(|child| child.filter_by_languages_set(languages))
            .collect();

        Some(Self {
            r#type: self.r#type.clone(),
            properties: self.properties.clone(),
            children: filtered_children.into(),
            id: self.id.clone(),
            lang: self.lang.clone(),
            value: self.value.clone(),
        })
    }
}

impl LanguageFilter for Items {
    fn matches_languages(&self, languages: &HashSet<&str>) -> bool {
        self.iter().any(|item| item.matches_languages(languages))
    }

    fn filter_by_languages_set(&self, languages: &HashSet<&str>) -> Option<Self> {
        let filtered: Vec<Item> = self
            .iter()
            .filter_map(|item| item.filter_by_languages_set(languages))
            .collect();
        if filtered.is_empty() {
            None
        } else {
            Some(Items::from(filtered))
        }
    }
}

impl FromIterator<Item> for Items {
    fn from_iter<I: IntoIterator<Item = Item>>(iter: I) -> Self {
        Items(iter.into_iter().collect())
    }
}

#[cfg(test)]
mod language_filter_tests {
    use super::*;
    use crate::{Class, Document, KnownClass, LanguageFilter, Properties};

    /// Helper macro to check language matching
    #[macro_export]
    macro_rules! lang_matches {
        ($item:expr, $($lang:expr),* $(,)?) => {{
            let mut set = std::collections::HashSet::new();
            $(
                set.insert($lang);
            )*
            $item.matches_languages(&set)
        }};
    }

    /// Helper function to create an Item with specified language and children
    fn make_item(lang: Option<&str>, children: Vec<Item>) -> Item {
        Item {
            r#type: vec![Class::Known(KnownClass::Entry)],
            properties: Properties::default(),
            children: Items::from(children),
            id: None,
            lang: lang.map(|s| s.to_string()),
            value: None,
        }
    }

    #[test]
    fn item_with_matching_lang_matches() {
        let item = make_item(Some("en"), vec![]);
        let languages: HashSet<&str> = vec!["en", "fr"].into_iter().collect();

        assert!(item.matches_languages(&languages));
    }

    #[test]
    fn item_with_non_matching_lang_does_not_match() {
        let item = make_item(Some("de"), vec![]);
        let languages: HashSet<&str> = vec!["en", "fr"].into_iter().collect();

        assert!(!item.matches_languages(&languages));
    }

    #[test]
    fn item_with_none_lang_matches_only_via_children() {
        // Item with no lang only matches if children match
        let child_with_en = make_item(Some("en"), vec![]);
        let item = make_item(None, vec![child_with_en]);

        let languages: HashSet<&str> = vec!["en"].into_iter().collect();
        assert!(item.matches_languages(&languages));

        // Item with no lang and no matching children doesn't match
        let child_with_de = make_item(Some("de"), vec![]);
        let item_no_match = make_item(None, vec![child_with_de]);

        assert!(!item_no_match.matches_languages(&languages));
    }

    #[test]
    fn items_filtering_works() {
        let item_en = make_item(Some("en"), vec![]);
        let item_fr = make_item(Some("fr"), vec![]);
        let item_de = make_item(Some("de"), vec![]);

        let items = Items::from(vec![item_en, item_fr, item_de]);
        let languages: HashSet<&str> = vec!["en", "fr"].into_iter().collect();

        let filtered = items.filter_by_languages_set(&languages);
        assert!(filtered.is_some());

        let filtered_items = filtered.unwrap();
        assert_eq!(filtered_items.len(), 2);
    }

    #[test]
    fn document_filtering_works() {
        let item_en = make_item(Some("en"), vec![]);
        let item_fr = make_item(Some("fr"), vec![]);
        let item_de = make_item(Some("de"), vec![]);

        let mut document = Document::default();
        document.items = vec![item_en, item_fr, item_de];

        let languages: HashSet<&str> = vec!["en"].into_iter().collect();

        let filtered = document.filter_by_languages_set(&languages);
        assert!(filtered.is_some());

        let filtered_doc = filtered.unwrap();
        assert_eq!(filtered_doc.items.len(), 1);
    }

    #[test]
    fn from_iterator_collect_pattern() {
        let items: Vec<Item> = vec![make_item(Some("en"), vec![]), make_item(Some("fr"), vec![])];

        // Test FromIterator for Items
        let collected: Items = items.into_iter().collect();
        assert_eq!(collected.len(), 2);
    }

    #[test]
    fn lang_matches_macro_works() {
        let item_en = make_item(Some("en"), vec![]);
        let item_fr = make_item(Some("fr"), vec![]);
        let item_none = make_item(None, vec![]);

        // Test single language match
        assert!(lang_matches!(item_en, "en"));
        assert!(!lang_matches!(item_fr, "en"));

        // Test multiple languages
        assert!(lang_matches!(item_en, "en", "fr", "de"));
        assert!(lang_matches!(item_fr, "en", "fr", "de"));

        // Item with None lang doesn't match any language filter directly
        assert!(!lang_matches!(item_none, "en"));
    }

    #[test]
    fn recursive_child_filtering() {
        // Create nested structure: parent -> child -> grandchild
        let grandchild = make_item(Some("de"), vec![]);
        let child = make_item(Some("en"), vec![grandchild]);
        let parent = make_item(None, vec![child]);

        // Filter for "en" - should keep parent (because child matches), child, but remove grandchild
        let languages: HashSet<&str> = vec!["en"].into_iter().collect();
        let filtered = parent.filter_by_languages_set(&languages);

        assert!(filtered.is_some());
        let filtered_parent = filtered.unwrap();
        assert_eq!(filtered_parent.children.len(), 1);

        let filtered_child = &filtered_parent.children[0];
        // Grandchild with "de" should be filtered out
        assert!(filtered_child.children.is_empty());
    }

    #[test]
    fn empty_language_list_filters_everything() {
        let item_with_lang = make_item(Some("en"), vec![]);
        let item_without_lang = make_item(None, vec![]);

        let empty_languages: HashSet<&str> = HashSet::new();

        // No item should match an empty language set
        assert!(!item_with_lang.matches_languages(&empty_languages));
        assert!(!item_without_lang.matches_languages(&empty_languages));

        // Filtering should return None
        assert!(
            item_with_lang
                .filter_by_languages_set(&empty_languages)
                .is_none()
        );
        assert!(
            item_without_lang
                .filter_by_languages_set(&empty_languages)
                .is_none()
        );
    }

    #[test]
    fn item_without_lang_and_children_does_not_match() {
        let item = make_item(None, vec![]);
        let languages: HashSet<&str> = vec!["en"].into_iter().collect();

        assert!(!item.matches_languages(&languages));
    }

    #[test]
    fn filter_preserves_item_properties() {
        let mut item = make_item(Some("en"), vec![]);
        item.id = Some("test-id".to_string());
        item.properties.insert(
            "name".to_string(),
            vec![crate::PropertyValue::Plain(crate::TextValue::new(
                "Test".to_string(),
            ))],
        );

        let languages: HashSet<&str> = vec!["en"].into_iter().collect();
        let filtered = item.filter_by_languages_set(&languages);

        assert!(filtered.is_some());
        let filtered_item = filtered.unwrap();
        assert_eq!(filtered_item.id, Some("test-id".to_string()));
        assert!(filtered_item.properties.contains_key("name"));
    }
}
