use super::*;
use crate::parse::{element, remove_surrounding_whitespace};
pub struct PropertyParser {
    property_elem: ElementPtr,
    base_url: Url,
}

impl PropertyParser {
    pub fn new(property_elem: ElementPtr, base_url: &Url) -> Self {
        Self {
            property_elem,
            base_url: base_url.to_owned(),
        }
    }

    fn discern_textual_item_value(
        &self,
        property_class: &DeclKind,
        item: &Item,
        prop_name: &str,
    ) -> Result<(String, Option<ValueKind>), Error> {
        let base_prop = item
            .properties
            .get(if matches!(property_class, DeclKind::Plain(_)) {
                "name"
            } else {
                "url"
            })
            .and_then(|values| {
                values
                    .iter()
                    .find(|value| matches!(value, PropertyValue::Plain(_) | PropertyValue::Url(_)))
            });

        let defined_prop = item.properties.get(prop_name).and_then(|values| {
            values
                .iter()
                .find(|value| matches!(value, PropertyValue::Plain(_) | PropertyValue::Url(_)))
        });

        let mut resulting_value = base_prop
            .or(defined_prop)
            .cloned()
            .filter(non_empty_property_value);

        if resulting_value.is_none() {
            let plain_text: String = self.property_elem.node.text_content(&self.base_url)?.into();
            resulting_value =
                Some(PropertyValue::Plain(plain_text)).filter(non_empty_property_value)
        };

        match resulting_value {
            Some(PropertyValue::Plain(v)) if matches!(property_class, DeclKind::Plain(_)) => Ok((
                prop_name.to_owned(),
                Some(ValueKind::Plain(remove_surrounding_whitespace(v))),
            )),
            Some(PropertyValue::Url(u)) if matches!(property_class, DeclKind::Linked(_)) => {
                Ok((prop_name.to_owned(), Some(ValueKind::Url(u))))
            }
            _ => Ok((prop_name.to_owned(), None)),
        }
    }

    fn discern_hypertext_item_value(
        &self,
        _property_class: &DeclKind,
        _item: &Item,
        prop_name: &str,
    ) -> Result<(String, Option<ValueKind>), Error> {
        unimplemented!("use the associated item here for {prop_name} (first one?), ValueKind::Item")
    }

    fn discern_item_value(
        &self,
        property_class: &DeclKind,
        item: &Item,
    ) -> Result<(String, Option<ValueKind>), Error> {
        match property_class {
            DeclKind::Plain(ref prop_name) | DeclKind::Linked(ref prop_name) => {
                self.discern_textual_item_value(property_class, item, prop_name)
            }
            DeclKind::Hypertext(prop_name) => {
                self.discern_hypertext_item_value(property_class, item, prop_name)
            }
            _ => unreachable!("define logic for {property_class:#?}"),
        }
    }

    pub fn expand(self, matched_elements: &element::MatchedElements) -> Result<Properties, Error> {
        let mut properties = Properties::default();
        let prop_classes = self.property_elem.node.property_classes();

        for prop_decl in prop_classes {
            let mut property_item = matched_elements
                .expand_item_from_element(Arc::clone(&self.property_elem), &self.base_url)?;
            let (property_name, value) = self.discern_item_value(&prop_decl, &property_item)?;

            property_item.value = value;

            if let Some(values) = properties.get_mut(&property_name) {
                values.push(PropertyValue::Item(property_item));
            } else {
                properties.insert(property_name, vec![PropertyValue::Item(property_item)]);
            }
        }

        Ok(properties)
    }
}

#[cfg(test)]
mod test;
