//! Normalize a selector tree in place.
//!
//! HTML mode lowercases element and attribute names. Attribute values get an
//! explicit case-sensitivity modifier and, when case-insensitive, a lowered
//! value. Each compound's list is sorted into a canonical order so equal
//! selectors serialize the same way.

use std::collections::HashSet;

use super::ast::{CompoundSelector, Modifier, SimpleSelector};
use super::serialize::serialize_simple;

/// Normalize options for the HTML mode used by the decision tree.
pub struct NormalizeOptions {
    /// Lowercased attribute names whose values default to case-insensitive.
    pub attributes_with_normalized_values: HashSet<String>,
}

/// Normalize a compound selector in place.
pub fn normalize(c: &mut CompoundSelector, opts: &NormalizeOptions) {
    for node in &mut c.list {
        visit_simple(node, opts);
    }
    sort_compound_list(&mut c.list);
}

fn visit_simple(sel: &mut SimpleSelector, opts: &NormalizeOptions) {
    match sel {
        SimpleSelector::Universal { namespace, .. } => {
            lower_ns(namespace);
        }
        SimpleSelector::Tag {
            name, namespace, ..
        } => {
            *name = name.to_lowercase();
            lower_ns(namespace);
        }
        SimpleSelector::AttrPresence {
            name, namespace, ..
        } => {
            *name = name.to_lowercase();
            lower_ns(namespace);
        }
        SimpleSelector::AttrValue {
            name,
            namespace,
            value,
            modifier,
            ..
        } => {
            *name = name.to_lowercase();
            lower_ns(namespace);
            // `name` is already lowercased above, and the option set is stored
            // lowercased, so borrow it directly for the lookup.
            let case_insensitive = *modifier == Some(Modifier::CaseInsensitive)
                || (modifier.is_none()
                    && opts
                        .attributes_with_normalized_values
                        .contains(name.as_str()));
            if case_insensitive {
                *value = value.to_lowercase();
            }
            if modifier.is_none() {
                *modifier = Some(if case_insensitive {
                    Modifier::CaseInsensitive
                } else {
                    Modifier::CaseSensitive
                });
            }
        }
        SimpleSelector::PseudoClass { name, .. } => {
            *name = name.to_lowercase();
        }
        SimpleSelector::FunctionalPseudoClass { name, list, .. } => {
            *name = name.to_lowercase();
            for inner in list.iter_mut() {
                normalize(inner, opts);
            }
            list.sort_by_key(serialize_compound_key);
        }
        SimpleSelector::Class { .. } | SimpleSelector::Id { .. } => {
            // Class and id selectors are rewritten before normalize runs, so
            // they are not expected here. Leave them untouched if present.
        }
        SimpleSelector::Combinator { left, .. } => {
            normalize(left, opts);
        }
    }
}

fn lower_ns(ns: &mut Option<String>) {
    if let Some(p) = ns {
        *p = p.to_lowercase();
    }
}

/// Stable sort of a compound's list by canonical priority.
fn sort_compound_list(list: &mut [SimpleSelector]) {
    list.sort_by(compare_priority);
}

/// Primary numeric priority and secondary string key for a simple selector.
fn selector_priority(sel: &SimpleSelector) -> (u8, Option<String>) {
    match sel {
        SimpleSelector::Universal { .. } => (1, None),
        SimpleSelector::Tag { .. } => (1, None),
        SimpleSelector::Id { .. } => (2, None),
        SimpleSelector::Class { name, .. } => (3, Some(name.clone())),
        SimpleSelector::AttrPresence { .. } => (4, Some(serialize_simple(sel))),
        SimpleSelector::AttrValue { .. } => (5, Some(serialize_simple(sel))),
        SimpleSelector::PseudoClass { name, .. } => (6, Some(name.clone())),
        SimpleSelector::FunctionalPseudoClass { .. } => (7, Some(serialize_simple(sel))),
        SimpleSelector::Combinator { .. } => (15, Some(serialize_simple(sel))),
    }
}

fn compare_priority(a: &SimpleSelector, b: &SimpleSelector) -> std::cmp::Ordering {
    let (pa, sa) = selector_priority(a);
    let (pb, sb) = selector_priority(b);
    match pa.cmp(&pb) {
        std::cmp::Ordering::Equal => compare_secondary(&sa, &sb),
        other => other,
    }
}

/// Compare secondary keys. `None` sorts first, then lexicographic by char.
fn compare_secondary(a: &Option<String>, b: &Option<String>) -> std::cmp::Ordering {
    match (a, b) {
        (None, None) => std::cmp::Ordering::Equal,
        (None, Some(_)) => std::cmp::Ordering::Less,
        (Some(_), None) => std::cmp::Ordering::Greater,
        (Some(x), Some(y)) => x.cmp(y),
    }
}

fn serialize_compound_key(c: &CompoundSelector) -> String {
    super::serialize::serialize_compound(c)
}
