//! Expression and path parsing for values and bind/connect.

use crate::ast::{BinaryOperator, Expression, Node, TypeCheckKind, UnaryOperator};
use crate::parser::lex::{name, qualified_name, starts_with_keyword, ws_and_comments};
use crate::parser::node_from_to;
use crate::parser::Input;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::map;
use nom::sequence::{delimited, preceded};
use nom::IResult;
use nom::Parser;

/// Numeric literal text: optional sign, mantissa, optional exponent (`5E9`, `195.3`, `6.022e23`).
fn numeric_literal_text(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    let frag = input.fragment();
    let mut i = 0usize;
    if matches!(frag.first(), Some(b'+' | b'-')) {
        i += 1;
    }
    let digit_start = i;
    while i < frag.len() && frag[i].is_ascii_digit() {
        i += 1;
    }
    if i == digit_start {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Digit,
        )));
    }
    if i < frag.len() && frag[i] == b'.' {
        i += 1;
        while i < frag.len() && frag[i].is_ascii_digit() {
            i += 1;
        }
    }
    if i < frag.len() && matches!(frag[i], b'e' | b'E') {
        i += 1;
        if i < frag.len() && matches!(frag[i], b'+' | b'-') {
            i += 1;
        }
        let exp_start = i;
        while i < frag.len() && frag[i].is_ascii_digit() {
            i += 1;
        }
        if i == exp_start {
            return Err(nom::Err::Error(nom::error::Error::new(
                input,
                nom::error::ErrorKind::Digit,
            )));
        }
    }
    let text = String::from_utf8_lossy(&frag[..i]).to_string();
    let (input, _) = nom::bytes::complete::take(i).parse(input)?;
    Ok((input, text))
}

fn classify_numeric_literal(text: &str) -> Expression {
    let normalized = text.trim();
    if normalized.contains('.') || normalized.chars().skip(1).any(|c| c == 'e' || c == 'E') {
        Expression::LiteralReal(normalized.to_string())
    } else {
        Expression::LiteralInteger(normalized.parse().unwrap_or(0))
    }
}

/// Integer literal.
fn literal_integer(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, text) = numeric_literal_text(input)?;
    if text.contains('.') || text.chars().skip(1).any(|c| c == 'e' || c == 'E') {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Digit,
        )));
    }
    Ok((
        input,
        node_from_to(start, input, classify_numeric_literal(&text)),
    ))
}

/// Real literal (decimal or scientific notation).
fn literal_real(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, text) = numeric_literal_text(input)?;
    if !text.contains('.') && !text.chars().skip(1).any(|c| c == 'e' || c == 'E') {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Digit,
        )));
    }
    Ok((
        input,
        node_from_to(start, input, classify_numeric_literal(&text)),
    ))
}

/// String literal: double-quoted.
fn literal_string(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, _) = tag(&b"\""[..]).parse(input)?;
    let frag = input.fragment();
    let mut i = 0;
    while i < frag.len() {
        if frag[i] == b'\\' && i + 1 < frag.len() {
            i += 2;
            continue;
        }
        if frag[i] == b'"' {
            let s = String::from_utf8_lossy(&frag[..i]).replace("\\\"", "\"");
            let (input, _) = nom::bytes::complete::take(i + 1).parse(input)?;
            return Ok((
                input,
                node_from_to(start, input, Expression::LiteralString(s)),
            ));
        }
        i += 1;
    }
    let s = String::from_utf8_lossy(frag).replace("\\\"", "\"");
    let (input, _) = nom::bytes::complete::take(frag.len()).parse(input)?;
    Ok((
        input,
        node_from_to(start, input, Expression::LiteralString(s)),
    ))
}

/// Boolean literal: true | false.
fn literal_boolean(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, v) = alt((
        map(tag(&b"true"[..]), |_| true),
        map(tag(&b"false"[..]), |_| false),
    ))
    .parse(input)?;
    Ok((
        input,
        node_from_to(start, input, Expression::LiteralBoolean(v)),
    ))
}

/// Feature reference: name or qualified name.
fn feature_ref_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, n) = qualified_name(input)?;
    Ok((input, node_from_to(start, input, Expression::FeatureRef(n))))
}

/// Metadata reference: @ qualified_name (e.g. @Safety, @Security for filter expressions).
fn metadata_ref_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, _) = tag(&b"@"[..]).parse(input)?;
    let (input, _) = ws_and_comments(input)?;
    let (input, n) = qualified_name(input)?;
    Ok((
        input,
        node_from_to(start, input, Expression::Classification { metaclass: n }),
    ))
}

fn constructor_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, _) = tag(&b"new"[..]).parse(input)?;
    let (input, _) = ws_and_comments(input)?;
    let (input, type_name) = qualified_name(input)?;
    let current = node_from_to(
        start,
        input,
        Expression::FeatureRef(format!("new {type_name}")),
    );
    postfix(input, start, current)
}

/// Literal only (no unit): integer, real, string, boolean.
fn literal_only(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        literal_boolean,
        literal_real,
        literal_integer,
        literal_string,
    ))
    .parse(input)
}

fn quoted_unit_string(input: Input<'_>) -> IResult<Input<'_>, String> {
    let quote = *input.fragment().first().ok_or_else(|| {
        nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Tag))
    })?;
    if quote != b'\'' && quote != b'"' {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Tag,
        )));
    }
    let (input, _) = nom::bytes::complete::take(1usize).parse(input)?;
    let frag = input.fragment();
    let mut i = 0usize;
    while i < frag.len() {
        if frag[i] == quote {
            let s = String::from_utf8_lossy(&frag[..i]).to_string();
            let (input, _) = nom::bytes::complete::take(i + 1).parse(input)?;
            return Ok((input, s));
        }
        if frag[i] == b'\\' && i + 1 < frag.len() {
            i += 2;
            continue;
        }
        i += 1;
    }
    Err(nom::Err::Error(nom::error::Error::new(
        input,
        nom::error::ErrorKind::Tag,
    )))
}

/// Unit text inside `[` … `]` (e.g. `kg`, `m/s`, `'$'`).
fn unit_name_in_brackets(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    if matches!(input.fragment().first(), Some(b'"' | b'\'')) {
        return quoted_unit_string(input);
    }
    let frag = input.fragment();
    let mut i = 0usize;
    while i < frag.len() {
        let c = frag[i];
        if c == b']' {
            break;
        }
        if c.is_ascii_whitespace() {
            break;
        }
        if c.is_ascii_alphanumeric() || matches!(c, b'_' | b'/' | b'-' | b'^' | b'.' | b'*' | b':')
        {
            i += 1;
            continue;
        }
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::AlphaNumeric,
        )));
    }
    if i == 0 {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::AlphaNumeric,
        )));
    }
    let s = String::from_utf8_lossy(&frag[..i]).trim().to_string();
    let (input, _) = nom::bytes::complete::take(i).parse(input)?;
    Ok((input, s))
}

/// Literal with optional [ unit ]: 1750 [kg] -> LiteralWithUnit(...).
fn literal_with_unit(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, value_node) = literal_only(input)?;
    let (input, _) = ws_and_comments(input)?;
    if !input.fragment().starts_with(b"[") {
        return Ok((input, value_node));
    }
    let (input, _) = tag(&b"["[..]).parse(input)?;
    let (input, _) = ws_and_comments(input)?;
    let unit_start = input;
    let (input, unit_name) = unit_name_in_brackets.parse(input)?;
    let unit_name_span = crate::parser::span_from_to(unit_start, input);
    let (input, _) = ws_and_comments(input)?;
    let (input, _) = tag(&b"]"[..]).parse(input)?;
    let unit = Node::new(
        unit_name_span.clone(),
        Expression::Bracket(Box::new(Node::new(
            unit_name_span,
            Expression::FeatureRef(unit_name),
        ))),
    );
    let expr = Expression::LiteralWithUnit {
        value: Box::new(value_node),
        unit: Box::new(unit),
    };
    Ok((input, node_from_to(start, input, expr)))
}

/// Parenthesized expression: `( expression )` for grouping, or `( e1, e2, ... )` as [`Expression::Tuple`].
fn parenthesized(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, _) = tag(&b"("[..]).parse(input)?;
    let (input, _) = ws_and_comments(input)?;
    let (input, first) = expression(input)?;
    let (input, _) = ws_and_comments(input)?;
    if input.fragment().starts_with(b")") {
        let (input, _) = tag(&b")"[..]).parse(input)?;
        // Include `(` … `)` in the span so consumers (e.g. Spec42 `text_from_span`) round-trip
        // the full parenthesized source, not only the inner expression.
        return Ok((input, node_from_to(start, input, first.value)));
    }
    let (input, _) = tag(&b","[..]).parse(input)?;
    let mut elements = vec![first];
    let mut input = input;
    loop {
        let (next, _) = ws_and_comments(input)?;
        if next.fragment().starts_with(b")") {
            let (input, _) = tag(&b")"[..]).parse(next)?;
            return Ok((
                input,
                node_from_to(start, input, Expression::Tuple(elements)),
            ));
        }
        let (next, expr) = expression(next)?;
        elements.push(expr);
        let (next, _) = ws_and_comments(next)?;
        if next.fragment().starts_with(b")") {
            let (input, _) = tag(&b")"[..]).parse(next)?;
            return Ok((
                input,
                node_from_to(start, input, Expression::Tuple(elements)),
            ));
        }
        if next.fragment().starts_with(b",") {
            let (next, _) = tag(&b","[..]).parse(next)?;
            input = next;
            continue;
        }
        return Err(nom::Err::Error(nom::error::Error::new(
            next,
            nom::error::ErrorKind::Tag,
        )));
    }
}

/// KerML null or empty sequence ().
fn null_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    let (input, _) = alt((
        map(tag(&b"null"[..]), |_| ()),
        map(
            delimited(tag(&b"("[..]), ws_and_comments, tag(&b")"[..])),
            |_| (),
        ),
    ))
    .parse(input)?;
    Ok((input, node_from_to(start, input, Expression::Null)))
}

/// SelectExpression: base `.?` selector
fn select_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, base) = feature_ref_primary(input)?;
    let (input, _) = tag(&b".?"[..]).parse(input)?;
    let (input, selector) = preceded(ws_and_comments, name).parse(input)?;
    Ok((
        input,
        node_from_to(
            start,
            input,
            Expression::Select {
                base: Box::new(base),
                selector,
            },
        ),
    ))
}

/// CollectExpression: base `.**` selector
fn collect_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, base) = feature_ref_primary(input)?;
    let (input, _) = tag(&b".**"[..]).parse(input)?;
    let (input, selector) = preceded(ws_and_comments, name).parse(input)?;
    Ok((
        input,
        node_from_to(
            start,
            input,
            Expression::Collect {
                base: Box::new(base),
                selector,
            },
        ),
    ))
}

/// SequenceExpression: `(` expr (`,` expr)* `)`
fn sequence_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = tag(&b"("[..]).parse(input)?;
    let (input, first) = preceded(ws_and_comments, expression).parse(input)?;
    let (input, rest) = nom::multi::many0(preceded(
        preceded(ws_and_comments, tag(&b","[..])),
        preceded(ws_and_comments, expression),
    ))
    .parse(input)?;
    let (input, _) = preceded(ws_and_comments, tag(&b")"[..])).parse(input)?;
    let mut args = vec![first];
    args.extend(rest);
    Ok((
        input,
        node_from_to(
            start,
            input,
            Expression::Invocation {
                callee: Box::new(node_from_to(start, start, Expression::Null)),
                args,
            },
        ),
    ))
}

/// KerML type test suffix: `istype Type`, `hastype Type`, or `as Type`.
fn type_check_kind_token(input: Input<'_>) -> IResult<Input<'_>, TypeCheckKind> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b"istype"[..]), |_| TypeCheckKind::Istype),
        map(tag(&b"hastype"[..]), |_| TypeCheckKind::Hastype),
        map(tag(&b"as"[..]), |_| TypeCheckKind::As),
    ))
    .parse(input)
}

fn type_check_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, kind) = type_check_kind_token(input)?;
    let (input, _) = ws_and_comments(input)?;
    let (input, type_name) = qualified_name(input)?;
    Ok((
        input,
        node_from_to(
            start,
            input,
            Expression::TypeCheck {
                kind,
                operand: None,
                type_name,
            },
        ),
    ))
}

/// Primary expression: literal with unit, literal only, metadata ref, feature ref, null, or parenthesized.
fn primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        literal_with_unit,
        literal_only,
        null_expression,
        metadata_ref_primary,
        type_check_primary,
        constructor_expression,
        collect_expression,
        select_expression,
        feature_ref_primary,
        parenthesized,
        sequence_expression,
    ))
    .parse(input)
}

/// Apply postfix #( expr ), . name, or :: name (qualified member access) to an expression.
fn postfix<'a>(
    input: Input<'a>,
    start: Input<'a>,
    current: Node<Expression>,
) -> IResult<Input<'a>, Node<Expression>> {
    let (input, _) = ws_and_comments(input)?;
    if input.fragment().starts_with(b"(") {
        let (input, _) = tag(&b"("[..]).parse(input)?;
        let (mut input, _) = ws_and_comments(input)?;
        let mut args = Vec::new();
        if input.fragment().starts_with(b")") {
            let (next, _) = tag(&b")"[..]).parse(input)?;
            let expr = Expression::Invocation {
                callee: Box::new(current),
                args,
            };
            return postfix(next, start, node_from_to(start, next, expr));
        }
        loop {
            let (next, arg) = expression(input)?;
            args.push(arg);
            let (next, _) = ws_and_comments(next)?;
            if next.fragment().starts_with(b")") {
                let (next, _) = tag(&b")"[..]).parse(next)?;
                let expr = Expression::Invocation {
                    callee: Box::new(current),
                    args,
                };
                return postfix(next, start, node_from_to(start, next, expr));
            }
            let (next, _) = tag(&b","[..]).parse(next)?;
            let (next, _) = ws_and_comments(next)?;
            input = next;
        }
    }
    if input.fragment().starts_with(b"#") {
        let (input, _) = tag(&b"#"[..]).parse(input)?;
        let (input, _) = preceded(ws_and_comments, tag(&b"("[..])).parse(input)?;
        let (input, index_node) = preceded(ws_and_comments, expression).parse(input)?;
        let (input, _) = preceded(ws_and_comments, tag(&b")"[..])).parse(input)?;
        let expr = Expression::Index {
            base: Box::new(current),
            index: Box::new(index_node),
        };
        return postfix(input, start, node_from_to(start, input, expr));
    }
    if input.fragment().starts_with(b"::") {
        let (input, _) = tag(&b"::"[..]).parse(input)?;
        let (input, _) = ws_and_comments(input)?;
        let (input, member) = name(input)?;
        let expr = Expression::MemberAccess(Box::new(current), member);
        return postfix(input, start, node_from_to(start, input, expr));
    }
    if input.fragment().starts_with(b".") {
        let (input, _) = tag(&b"."[..]).parse(input)?;
        let (input, _) = ws_and_comments(input)?;
        let (input, member) = name(input)?;
        let expr = Expression::MemberAccess(Box::new(current), member);
        return postfix(input, start, node_from_to(start, input, expr));
    }
    if let Ok((after_kind, kind)) = type_check_kind_token(input) {
        if let Ok((after_type, type_name)) = qualified_name(after_kind) {
            let expr = node_from_to(
                start,
                after_type,
                Expression::TypeCheck {
                    kind,
                    operand: Some(Box::new(current)),
                    type_name,
                },
            );
            return postfix(after_type, start, expr);
        }
    }
    if starts_with_keyword(input.fragment(), b"meta") {
        let (input, _) = tag(&b"meta"[..]).parse(input)?;
        let (input, _) = ws_and_comments(input)?;
        let (input, metaclass) = qualified_name(input)?;
        let expr = Expression::MetaCast {
            base: Box::new(current),
            metaclass,
        };
        return postfix(input, start, node_from_to(start, input, expr));
    }
    Ok((input, current))
}

fn logical_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b"and"[..]), |_| "&&".to_string()),
        map(tag(&b"or"[..]), |_| "||".to_string()),
        map(tag(&b"xor"[..]), |_| "xor".to_string()),
        map(tag(&b"&&"[..]), |_| "&&".to_string()),
        map(tag(&b"||"[..]), |_| "||".to_string()),
    ))
    .parse(input)
}

/// Implication: lower precedence than `or` / `and` (constraint and filter bodies).
fn implies_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    preceded(ws_and_comments, tag(&b"implies"[..]))
        .map(|_| "implies".to_string())
        .parse(input)
}

fn equality_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b"==="[..]), |_| "===".to_string()),
        map(tag(&b"!=="[..]), |_| "!==".to_string()),
        map(tag(&b"=="[..]), |_| "==".to_string()),
        map(tag(&b"!="[..]), |_| "!=".to_string()),
    ))
    .parse(input)
}

fn comparison_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b">="[..]), |_| ">=".to_string()),
        map(tag(&b"<="[..]), |_| "<=".to_string()),
        map(tag(&b">"[..]), |_| ">".to_string()),
        map(tag(&b"<"[..]), |_| "<".to_string()),
        map(tag(&b".."[..]), |_| "..".to_string()),
    ))
    .parse(input)
}

fn additive_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b"+"[..]), |_| "+".to_string()),
        map(tag(&b"-"[..]), |_| "-".to_string()),
        map(tag(&b"|"[..]), |_| "|".to_string()),
        map(tag(&b"&"[..]), |_| "&".to_string()),
    ))
    .parse(input)
}

fn multiplicative_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b"**"[..]), |_| "**".to_string()),
        map(tag(&b"*"[..]), |_| "*".to_string()),
        map(tag(&b"/"[..]), |_| "/".to_string()),
        map(tag(&b"%"[..]), |_| "%".to_string()),
        map(tag(&b"^"[..]), |_| "^".to_string()),
    ))
    .parse(input)
}

fn binary_chain_with<'a, P, N>(
    mut input: Input<'a>,
    start: Input<'a>,
    mut left: Node<Expression>,
    mut op_parser: P,
    mut next_parser: N,
) -> IResult<Input<'a>, Node<Expression>>
where
    P: Parser<Input<'a>, Output = String, Error = nom::error::Error<Input<'a>>>,
    N: Parser<Input<'a>, Output = Node<Expression>, Error = nom::error::Error<Input<'a>>>,
{
    loop {
        let Ok((next_input, op)) = op_parser.parse(input) else {
            return Ok((input, left));
        };
        let (next_input, right) = next_parser.parse(next_input)?;
        left = node_from_to(
            start,
            next_input,
            Expression::BinaryOp {
                op: BinaryOperator::from_token(&op),
                left: Box::new(left),
                right: Box::new(right),
            },
        );
        input = next_input;
    }
}

/// Unary operator token: + - ~ not (KerML UnaryOperator).
fn unary_op_token(input: Input<'_>) -> IResult<Input<'_>, String> {
    let (input, _) = ws_and_comments(input)?;
    alt((
        map(tag(&b"not"[..]), |_| "not".to_string()),
        map(tag(&b"~"[..]), |_| "~".to_string()),
        map(tag(&b"+"[..]), |_| "+".to_string()),
        map(tag(&b"-"[..]), |_| "-".to_string()),
    ))
    .parse(input)
}

/// Parse unary prefixes then primary; build nested UnaryOp from the right.
fn unary_and_primary(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, prefixes) = nom::multi::many0(unary_op_token).parse(input)?;
    let primary_start = input;
    let (input, primary_node) = primary(input)?;
    let (input, after_postfix) = postfix(input, primary_start, primary_node)?;
    let mut expr = after_postfix;
    for op in prefixes.into_iter().rev() {
        expr = node_from_to(
            start,
            input,
            Expression::UnaryOp {
                op: UnaryOperator::from_token(&op),
                operand: Box::new(expr),
            },
        );
    }
    Ok((input, expr))
}

fn multiplicative_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, left) = unary_and_primary(input)?;
    binary_chain_with(
        input,
        start,
        left,
        multiplicative_op_token,
        unary_and_primary,
    )
}

fn additive_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, left) = multiplicative_expression(input)?;
    binary_chain_with(
        input,
        start,
        left,
        additive_op_token,
        multiplicative_expression,
    )
}

fn comparison_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, left) = additive_expression(input)?;
    binary_chain_with(input, start, left, comparison_op_token, additive_expression)
}

fn equality_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, left) = comparison_expression(input)?;
    binary_chain_with(input, start, left, equality_op_token, comparison_expression)
}

fn logical_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, left) = equality_expression(input)?;
    binary_chain_with(input, start, left, logical_op_token, equality_expression)
}

/// Full expression with precedence-aware binary parsing.
pub(crate) fn expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, left) = logical_expression(input)?;
    binary_chain_with(input, start, left, implies_op_token, logical_expression)
}

/// Path expression: qualified name and/or member access (for bind/connect).
/// Supports `A`, `A::B::C`, `A.B.C`, and combinations like `A::B.C`.
pub(crate) fn path_expression(input: Input<'_>) -> IResult<Input<'_>, Node<Expression>> {
    let start = input;
    let (input, _) = ws_and_comments(input)?;
    // `qualified_name` covers `::`-separated chains (common in SysML examples for feature chains).
    // We keep it as a single FeatureRef string, then allow additional `.` member access.
    let (input, first) = crate::parser::lex::qualified_name(input)?;
    let mut expr = Expression::FeatureRef(first);
    let mut rest = input;
    loop {
        let (next, _) = ws_and_comments(rest)?;
        if !next.fragment().starts_with(b".") {
            break;
        }
        let (next, _) = tag(&b"."[..]).parse(next)?;
        let (next, _) = ws_and_comments(next)?;
        let (next, member) = name(next)?;
        expr =
            Expression::MemberAccess(Box::new(Node::new(crate::ast::Span::dummy(), expr)), member);
        rest = next;
    }
    Ok((rest, node_from_to(start, rest, expr)))
}

#[cfg(test)]
mod tests {
    use super::*;
    use nom_locate::LocatedSpan;

    fn span_input(text: &str) -> Input<'_> {
        LocatedSpan::new(text.as_bytes())
    }

    #[test]
    fn expression_parses_implies_lower_than_or() {
        let input = span_input("a or b implies c");
        let (_, node) = expression(input).expect("expression");
        match &node.value {
            Expression::BinaryOp { op, left, right } => {
                assert_eq!(op, &BinaryOperator::Implies);
                match &left.value {
                    Expression::BinaryOp { op, .. } => assert_eq!(op, &BinaryOperator::Or),
                    other => panic!("expected or on lhs, got {other:?}"),
                }
                assert!(matches!(&right.value, Expression::FeatureRef(s) if s == "c"));
            }
            other => panic!("expected implies, got {other:?}"),
        }
    }
}
