//! Run a built decision tree against an element.
//!
//! A builder plugin compiles the tree into a [`MatcherFn`] over a concrete
//! element type. [`Picker`] wraps that function and offers `pick_all` and
//! `pick1`.

use std::cmp::Ordering;

use crate::ast::ValueContainer;
use crate::css::compare_specificity;

/// A function that matches an element stack and returns matched values.
///
/// The first slice element is the current element. The rest is the stack
/// pushed by combinators, top first.
pub type MatcherFn<L, V> = Box<dyn Fn(&[L]) -> Vec<ValueContainer<V>>>;

/// Wraps a matcher function. The recommended return type for builder plugins.
///
/// `L` is the element type of the target DOM. `V` is the associated value type.
pub struct Picker<L, V> {
    f: MatcherFn<L, V>,
}

impl<L, V> Picker<L, V> {
    /// Create a picker from a matcher function.
    pub fn new(f: MatcherFn<L, V>) -> Self {
        Picker { f }
    }

    /// Match `el` and return every matched value with its specificity.
    ///
    /// Order follows tree-walk order, not specificity. Callers sort or filter
    /// as needed.
    pub fn pick_all(&self, el: L) -> Vec<ValueContainer<V>> {
        (self.f)(&[el])
    }

    /// Match `el` and return the value from the most specific selector.
    ///
    /// On a specificity tie, `prefer_first` chooses the lowest original index.
    /// The default chooses the highest index. Returns `None` when nothing
    /// matches.
    pub fn pick1(&self, el: L, prefer_first: bool) -> Option<V>
    where
        V: Clone,
    {
        let results = (self.f)(&[el]);
        pick1_from(&results, prefer_first)
    }
}

/// Choose the best value from a result list per the `pick1` rules.
pub fn pick1_from<V: Clone>(results: &[ValueContainer<V>], prefer_first: bool) -> Option<V> {
    let len = results.len();
    if len == 0 {
        return None;
    }
    if len == 1 {
        return Some(results[0].value.clone());
    }
    let mut best = &results[0];
    for next in &results[1..] {
        let adopt = if prefer_first {
            prefer_first_better(best, next)
        } else {
            prefer_last_better(best, next)
        };
        if adopt {
            best = next;
        }
    }
    Some(best.value.clone())
}

/// True when `next` should replace `acc`, preferring the lowest index on ties.
fn prefer_first_better<V>(acc: &ValueContainer<V>, next: &ValueContainer<V>) -> bool {
    match compare_specificity(next.specificity, acc.specificity) {
        Ordering::Greater => true,
        Ordering::Equal => next.index < acc.index,
        Ordering::Less => false,
    }
}

/// True when `next` should replace `acc`, preferring the highest index on ties.
fn prefer_last_better<V>(acc: &ValueContainer<V>, next: &ValueContainer<V>) -> bool {
    match compare_specificity(next.specificity, acc.specificity) {
        Ordering::Greater => true,
        Ordering::Equal => next.index > acc.index,
        Ordering::Less => false,
    }
}
