//! Recursive-descent parser for a single compound or complex selector.
//!
//! The grammar follows the CSS Selectors spec subset that the lexer tokenizes.
//! Commas are not consumed here, so a selector list is rejected as a partial
//! parse. This mirrors the single-selector entry point.

use super::ast::{CompoundSelector, Matcher, Modifier, SimpleSelector, Specificity};
use super::lexer::{lex, Token, TokenKind};
use super::spec::{max_all_spec, sum_spec};
use super::unescape::unescape;
use crate::error::SelectorError;

/// Parse one complex or compound selector.
///
/// # Errors
///
/// Returns [`SelectorError::Parse`] on a tokenizer or parser failure. The
/// message text is part of the contract for callers that surface it.
pub fn parse1(input: &str) -> Result<CompoundSelector, SelectorError> {
    let lexed = lex(input);
    if lexed.offset < utf16_len(input) {
        return Err(SelectorError::Parse {
            message: format!(
                "The input \"{}\" was only partially tokenized, stopped at offset {}!\n{}",
                input,
                lexed.offset,
                pretty_print_position(input, lexed.offset, 1),
            ),
        });
    }

    let mut p = Parser {
        tokens: &lexed.tokens,
        pos: 0,
    };
    p.skip_ws();
    let ast = match p.complex_selector() {
        Some(ast) => ast,
        None => {
            return Err(SelectorError::Parse {
                message: format!("No match for \"{}\" input!", input),
            })
        }
    };
    p.skip_ws();
    if p.pos < p.tokens.len() {
        let token = &p.tokens[p.pos];
        return Err(SelectorError::Parse {
            message: format!(
                "The input \"{}\" was only partially parsed, stopped at offset {}!\n{}",
                input,
                token.offset,
                pretty_print_position(input, token.offset, 1),
            ),
        });
    }
    Ok(ast)
}

/// Count UTF-16 code units in `s`.
fn utf16_len(s: &str) -> usize {
    s.chars().map(|c| c.len_utf16()).sum()
}

/// Build the caret diagnostic line(s) shown under a failing input.
///
/// Tabs and line breaks in the echoed input are replaced with visible glyphs so
/// the caret column lines up.
fn pretty_print_position(str_in: &str, offset: usize, len: usize) -> String {
    let mut echoed = String::with_capacity(str_in.len());
    for c in str_in.chars() {
        match c {
            '\t' => echoed.push('\u{2409}'),
            '\r' => echoed.push('\u{240D}'),
            '\n' => echoed.push('\u{240A}'),
            other => echoed.push(other),
        }
    }
    let mut caret = String::new();
    for _ in 0..offset {
        caret.push(' ');
    }
    for _ in 0..len {
        caret.push('^');
    }
    format!("{}\n{}", echoed, caret)
}

struct Parser<'a> {
    tokens: &'a [Token],
    pos: usize,
}

impl<'a> Parser<'a> {
    fn peek(&self) -> Option<&Token> {
        self.tokens.get(self.pos)
    }

    fn skip_ws(&mut self) {
        while let Some(t) = self.peek() {
            if t.kind == TokenKind::Ws {
                self.pos += 1;
            } else {
                break;
            }
        }
    }

    fn eat_char(&mut self, c: char) -> bool {
        if let Some(t) = self.peek() {
            if t.kind == TokenKind::Char(c) {
                self.pos += 1;
                return true;
            }
        }
        false
    }

    /// `complexSelector = compound (combinatorSeparator compound)*`, folded
    /// left-associatively. The combinator is appended to the right compound's
    /// list as the last entry.
    fn complex_selector(&mut self) -> Option<CompoundSelector> {
        let mut left = self.compound_selector()?;
        loop {
            let save = self.pos;
            let comb = self.combinator_separator();
            match comb {
                Some(c) => match self.compound_selector() {
                    Some(mut right) => {
                        let left_spec = left.specificity;
                        right.list.push(SimpleSelector::Combinator {
                            combinator: c,
                            left: Box::new(left),
                            specificity: left_spec,
                        });
                        right.specificity = sum_spec(left_spec, right.specificity);
                        left = right;
                    }
                    None => {
                        self.pos = save;
                        break;
                    }
                },
                None => {
                    self.pos = save;
                    break;
                }
            }
        }
        Some(left)
    }

    /// `combinatorSeparator = ws? combinator ws?  |  ws` (descendant).
    fn combinator_separator(&mut self) -> Option<String> {
        let had_ws = matches!(self.peek().map(|t| &t.kind), Some(TokenKind::Ws));
        self.skip_ws();
        if let Some(c) = self.combinator() {
            self.skip_ws();
            return Some(c);
        }
        if had_ws {
            // bare whitespace is the descendant combinator
            return Some(" ".to_string());
        }
        None
    }

    /// `combinator = > | + | ~ | ||`.
    fn combinator(&mut self) -> Option<String> {
        if self.eat_char('>') {
            return Some(">".to_string());
        }
        if self.eat_char('+') {
            return Some("+".to_string());
        }
        if self.eat_char('~') {
            return Some("~".to_string());
        }
        // column `||`
        if let Some(t) = self.peek() {
            if t.kind == TokenKind::Char('|') {
                if let Some(t2) = self.tokens.get(self.pos + 1) {
                    if t2.kind == TokenKind::Char('|') {
                        self.pos += 2;
                        return Some("||".to_string());
                    }
                }
            }
        }
        None
    }

    /// `compoundSelector = (typeSelector subclassSelector*) | subclassSelector+`.
    fn compound_selector(&mut self) -> Option<CompoundSelector> {
        let mut list: Vec<SimpleSelector> = Vec::new();
        if let Some(type_sel) = self.type_selector() {
            list.push(type_sel);
            while let Some(sub) = self.subclass_selector() {
                list.push(sub);
            }
        } else {
            let first = self.subclass_selector()?;
            list.push(first);
            while let Some(sub) = self.subclass_selector() {
                list.push(sub);
            }
        }
        let specificity = list
            .iter()
            .map(simple_specificity)
            .fold([0, 0, 0], sum_spec);
        Some(CompoundSelector { list, specificity })
    }

    /// `typeSelector = uniSelector | tagSelector`.
    fn type_selector(&mut self) -> Option<SimpleSelector> {
        let save = self.pos;
        if let Some(u) = self.uni_selector() {
            return Some(u);
        }
        self.pos = save;
        self.tag_selector()
    }

    /// `uniSelector = (namespace '*') | '*'`.
    fn uni_selector(&mut self) -> Option<SimpleSelector> {
        let save = self.pos;
        if let Some(ns) = self.namespace() {
            if self.eat_char('*') {
                return Some(SimpleSelector::Universal {
                    namespace: Some(ns),
                    specificity: [0, 0, 0],
                });
            }
            self.pos = save;
        }
        if self.eat_char('*') {
            return Some(SimpleSelector::Universal {
                namespace: None,
                specificity: [0, 0, 0],
            });
        }
        None
    }

    /// `tagSelector = qualifiedName`.
    fn tag_selector(&mut self) -> Option<SimpleSelector> {
        let (name, namespace) = self.qualified_name()?;
        Some(SimpleSelector::Tag {
            name,
            namespace,
            specificity: [0, 0, 1],
        })
    }

    /// `qualifiedName = (namespace ident) | ident`.
    fn qualified_name(&mut self) -> Option<(String, Option<String>)> {
        let save = self.pos;
        if let Some(ns) = self.namespace() {
            if let Some(name) = self.ident() {
                return Some((name, Some(ns)));
            }
            self.pos = save;
        }
        let name = self.ident()?;
        Some((name, None))
    }

    /// `namespace = ident? '|'` (default empty prefix). Returns the prefix and
    /// consumes the `|`. Bare `||` is not a namespace separator.
    fn namespace(&mut self) -> Option<String> {
        let save = self.pos;
        let prefix = self.ident().unwrap_or_default();
        if let Some(t) = self.peek() {
            if t.kind == TokenKind::Char('|') {
                // Reject `||` (column combinator).
                if self.tokens.get(self.pos + 1).map(|t| &t.kind) == Some(&TokenKind::Char('|')) {
                    self.pos = save;
                    return None;
                }
                self.pos += 1;
                return Some(prefix);
            }
        }
        self.pos = save;
        None
    }

    /// `subclassSelector = id | class | attr | functionalPc | pc`.
    fn subclass_selector(&mut self) -> Option<SimpleSelector> {
        if let Some(s) = self.id_selector() {
            return Some(s);
        }
        if let Some(s) = self.class_selector() {
            return Some(s);
        }
        if let Some(s) = self.attr_selector() {
            return Some(s);
        }
        if let Some(s) = self.functional_pseudo_class() {
            return Some(s);
        }
        if let Some(s) = self.pseudo_class() {
            return Some(s);
        }
        None
    }

    /// `idSelector = hash`.
    fn id_selector(&mut self) -> Option<SimpleSelector> {
        if let Some(t) = self.peek() {
            if t.kind == TokenKind::Hash {
                let name = unescape(&t.text[1..]); // strip '#'
                self.pos += 1;
                return Some(SimpleSelector::Id {
                    name,
                    specificity: [1, 0, 0],
                });
            }
        }
        None
    }

    /// `classSelector = '.' ident`.
    fn class_selector(&mut self) -> Option<SimpleSelector> {
        let save = self.pos;
        if self.eat_char('.') {
            if let Some(name) = self.ident() {
                return Some(SimpleSelector::Class {
                    name,
                    specificity: [0, 1, 0],
                });
            }
        }
        self.pos = save;
        None
    }

    /// `attrPresence | attrValue`, both starting with `[`.
    fn attr_selector(&mut self) -> Option<SimpleSelector> {
        let save = self.pos;
        if !self.eat_char('[') {
            return None;
        }
        self.skip_ws();
        let (name, namespace) = match self.qualified_name() {
            Some(q) => q,
            None => {
                self.pos = save;
                return None;
            }
        };
        // attrPresence: '[' ws? qualifiedName ws? ']'
        let after_name = self.pos;
        self.skip_ws();
        if self.eat_char(']') {
            return Some(SimpleSelector::AttrPresence {
                name,
                namespace,
                specificity: [0, 1, 0],
            });
        }
        self.pos = after_name;
        // attrValue: '[' ws? qualifiedName attrMatcher ws? attrValue ws? ']'
        let matcher = match self.attr_matcher() {
            Some(m) => m,
            None => {
                self.pos = save;
                return None;
            }
        };
        self.skip_ws();
        let (value, modifier) = match self.attr_value() {
            Some(v) => v,
            None => {
                self.pos = save;
                return None;
            }
        };
        self.skip_ws();
        if !self.eat_char(']') {
            self.pos = save;
            return None;
        }
        Some(SimpleSelector::AttrValue {
            name,
            namespace,
            matcher,
            value,
            modifier,
            specificity: [0, 1, 0],
        })
    }

    /// `attrMatcher = = | ~= | |= | ^= | $= | *=`.
    fn attr_matcher(&mut self) -> Option<Matcher> {
        if self.eat_char('=') {
            return Some(Matcher::Equals);
        }
        let twochar = |a: char| {
            self.tokens.get(self.pos).map(|t| &t.kind) == Some(&TokenKind::Char(a))
                && self.tokens.get(self.pos + 1).map(|t| &t.kind) == Some(&TokenKind::Char('='))
        };
        if twochar('~') {
            self.pos += 2;
            return Some(Matcher::Includes);
        }
        if twochar('|') {
            self.pos += 2;
            return Some(Matcher::DashMatch);
        }
        if twochar('^') {
            self.pos += 2;
            return Some(Matcher::Prefix);
        }
        if twochar('$') {
            self.pos += 2;
            return Some(Matcher::Suffix);
        }
        if twochar('*') {
            self.pos += 2;
            return Some(Matcher::Substring);
        }
        None
    }

    /// `attrValue = string (ws? modifier)? | ident (ws modifier)?`.
    fn attr_value(&mut self) -> Option<(String, Option<Modifier>)> {
        if let Some(t) = self.peek() {
            match t.kind {
                TokenKind::Str1 | TokenKind::Str2 => {
                    let inner = &t.text[1..t.text.len() - 1];
                    let value = unescape(inner);
                    self.pos += 1;
                    // optional ws? modifier
                    let save = self.pos;
                    self.skip_ws();
                    let modifier = self.attr_modifier();
                    if modifier.is_none() {
                        self.pos = save;
                    }
                    return Some((value, modifier));
                }
                TokenKind::Ident => {
                    let value = unescape(&t.text);
                    self.pos += 1;
                    // optional ws modifier (whitespace required before modifier)
                    let save = self.pos;
                    if matches!(self.peek().map(|t| &t.kind), Some(TokenKind::Ws)) {
                        self.skip_ws();
                        if let Some(m) = self.attr_modifier() {
                            return Some((value, Some(m)));
                        }
                    }
                    self.pos = save;
                    return Some((value, None));
                }
                _ => {}
            }
        }
        None
    }

    /// `attrModifier = ident in {i, I, s, S}`.
    fn attr_modifier(&mut self) -> Option<Modifier> {
        if let Some(t) = self.peek() {
            if t.kind == TokenKind::Ident {
                match t.text.as_str() {
                    "i" | "I" => {
                        self.pos += 1;
                        return Some(Modifier::CaseInsensitive);
                    }
                    "s" | "S" => {
                        self.pos += 1;
                        return Some(Modifier::CaseSensitive);
                    }
                    _ => {}
                }
            }
        }
        None
    }

    /// `functionalPseudoClass = ':' ident '(' list ')'` for is/where/not.
    fn functional_pseudo_class(&mut self) -> Option<SimpleSelector> {
        let save = self.pos;
        if !self.eat_char(':') {
            return None;
        }
        let name = match self.ident() {
            Some(n) => n,
            None => {
                self.pos = save;
                return None;
            }
        };
        if !self.eat_char('(') {
            self.pos = save;
            return None;
        }
        self.skip_ws();
        let mut list = Vec::new();
        match self.complex_selector() {
            Some(c) => list.push(c),
            None => {
                self.pos = save;
                return None;
            }
        }
        loop {
            let inner_save = self.pos;
            self.skip_ws();
            if self.eat_char(',') {
                self.skip_ws();
                match self.complex_selector() {
                    Some(c) => list.push(c),
                    None => {
                        self.pos = inner_save;
                        break;
                    }
                }
            } else {
                self.pos = inner_save;
                break;
            }
        }
        self.skip_ws();
        if !self.eat_char(')') {
            self.pos = save;
            return None;
        }
        let specificity = if name.eq_ignore_ascii_case("where") {
            [0, 0, 0]
        } else {
            let specs: Vec<Specificity> = list.iter().map(|c| c.specificity).collect();
            max_all_spec(&specs)
        };
        Some(SimpleSelector::FunctionalPseudoClass {
            name,
            list,
            specificity,
        })
    }

    /// `pseudoClass = ':' ident` not followed by `(`.
    fn pseudo_class(&mut self) -> Option<SimpleSelector> {
        let save = self.pos;
        if !self.eat_char(':') {
            return None;
        }
        let name = match self.ident() {
            Some(n) => n,
            None => {
                self.pos = save;
                return None;
            }
        };
        if matches!(self.peek().map(|t| &t.kind), Some(TokenKind::Char('('))) {
            self.pos = save;
            return None;
        }
        Some(SimpleSelector::PseudoClass {
            name,
            specificity: [0, 1, 0],
        })
    }

    /// Consume one `ident` token, returning its unescaped text.
    fn ident(&mut self) -> Option<String> {
        if let Some(t) = self.peek() {
            if t.kind == TokenKind::Ident {
                let text = unescape(&t.text);
                self.pos += 1;
                return Some(text);
            }
        }
        None
    }
}

/// Specificity of a simple selector node, read from its stored value.
fn simple_specificity(sel: &SimpleSelector) -> Specificity {
    match sel {
        SimpleSelector::Universal { specificity, .. }
        | SimpleSelector::Tag { specificity, .. }
        | SimpleSelector::Class { specificity, .. }
        | SimpleSelector::Id { specificity, .. }
        | SimpleSelector::AttrPresence { specificity, .. }
        | SimpleSelector::AttrValue { specificity, .. }
        | SimpleSelector::PseudoClass { specificity, .. }
        | SimpleSelector::FunctionalPseudoClass { specificity, .. }
        | SimpleSelector::Combinator { specificity, .. } => *specificity,
    }
}
