//! Golden tests for the full build pipeline.
//!
//! Each case builds a decision tree from selector and value pairs, renders it
//! with `treeify`, and compares to a stored golden string. The goldens pin
//! branch ordering, normalization, specificity, and tree shape.

use selderee::{treeify, DecisionTree, DecisionTreeOptions};

fn build(input: &[(&str, &str)], opts: DecisionTreeOptions) -> String {
    let owned: Vec<(String, String)> = input
        .iter()
        .map(|(s, v)| (s.to_string(), v.to_string()))
        .collect();
    let tree = DecisionTree::new(owned, opts).expect("selector should build");
    tree.build(treeify)
}

fn check(input: &[(&str, &str)], golden: &str) {
    let got = build(input, DecisionTreeOptions::default());
    assert_eq!(got, golden);
}

#[test]
fn empty_input() {
    check(&[], include_str!("golden/empty_input.txt"));
}

#[test]
fn universal_selector() {
    check(
        &[("*", "value 1"), ("*", "value 2")],
        include_str!("golden/universal_selector.txt"),
    );
}

#[test]
fn tag_name() {
    check(
        &[("foo", "value 1"), ("bar", "value 2")],
        include_str!("golden/tag_name.txt"),
    );
}

#[test]
fn attribute_presence() {
    check(
        &[("[foo]", "value 1"), ("[bar]", "value 2")],
        include_str!("golden/attribute_presence.txt"),
    );
}

#[test]
fn different_attributes_value() {
    check(
        &[
            ("#foo", "value 1"),
            (".bar", "value 2"),
            ("[baz^=qux i]", "value 3"),
        ],
        include_str!("golden/different_attributes_value.txt"),
    );
}

#[test]
fn same_attribute_different_values() {
    check(
        &[
            ("[foo=a]", "value 1"),
            ("[foo=b]", "value 2"),
            ("[foo^=c s]", "value 3"),
        ],
        include_str!("golden/same_attribute_different_values.txt"),
    );
}

#[test]
fn combinators() {
    check(
        &[("bar > foo", "value 1"), ("qux + baz", "value 2")],
        include_str!("golden/combinators.txt"),
    );
}

#[test]
fn combinators_with_universal() {
    check(
        &[("* + * > *", "value 1")],
        include_str!("golden/combinators_with_universal.txt"),
    );
}

#[test]
fn compound_different_depth() {
    check(
        &[
            (".foo", "value 1"),
            (".foo.bar", "value 2"),
            (".foo.bar.baz", "value 3"),
        ],
        include_str!("golden/compound_different_depth.txt"),
    );
}

#[test]
fn complex_different_depth() {
    check(
        &[
            ("[foo]", "value 1"),
            ("[bar] + [foo]", "value 2"),
            ("[baz] > [bar] + [foo]", "value 3"),
        ],
        include_str!("golden/complex_different_depth.txt"),
    );
}

#[test]
fn pop_element_least_common() {
    check(
        &[
            ("[bar] + [foo][baz]", "value 1"),
            ("[bar] + [foo]", "value 2"),
        ],
        include_str!("golden/pop_element_least_common.txt"),
    );
}

#[test]
fn same_combinators() {
    check(
        &[("[bar] + [foo]", "value 1"), ("[bar] + [foo]", "value 2")],
        include_str!("golden/same_combinators.txt"),
    );
}

#[test]
fn repeated_simple_selectors() {
    check(
        &[
            ("[foo][foo].bar.bar.bar.bar.bar", "value 1"),
            ("[foo]", "value 2"),
        ],
        include_str!("golden/repeated_simple_selectors.txt"),
    );
}

#[test]
fn non_ascii_escape() {
    check(
        &[
            ("#\\..♫", "value 1"),
            (".👩‍🚀", "value 2"),
            (".\\22 .\\2d", "value 3"),
            ("[\\31=\"\\31\"]", "value 4"),
        ],
        include_str!("golden/non_ascii_escape.txt"),
    );
}

#[test]
fn id_selector_vs_attr() {
    check(
        &[
            ("#foo", "value 1"),
            ("[id=\"foo\"]", "value 2"),
            ("#foo.bar", "value 3"),
            ("[id=\"foo\"].bar", "value 4"),
        ],
        include_str!("golden/id_selector_vs_attr.txt"),
    );
}

#[test]
fn supported_pseudo_classes() {
    check(
        &[
            (":empty", "value 1"),
            (":first-child:last-child", "value 2"),
            (":only-child", "value 3"),
        ],
        include_str!("golden/supported_pseudo_classes.txt"),
    );
}

#[test]
fn namespace_attr_value_is_rejected() {
    // Dropping the `a` prefix would widen `[a|b=c]` to `[b=c]`, so it is
    // rejected at build time.
    let input = vec![("[a|b=c]".to_string(), "v".to_string())];
    let err = DecisionTree::new(input, DecisionTreeOptions::default())
        .err()
        .expect("namespaced selector should be rejected");
    assert_eq!(err.to_string(), "Unsupported namespace prefix: a|");
}

#[test]
fn namespace_universal_is_rejected() {
    // `ns|*` would drop to a bare `*` and match every element, so it is
    // rejected at build time.
    let input = vec![("ns|*".to_string(), "v".to_string())];
    let err = DecisionTree::new(input, DecisionTreeOptions::default())
        .err()
        .expect("namespaced selector should be rejected");
    assert_eq!(err.to_string(), "Unsupported namespace prefix: ns|");
}

#[test]
fn decision_tree_is_send_and_sync() {
    fn assert_send_sync<T: Send + Sync>() {}
    assert_send_sync::<DecisionTree<String>>();
}

#[test]
fn html_lowercases_tag_name() {
    check(&[("DIV", "v")], include_str!("golden/lowercase_tag.txt"));
}

#[test]
fn html_lowercases_attr_presence_name() {
    check(
        &[("[FOO]", "v")],
        include_str!("golden/lowercase_attr_presence.txt"),
    );
}

#[test]
fn html_lowercases_pseudo_class_name() {
    // Lowercasing runs before the allow-list check, so `:EMPTY` builds.
    check(
        &[(":EMPTY", "v")],
        include_str!("golden/lowercase_pseudo_class.txt"),
    );
}

#[test]
fn attribute_value_normalization() {
    let opts = DecisionTreeOptions {
        attributes_with_normalized_values: vec!["type".to_string()],
    };
    let got = build(
        &[
            ("[type=\"text\"]", "value 1"),
            ("[TYPE=\"TEXT\"]", "value 2"),
            ("[Type=\"Text\"]", "value 3"),
            ("[data-foo=\"bar\"]", "value 4"),
            ("[data-FOO=\"BAR\"]", "value 5"),
        ],
        opts,
    );
    assert_eq!(
        got,
        include_str!("golden/attribute_value_normalization.txt")
    );
}
