//! CSS selector syntax tree.
//!
//! These types model a single compound or complex selector after parsing. The
//! decision tree consumes and mutates these nodes while it weaves selectors
//! together.

/// Selector specificity as `[id, class, tag]`.
///
/// Three levels per the Selectors spec. HTML inline-style levels are out of
/// scope and get no slot.
pub type Specificity = [u32; 3];

/// One simple selector inside a compound selector.
///
/// A compound selector is a list of these. A combinator, when present, is the
/// last entry of a compound's list and carries the entire left side.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SimpleSelector {
    /// `*` or `ns|*`.
    Universal {
        /// Namespace prefix, or `None` when absent.
        namespace: Option<String>,
        /// Always `[0, 0, 0]`.
        specificity: Specificity,
    },
    /// `tag` or `ns|tag`.
    Tag {
        /// Tag name.
        name: String,
        /// Namespace prefix, or `None` when absent.
        namespace: Option<String>,
        /// Always `[0, 0, 1]`.
        specificity: Specificity,
    },
    /// `.name`.
    Class {
        /// Class name.
        name: String,
        /// Always `[0, 1, 0]`.
        specificity: Specificity,
    },
    /// `#name`.
    Id {
        /// Id name.
        name: String,
        /// Always `[1, 0, 0]`.
        specificity: Specificity,
    },
    /// `[name]`.
    AttrPresence {
        /// Attribute name.
        name: String,
        /// Namespace prefix, or `None` when absent.
        namespace: Option<String>,
        /// Always `[0, 1, 0]`.
        specificity: Specificity,
    },
    /// `[name op value modifier]`.
    AttrValue {
        /// Attribute name.
        name: String,
        /// Namespace prefix, or `None` when absent.
        namespace: Option<String>,
        /// The match operator.
        matcher: Matcher,
        /// Expected value text.
        value: String,
        /// Case-sensitivity modifier, or `None` when unspecified.
        modifier: Option<Modifier>,
        /// Always `[0, 1, 0]`.
        specificity: Specificity,
    },
    /// `:name` (simple pseudo-class, not functional).
    PseudoClass {
        /// Pseudo-class name.
        name: String,
        /// Always `[0, 1, 0]`.
        specificity: Specificity,
    },
    /// `:name(...)` such as `:is()`, `:where()`, `:not()`.
    FunctionalPseudoClass {
        /// Pseudo-class name (without parentheses).
        name: String,
        /// Argument selector list.
        list: Vec<CompoundSelector>,
        /// Computed from the argument list.
        specificity: Specificity,
    },
    /// A combinator joining a left side to the current compound.
    ///
    /// Always the last entry of a compound's list.
    Combinator {
        /// One of `>`, `+`, `~`, `||`, or a single space for descendant.
        combinator: String,
        /// The full compound selector on the left.
        left: Box<CompoundSelector>,
        /// Equal to the left side's specificity.
        specificity: Specificity,
    },
}

/// Attribute match operator.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Matcher {
    /// `=` exact match.
    Equals,
    /// `~=` whitespace-token membership.
    Includes,
    /// `|=` exact or `prefix-`.
    DashMatch,
    /// `^=` prefix.
    Prefix,
    /// `$=` suffix.
    Suffix,
    /// `*=` substring.
    Substring,
}

impl Matcher {
    /// The operator text, such as `~=`.
    pub fn as_str(&self) -> &'static str {
        match self {
            Matcher::Equals => "=",
            Matcher::Includes => "~=",
            Matcher::DashMatch => "|=",
            Matcher::Prefix => "^=",
            Matcher::Suffix => "$=",
            Matcher::Substring => "*=",
        }
    }
}

/// Case-sensitivity modifier on an attribute value selector.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Modifier {
    /// `i`: case-insensitive.
    CaseInsensitive,
    /// `s`: case-sensitive.
    CaseSensitive,
}

impl Modifier {
    /// The modifier text, `i` or `s`.
    pub fn as_str(&self) -> &'static str {
        match self {
            Modifier::CaseInsensitive => "i",
            Modifier::CaseSensitive => "s",
        }
    }
}

/// A compound selector: a list of simple selectors plus a total specificity.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompoundSelector {
    /// The simple selectors. A combinator, if any, is the last entry.
    pub list: Vec<SimpleSelector>,
    /// Total specificity of the whole complex selector.
    pub specificity: Specificity,
}
