//! Decision-tree node model.
//!
//! These nodes describe the woven tree. A builder turns them into something
//! callable. The [`crate::treeify`] builder renders them as text.

use std::fmt;
use std::sync::Arc;

use crate::css::ast::{Matcher, Modifier};

/// Selector specificity as `[id, class, tag]`.
pub type Specificity = [u32; 3];

/// Associated value plus its position and specificity.
///
/// `index` is the position in the original input array. `specificity` is the
/// selector specificity. `value` is the caller-supplied value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValueContainer<V> {
    /// Position in the original input array.
    pub index: usize,
    /// Selector specificity.
    pub specificity: Specificity,
    /// The associated value.
    pub value: V,
}

/// A predicate that tests an attribute value, with enough data to render it.
///
/// The closure runs at match time. The fields describe the matcher so a
/// renderer or debugger can show it.
#[derive(Clone)]
pub struct AttrPredicate {
    /// The match operator.
    pub matcher: Matcher,
    /// Case-sensitivity modifier, or `None` when unspecified.
    pub modifier: Option<Modifier>,
    /// Expected value text.
    pub value: String,
    /// The ready-made test against an actual attribute value.
    pub test: Arc<dyn Fn(&str) -> bool + Send + Sync>,
}

impl AttrPredicate {
    /// Run the predicate against an actual attribute value.
    pub fn matches(&self, actual: &str) -> bool {
        (self.test)(actual)
    }
}

impl fmt::Debug for AttrPredicate {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AttrPredicate")
            .field("matcher", &self.matcher)
            .field("modifier", &self.modifier)
            .field("value", &self.value)
            .finish()
    }
}

/// One tag-name variant inside a [`DecisionTreeNode::TagName`] node.
#[derive(Debug, Clone)]
pub struct VariantNode<V> {
    /// The tag name to match.
    pub value: String,
    /// What to check next when the tag matches.
    pub cont: Vec<DecisionTreeNode<V>>,
}

/// One value matcher inside a [`DecisionTreeNode::AttrValue`] node.
#[derive(Debug, Clone)]
pub struct MatcherNode<V> {
    /// The match operator.
    pub matcher: Matcher,
    /// Case-sensitivity modifier, or `None` when unspecified.
    pub modifier: Option<Modifier>,
    /// Expected value text.
    pub value: String,
    /// The predicate to test an actual attribute value.
    pub predicate: AttrPredicate,
    /// What to check next when the predicate passes.
    pub cont: Vec<DecisionTreeNode<V>>,
}

/// One node of the decision tree.
///
/// `Terminal` records a match. The other variants narrow the element under
/// test or move along combinators before continuing.
#[derive(Debug, Clone)]
pub enum DecisionTreeNode<V> {
    /// A successful match. Adds the value container to the results.
    Terminal {
        /// The matched value, its index, and specificity.
        value_container: ValueContainer<V>,
    },
    /// Check the element tag name against a set of variants.
    TagName {
        /// The known tag names and their continuations.
        variants: Vec<VariantNode<V>>,
    },
    /// Check that an attribute with the given name is present.
    AttrPresence {
        /// Attribute name.
        name: String,
        /// What to check next.
        cont: Vec<DecisionTreeNode<V>>,
    },
    /// Check an attribute value against a set of matchers.
    AttrValue {
        /// Attribute name.
        name: String,
        /// The value matchers and their continuations.
        matchers: Vec<MatcherNode<V>>,
    },
    /// Check a simple pseudo-class.
    PseudoClass {
        /// Pseudo-class name.
        name: String,
        /// What to check next.
        cont: Vec<DecisionTreeNode<V>>,
    },
    /// Push the element named by the combinator onto the stack.
    PushElement {
        /// `>` for parent, `+` for preceding sibling.
        combinator: char,
        /// What to check on the pushed element.
        cont: Vec<DecisionTreeNode<V>>,
    },
    /// Pop the top element. Following checks run on the element beneath.
    PopElement {
        /// What to check after popping.
        cont: Vec<DecisionTreeNode<V>>,
    },
}
