#![allow(clippy::empty_docs)]
pub mod errors;
mod macros;
pub mod model;
mod tests;

use crate::parser::errors::JsonPathError;
use crate::parser::model::{
    Comparable, Comparison, Filter, FilterAtom, FnArg, JpQuery, Literal, Segment, Selector,
    SingularQuery, SingularQuerySegment, Test, TestFunction,
};

use pest::iterators::Pair;
use pest::Parser;

#[derive(Parser)]
#[grammar = "parser/grammar/json_path_9535.pest"]
pub(super) struct JSPathParser;
// const MAX_VAL: i64 = 9007199254740991; // Maximum safe integer value in JavaScript
// const MIN_VAL: i64 = -9007199254740991; // Minimum safe integer value in JavaScript

pub type Parsed<T> = Result<T, JsonPathError>;

/// The maximum nesting depth of parentheses and brackets accepted in a query.
///
/// Parsing is recursive (both in the underlying PEG parser and in the AST
/// construction that follows), so a query that nests grouping constructs
/// unboundedly would recurse until the stack overflows and the process aborts.
/// Real-world queries nest only a handful of levels, so this limit is generous
/// while still keeping stack usage bounded.
const MAX_NESTING_DEPTH: usize = 128;

/// Parses a string into a [JsonPath].
///
/// # Errors
///
/// Returns a variant of [crate::JsonPathParserError] if the parsing operation failed.
pub fn parse_json_path(jp_str: &str) -> Parsed<JpQuery> {
    check_nesting_depth(jp_str)?;
    JSPathParser::parse(Rule::main, jp_str)
        .map_err(Box::new)?
        .next()
        .ok_or(JsonPathError::UnexpectedPestOutput)
        .and_then(next_down)
        .and_then(jp_query)
}

/// Rejects queries whose parentheses/brackets nest deeper than
/// [`MAX_NESTING_DEPTH`] before they reach the recursive parser.
///
/// Grouping (`(...)` in filters and `[...]` selections/filters) is the only
/// source of parser recursion, so scanning the raw input for the deepest run of
/// still-open `(`/`[` bounds the recursion depth without first having to build
/// the parse tree. Brackets and parentheses that appear inside a string literal
/// are data rather than grouping tokens, so string contents are skipped.
fn check_nesting_depth(jp_str: &str) -> Parsed<()> {
    let mut depth: usize = 0;
    let mut string_delim: Option<char> = None;
    let mut escaped = false;

    for c in jp_str.chars() {
        if let Some(delim) = string_delim {
            if escaped {
                escaped = false;
            } else if c == '\\' {
                escaped = true;
            } else if c == delim {
                string_delim = None;
            }
            continue;
        }

        match c {
            '\'' | '"' => string_delim = Some(c),
            '(' | '[' => {
                depth += 1;
                if depth > MAX_NESTING_DEPTH {
                    return Err(JsonPathError::MaxNestingDepthExceeded(MAX_NESTING_DEPTH));
                }
            }
            ')' | ']' => depth = depth.saturating_sub(1),
            _ => {}
        }
    }

    Ok(())
}

pub fn jp_query(rule: Pair<Rule>) -> Parsed<JpQuery> {
    Ok(JpQuery::new(segments(next_down(rule)?)?))
}
pub fn rel_query(rule: Pair<Rule>) -> Parsed<Vec<Segment>> {
    segments(next_down(rule)?)
}

pub fn segments(rule: Pair<Rule>) -> Parsed<Vec<Segment>> {
    let mut segments = vec![];
    for r in rule.into_inner() {
        segments.push(segment(next_down(r)?)?);
    }
    Ok(segments)
}

pub fn child_segment(rule: Pair<Rule>) -> Parsed<Segment> {
    match rule.as_rule() {
        Rule::wildcard_selector => Ok(Segment::Selector(Selector::Wildcard)),
        Rule::member_name_shorthand => Ok(Segment::name(rule.as_str().trim())),
        Rule::bracketed_selection => {
            let mut selectors = vec![];
            for r in rule.into_inner() {
                selectors.push(selector(r)?);
            }
            if selectors.len() == 1 {
                Ok(Segment::Selector(
                    selectors
                        .into_iter()
                        .next()
                        .ok_or(JsonPathError::empty("selector"))?,
                ))
            } else {
                Ok(Segment::Selectors(selectors))
            }
        }
        _ => Err(rule.into()),
    }
}

pub fn segment(child: Pair<Rule>) -> Parsed<Segment> {
    match child.as_rule() {
        Rule::child_segment => {
            let val = child.as_str().strip_prefix(".").unwrap_or_default();
            if val != val.trim_start() {
                Err(JsonPathError::InvalidJsonPath(format!(
                    "Invalid child segment `{}`",
                    child.as_str()
                )))
            } else {
                child_segment(next_down(child)?)
            }
        }
        Rule::descendant_segment => {
            if child
                .as_str()
                .chars()
                .nth(2)
                .ok_or(JsonPathError::empty(child.as_str()))?
                .is_whitespace()
            {
                Err(JsonPathError::InvalidJsonPath(format!(
                    "Invalid descendant segment `{}`",
                    child.as_str()
                )))
            } else {
                Ok(Segment::Descendant(Box::new(child_segment(next_down(
                    child,
                )?)?)))
            }
        }
        _ => Err(child.into()),
    }
}

pub fn selector(rule: Pair<Rule>) -> Parsed<Selector> {
    let child = next_down(rule)?;
    match child.as_rule() {
        Rule::name_selector => Ok(Selector::Name(unquote_and_unescape(validate_js_str(
            child.as_str().trim(),
        )?)?)),
        Rule::wildcard_selector => Ok(Selector::Wildcard),
        Rule::index_selector => Ok(Selector::Index(
            child
                .as_str()
                .trim()
                .parse::<i64>()
                .map_err(|e| (e, "wrong integer"))?,
        )),
        Rule::slice_selector => {
            let (start, end, step) = slice_selector(child)?;
            Ok(Selector::Slice(start, end, step))
        }
        Rule::filter_selector => Ok(Selector::Filter(logical_expr(next_down(child)?)?)),
        _ => Err(child.into()),
    }
}

pub fn function_expr(rule: Pair<Rule>) -> Parsed<TestFunction> {
    let fn_str = rule.as_str();
    let mut elems = rule.into_inner();
    let name = elems
        .next()
        .map(|e| e.as_str())
        .ok_or(JsonPathError::empty("function expression"))?;

    // Check if the function name is valid namely nothing between the name and the opening parenthesis
    if fn_str
        .chars()
        .nth(name.len())
        .map(|c| c != '(')
        .unwrap_or_default()
    {
        Err(JsonPathError::InvalidJsonPath(format!(
            "Invalid function expression `{}`",
            fn_str
        )))
    } else {
        let mut args = vec![];
        for arg in elems {
            let next = next_down(arg)?;
            match next.as_rule() {
                Rule::literal => args.push(FnArg::Literal(literal(next)?)),
                Rule::test => args.push(FnArg::Test(Box::new(test(next)?))),
                Rule::logical_expr => args.push(FnArg::Filter(logical_expr(next)?)),

                _ => return Err(next.into()),
            }
        }

        TestFunction::try_new(name, args)
    }
}

pub fn test(rule: Pair<Rule>) -> Parsed<Test> {
    let child = next_down(rule)?;
    match child.as_rule() {
        Rule::jp_query => Ok(Test::AbsQuery(jp_query(child)?)),
        Rule::rel_query => Ok(Test::RelQuery(rel_query(child)?)),
        Rule::function_expr => Ok(Test::Function(Box::new(function_expr(child)?))),
        _ => Err(child.into()),
    }
}

pub fn logical_expr(rule: Pair<Rule>) -> Parsed<Filter> {
    let mut ors = vec![];
    for r in rule.into_inner() {
        ors.push(logical_expr_and(r)?);
    }
    if ors.len() == 1 {
        Ok(ors
            .into_iter()
            .next()
            .ok_or(JsonPathError::empty("logical expression"))?)
    } else {
        Ok(Filter::Or(ors))
    }
}

pub fn logical_expr_and(rule: Pair<Rule>) -> Parsed<Filter> {
    let mut ands = vec![];
    for r in rule.into_inner() {
        ands.push(Filter::Atom(filter_atom(r)?));
    }
    if ands.len() == 1 {
        Ok(ands
            .into_iter()
            .next()
            .ok_or(JsonPathError::empty("logical expression"))?)
    } else {
        Ok(Filter::And(ands))
    }
}

pub fn singular_query_segments(rule: Pair<Rule>) -> Parsed<Vec<SingularQuerySegment>> {
    let mut segments = vec![];
    for r in rule.into_inner() {
        match r.as_rule() {
            Rule::name_segment => {
                let name = next_down(r)?;
                segments.push(SingularQuerySegment::Name(match name.as_rule() {
                    Rule::name_selector => {
                        unquote_and_unescape(validate_js_str(name.as_str().trim())?)?
                    }
                    _ => name.as_str().trim().to_string(),
                }));
            }
            Rule::index_segment => {
                segments.push(SingularQuerySegment::Index(
                    next_down(r)?
                        .as_str()
                        .trim()
                        .parse::<i64>()
                        .map_err(|e| (e, "int"))?,
                ));
            }
            _ => return Err(r.into()),
        }
    }
    Ok(segments)
}

pub fn slice_selector(rule: Pair<Rule>) -> Parsed<(Option<i64>, Option<i64>, Option<i64>)> {
    let mut start = None;
    let mut end = None;
    let mut step = None;
    let get_int = |r: Pair<Rule>| r.as_str().trim().parse::<i64>().map_err(|e| (e, "int"));

    for r in rule.into_inner() {
        match r.as_rule() {
            Rule::start => start = Some(get_int(r)?),
            Rule::end => end = Some(get_int(r)?),
            Rule::step => {
                step = {
                    if let Some(int) = r.into_inner().next() {
                        Some(get_int(int)?)
                    } else {
                        None
                    }
                }
            }

            _ => return Err(r.into()),
        }
    }
    Ok((start, end, step))
}

pub fn singular_query(rule: Pair<Rule>) -> Parsed<SingularQuery> {
    let query = next_down(rule)?;
    let segments = singular_query_segments(next_down(query.clone())?)?;
    match query.as_rule() {
        Rule::rel_singular_query => Ok(SingularQuery::Current(segments)),
        Rule::abs_singular_query => Ok(SingularQuery::Root(segments)),
        _ => Err(query.into()),
    }
}

pub fn comp_expr(rule: Pair<Rule>) -> Parsed<Comparison> {
    let mut children = rule.into_inner();

    let lhs = comparable(children.next().ok_or(JsonPathError::empty("comparison"))?)?;
    let op = children
        .next()
        .ok_or(JsonPathError::empty("comparison"))?
        .as_str();
    let rhs = comparable(children.next().ok_or(JsonPathError::empty("comparison"))?)?;

    Comparison::try_new(op, lhs, rhs)
}

/// Validates a JSONPath string literal according to RFC 9535
/// Control characters (U+0000 through U+001F and U+007F) are not allowed unescaped
/// in string literals, whether single-quoted or double-quoted
fn validate_js_str(s: &str) -> Parsed<&str> {
    for (i, c) in s.chars().enumerate() {
        if c <= '\u{001F}' {
            return Err(JsonPathError::InvalidJsonPath(format!(
                "Invalid control character U+{:04X} at position {} in string literal",
                c as u32, i
            )));
        }
    }

    Ok(s)
}

/// Converts a string literal to the string it denotes, per RFC 9535, section 2.3.1.2:
/// the quotes are removed and every escape sequence is replaced by the character it names.
fn unquote_and_unescape(literal: &str) -> Parsed<String> {
    let invalid =
        || JsonPathError::InvalidJsonPath(format!("Invalid string literal `{}`", literal));

    let body = literal
        .strip_prefix('"')
        .and_then(|s| s.strip_suffix('"'))
        .or_else(|| {
            literal
                .strip_prefix('\'')
                .and_then(|s| s.strip_suffix('\''))
        })
        .ok_or_else(invalid)?;

    if !body.contains('\\') {
        return Ok(body.to_string());
    }

    let mut unescaped = String::with_capacity(body.len());
    let mut chars = body.chars();
    while let Some(ch) = chars.next() {
        if ch != '\\' {
            unescaped.push(ch);
            continue;
        }
        match chars.next().ok_or_else(invalid)? {
            'b' => unescaped.push('\u{0008}'),
            'f' => unescaped.push('\u{000C}'),
            'n' => unescaped.push('\n'),
            'r' => unescaped.push('\r'),
            't' => unescaped.push('\t'),
            '/' => unescaped.push('/'),
            '\\' => unescaped.push('\\'),
            '"' => unescaped.push('"'),
            '\'' => unescaped.push('\''),
            'u' => unescaped.push(unescape_unicode(&mut chars).ok_or_else(invalid)?),
            _ => return Err(invalid()),
        }
    }
    Ok(unescaped)
}

/// Reads the `XXXX` of a `\uXXXX` escape, joining a surrogate pair into one scalar value.
fn unescape_unicode(chars: &mut std::str::Chars) -> Option<char> {
    fn hex4(chars: &mut std::str::Chars) -> Option<u32> {
        let mut code = 0;
        for _ in 0..4 {
            code = code * 16 + chars.next()?.to_digit(16)?;
        }
        Some(code)
    }

    match hex4(chars)? {
        high @ 0xD800..=0xDBFF => {
            if chars.next()? != '\\' || chars.next()? != 'u' {
                return None;
            }
            match hex4(chars)? {
                low @ 0xDC00..=0xDFFF => {
                    char::from_u32(0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00))
                }
                _ => None,
            }
        }
        0xDC00..=0xDFFF => None,
        code => char::from_u32(code),
    }
}

pub fn literal(rule: Pair<Rule>) -> Parsed<Literal> {
    fn parse_number(num: &str) -> Parsed<Literal> {
        let num = num.trim();

        if num.contains('.') || num.contains('e') || num.contains('E') {
            Ok(Literal::Float(num.parse::<f64>().map_err(|e| (e, num))?))
        } else {
            Ok(Literal::Int(
                num.trim().parse::<i64>().map_err(|e| (e, num))?,
            ))
        }
    }

    fn parse_string(string: &str) -> Parsed<Literal> {
        Ok(Literal::String(unquote_and_unescape(validate_js_str(
            string.trim(),
        )?)?))
    }

    let first = next_down(rule)?;

    match first.as_rule() {
        Rule::string => parse_string(first.as_str()),
        Rule::number => parse_number(first.as_str()),
        Rule::bool => Ok(Literal::Bool(first.as_str().parse::<bool>()?)),
        Rule::null => Ok(Literal::Null),

        _ => Err(first.into()),
    }
}

pub fn filter_atom(pair: Pair<Rule>) -> Parsed<FilterAtom> {
    let rule = next_down(pair)?;

    match rule.as_rule() {
        Rule::paren_expr => {
            let mut not = false;
            let mut logic_expr = None;
            for r in rule.into_inner() {
                match r.as_rule() {
                    Rule::not_op => not = true,
                    Rule::logical_expr => logic_expr = Some(logical_expr(r)?),
                    _ => (),
                }
            }

            logic_expr
                .map(|expr| FilterAtom::filter(expr, not))
                .ok_or("Logical expression is absent".into())
        }
        Rule::comp_expr => Ok(FilterAtom::cmp(Box::new(comp_expr(rule)?))),
        Rule::test_expr => {
            let mut not = false;
            let mut test_expr = None;
            for r in rule.into_inner() {
                match r.as_rule() {
                    Rule::not_op => not = true,
                    Rule::test => test_expr = Some(test(r)?),
                    _ => (),
                }
            }

            test_expr
                .map(|expr| FilterAtom::test(expr, not))
                .ok_or("Logical expression is absent".into())
        }
        _ => Err(rule.into()),
    }
}

pub fn comparable(rule: Pair<Rule>) -> Parsed<Comparable> {
    let rule = next_down(rule)?;
    match rule.as_rule() {
        Rule::literal => Ok(Comparable::Literal(literal(rule)?)),
        Rule::singular_query => Ok(Comparable::SingularQuery(singular_query(rule)?)),
        Rule::function_expr => {
            let tf = function_expr(rule)?;
            if tf.is_comparable() {
                Ok(Comparable::Function(tf))
            } else {
                Err(JsonPathError::InvalidJsonPath(format!(
                    "Function {} is not comparable",
                    tf.to_string()
                )))
            }
        }
        _ => Err(rule.into()),
    }
}

fn next_down(rule: Pair<Rule>) -> Parsed<Pair<Rule>> {
    let rule_as_str = rule.as_str().to_string();
    rule.into_inner()
        .next()
        .ok_or(JsonPathError::InvalidJsonPath(rule_as_str))
}
