//! Control expression parsing for Fermented UnrealScript.
//!
//! ## Condition boundary recovery and legacy compatibility
//!
//! Fermented UnrealScript allows omitting parentheses `(...)` around
//! condition expressions of `if`/`while`/`until` and similar constructs.
//! Conditions are therefore parsed as ordinary expressions by default.
//!
//! This means that a leading parenthesized expression may still be part of a
//! larger condition:
//!
//! ```unrealscript
//! if (2 + 2) * 2 < 7 { ... }
//! while (Index + 1) < Count DoWork();
//! ```
//!
//! For compatibility with older `UnrealScript` code, we apply one conservative
//! legacy cut-off rule:
//!
//! If the condition begins with a parenthesized expression, and the token after
//! the matching `)` is identifier-like, the parser treats only the
//! parenthesized expression as the condition. The following identifier-like
//! token is left for the branch body.
//!
//! This prevents the parser from accidentally consuming the following
//! statement/body as part of the condition in older code such as:
//!
//! ```unrealscript
//! if ( AIController(Controller) != None ) Cross = vect(0,0,0);
//! ```
//!
//! Without the legacy cut-off, a permissive expression parser could interpret
//! the identifier-like body opener, such as `Cross`, as an operator-like
//! continuation of the parenthesized condition.
//!
//! Operator tokens such as `*`, `+`, `<`, `==`, etc. do not trigger this
//! legacy cut-off. They allow the normal expression parser to continue the
//! condition.
//!
//! Trade-off: if an identifier-like token after the closing `)` was intended as
//! a custom/named operator, the parser prefers the legacy interpretation and
//! ends the condition at the closing `)`. Write the condition with additional
//! parentheses or use an unambiguous operator form.

use crate::ast::{BranchBody, Expression, ExpressionRef};
use crate::lexer::{Keyword, Token, TokenPosition, TokenSpan};
use crate::parser::{
    ParseErrorKind, ParseExpressionResult, ParseResult, Parser, ResultRecoveryExt,
    diagnostic_labels,
};

pub(super) mod for_loop;

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Returns the opening `(` of a legacy parenthesized condition cut-off
    /// when it applies.
    fn find_legacy_parenthesized_condition_opening_position(&mut self) -> Option<TokenPosition> {
        let Some((Token::LeftParenthesis, left_parenthesis_position)) =
            self.peek_token_and_position()
        else {
            return None;
        };

        let right_parenthesis_position =
            self.file().matching_delimiter(left_parenthesis_position)?;

        self.peek_token_after_position(right_parenthesis_position)
            .is_some_and(|token| token.is_valid_identifier_name())
            .then_some(left_parenthesis_position)
    }

    /// Parses a control-flow condition.
    ///
    /// Conditions are parsed as ordinary expressions unless the legacy
    /// parenthesized-condition cut-off applies.
    fn parse_condition(
        &mut self,
        invalid_start_error_kind: ParseErrorKind,
    ) -> ParseExpressionResult<'src, 'arena> {
        if self.next_token_definitely_cannot_start_expression() {
            let keyword_position = self.last_consumed_position_or_start();
            let error_position = self.peek_position_or_eof();
            return Err(self
                .make_error_at(invalid_start_error_kind, error_position)
                .blame_token(error_position)
                .related_token(diagnostic_labels::EXPRESSION_REQUIRED_BY, keyword_position));
        }
        if let Some(left_parenthesis_position) =
            self.find_legacy_parenthesized_condition_opening_position()
        {
            self.advance();
            Ok(self.parse_parenthesized_expression_tail(left_parenthesis_position))
        } else {
            Ok(self.parse_expression())
        }
    }

    /// Parses a branch body for a control-flow construct.
    ///
    /// Normalizes the following source forms into a [`BranchBody`]:
    ///
    /// - empty body with semicolon: `if (cond);`
    /// - empty body before a closing `}`: `if (cond) }`
    /// - non-empty block body: `if (cond) { ... }`
    /// - non-empty single-expression body: `if (cond) expr;`
    ///
    /// For non-block bodies, this method consumes a trailing `;` when present
    /// and records its position in the returned [`BranchBody`].
    fn parse_branch_body(
        &mut self,
        branch_owner_keyword_position: TokenPosition,
    ) -> BranchBody<'src, 'arena> {
        let Some((first_token, first_token_position)) = self.peek_token_and_position() else {
            return self.recover_missing_branch_body(branch_owner_keyword_position);
        };
        // `if (is_condition);`
        if first_token == Token::Semicolon {
            return self.parse_empty_semicolon_branch_body(first_token_position);
        }
        // `{ ... if (is_condition) }`
        if first_token == Token::RightBrace {
            return self.make_empty_branch_body_before_closing_brace();
        }
        self.parse_non_empty_branch_body(branch_owner_keyword_position)
    }

    fn recover_missing_branch_body(
        &mut self,
        branch_owner_keyword_position: TokenPosition,
    ) -> BranchBody<'src, 'arena> {
        let error = self
            .make_error_at_last_consumed(ParseErrorKind::ControlFlowBodyExpected)
            .blame_token(self.file.eof())
            .related_token(
                diagnostic_labels::EXPRESSION_REQUIRED_BY,
                branch_owner_keyword_position,
            )
            .related_token(
                diagnostic_labels::EXPRESSION_EXPECTED_AFTER,
                self.last_consumed_position_or_start(),
            );
        let end_anchor_token_position = error.covered_span.end;
        self.report_error(error);
        BranchBody {
            expression: None,
            semicolon_position: None,
            end_anchor_token_position,
        }
    }

    fn parse_empty_semicolon_branch_body(
        &mut self,
        semicolon_position: TokenPosition,
    ) -> BranchBody<'src, 'arena> {
        self.advance(); // ';'
        BranchBody {
            expression: None,
            semicolon_position: Some(semicolon_position),
            end_anchor_token_position: semicolon_position,
        }
    }

    fn make_empty_branch_body_before_closing_brace(&mut self) -> BranchBody<'src, 'arena> {
        BranchBody {
            expression: None,
            semicolon_position: None,
            end_anchor_token_position: self.last_consumed_position_or_start(),
        }
    }

    fn parse_non_empty_branch_body(
        &mut self,
        branch_owner_keyword_position: TokenPosition,
    ) -> BranchBody<'src, 'arena> {
        let branch_expression = self
            .parse_required_expression_with_context(
                ParseErrorKind::ControlFlowBodyExpected,
                branch_owner_keyword_position,
                self.last_consumed_position_or_start(),
            )
            .unwrap_or_fallback(self);
        let end_anchor_token_position = branch_expression.span().end;
        // A block body in `if {...}` or `if {...};` owns its own terminator;
        // a following `;` does not belong to the branch body.
        if let Expression::Block(_) = *branch_expression {
            return BranchBody {
                expression: Some(branch_expression),
                semicolon_position: None,
                end_anchor_token_position,
            };
        }
        // Single-expression bodies own their optional trailing `;`.
        let trailing_semicolon_position = if self.eat(Token::Semicolon) {
            self.last_consumed_position()
        } else {
            None
        };
        BranchBody {
            expression: Some(branch_expression),
            semicolon_position: trailing_semicolon_position,
            end_anchor_token_position: trailing_semicolon_position
                .unwrap_or(end_anchor_token_position),
        }
    }

    fn parse_condition_and_branch_body(
        &mut self,
        branch_owner_keyword_position: TokenPosition,
        condition_expected_error_kind: ParseErrorKind,
    ) -> ParseResult<'src, 'arena, (ExpressionRef<'src, 'arena>, BranchBody<'src, 'arena>)> {
        let condition_start_position = self.peek_position_or_eof();
        let condition = self.parse_condition(condition_expected_error_kind)?;
        // Prefer diagnosing `if {...}` as a missing condition instead of
        // treating the block as the condition and then reporting
        // a missing body.
        if let Expression::Block(..) = *condition
            && self.next_token_definitely_cannot_start_expression()
        {
            return Err(self
                .make_error_at(condition_expected_error_kind, condition_start_position)
                .blame_token(condition_start_position)
                .related_token(
                    diagnostic_labels::EXPRESSION_REQUIRED_BY,
                    branch_owner_keyword_position,
                )
                .related("branch_body", *condition.span()));
        }
        let branch_body = self.parse_branch_body(branch_owner_keyword_position);
        Ok((condition, branch_body))
    }

    /// Parses an `if` expression after the `if` keyword.
    ///
    /// The resulting [`Expression::If`] spans from `if_keyword_position` to the
    /// end of the `if` body, or to the end of the `else` body if one is
    /// present.
    #[must_use]
    pub(super) fn parse_if_tail(
        &mut self,
        if_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (condition, then_body) = match self
            .parse_condition_and_branch_body(if_keyword_position, ParseErrorKind::ConditionExpected)
        {
            Ok(condition_and_body) => condition_and_body,
            Err(error) => return error.fallback(self),
        };

        let (else_body, if_expression_end_position) = if self.peek_keyword() == Some(Keyword::Else)
        {
            self.advance(); // 'else'
            let else_body = self.parse_branch_body(self.last_consumed_position_or_start());
            let else_body_end_position = else_body.end_anchor_token_position;
            (Some(else_body), else_body_end_position)
        } else {
            (None, then_body.end_anchor_token_position)
        };

        let span = TokenSpan::range(if_keyword_position, if_expression_end_position);
        self.arena.alloc_node(
            Expression::If {
                condition,
                then_body,
                else_body,
            },
            span,
        )
    }

    /// Parses a `while` expression after the `while` keyword.
    ///
    /// The resulting [`Expression::While`] spans from `while_keyword_position`
    /// to the end of its body.
    #[must_use]
    pub(super) fn parse_while_tail(
        &mut self,
        while_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (condition, body) = match self.parse_condition_and_branch_body(
            while_keyword_position,
            ParseErrorKind::ConditionExpected,
        ) {
            Ok(condition_and_body) => condition_and_body,
            Err(error) => return error.fallback(self),
        };
        let span = TokenSpan::range(while_keyword_position, body.end_anchor_token_position);
        self.arena
            .alloc_node(Expression::While { condition, body }, span)
    }

    /// Parses a `do ... until ...` expression after the `do` keyword.
    ///
    /// The resulting [`Expression::DoUntil`] spans from `do_keyword_position`
    /// to the end of the condition.
    #[must_use]
    pub(super) fn parse_do_until_tail(
        &mut self,
        do_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let body = self.parse_branch_body(do_keyword_position);

        let until_keyword_was_missing = self
            .expect_keyword(Keyword::Until, ParseErrorKind::DoMissingUntil)
            .widen_error_span_from(do_keyword_position)
            .related_token("do_keyword", do_keyword_position)
            .report_error(self);
        let condition = if until_keyword_was_missing {
            self.make_error_expression_at(body.end_anchor_token_position)
        } else {
            self.parse_condition(ParseErrorKind::ConditionExpected)
                .related_token("do_keyword", do_keyword_position)
                .unwrap_or_fallback(self)
        };
        let span = TokenSpan::range(do_keyword_position, condition.span().end);
        self.arena
            .alloc_node(Expression::DoUntil { condition, body }, span)
    }

    /// Parses a `foreach` expression after the `foreach` keyword.
    ///
    /// The iterator expression must start with an identifier-like token. Later
    /// stages validate its full shape.
    ///
    /// The resulting [`Expression::ForEach`] spans from
    /// `foreach_keyword_position` to the end of the body.
    #[must_use]
    pub(super) fn parse_foreach_tail(
        &mut self,
        foreach_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        // End-of-file is allowed through so the shared condition parser can
        // produce the missing-iterator diagnostic.
        if self
            .peek_token()
            .is_some_and(|token| !token.is_valid_identifier_name())
        {
            let error_position = self.peek_position_or_eof();
            return self
                .make_error_at(
                    ParseErrorKind::ForEachIteratorExpressionExpected,
                    error_position,
                )
                .blame_token(error_position)
                .related_token(
                    diagnostic_labels::EXPRESSION_REQUIRED_BY,
                    foreach_keyword_position,
                )
                .fallback(self);
        }
        let iterator_expression =
            match self.parse_condition(ParseErrorKind::ForEachIteratorExpressionExpected) {
                Ok(iterator_expression) => iterator_expression,
                Err(error) => return error.fallback(self),
            };
        let body = self.parse_branch_body(foreach_keyword_position);
        let span = TokenSpan::range(foreach_keyword_position, body.end_anchor_token_position);
        self.arena.alloc_node(
            Expression::ForEach {
                iterator_expression,
                body,
            },
            span,
        )
    }

    /// Parses a `return` expression after the `return` keyword.
    ///
    /// Consumes an optional return value expression. The terminating `;` is
    /// left for the surrounding expression parser.
    #[must_use]
    pub(super) fn parse_return_tail(
        &mut self,
        return_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (return_value, span) = match self.peek_token() {
            // Non-`;` followers are parsed here so invalid return values are
            // reported as return-specific diagnostics.
            None | Some(Token::Semicolon) => (None, TokenSpan::new(return_keyword_position)),
            _ => {
                let return_value = self
                    .parse_required_expression(
                        ParseErrorKind::ReturnValueInvalidStart,
                        return_keyword_position,
                    )
                    .unwrap_or_fallback(self);
                let span = TokenSpan::range(return_keyword_position, return_value.span().end);
                (Some(return_value), span)
            }
        };
        self.arena
            .alloc_node(Expression::Return(return_value), span)
    }

    /// Parses a `break` expression after the `break` keyword.
    ///
    /// Consumes an optional break value expression. The terminating `;` is left
    /// for the surrounding expression parser.
    #[must_use]
    pub(super) fn parse_break_tail(
        &mut self,
        break_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (break_value, span) = match self.peek_token() {
            // Non-`;` followers are parsed here so invalid break values are
            // reported as break-specific diagnostics.
            None | Some(Token::Semicolon) => (None, TokenSpan::new(break_keyword_position)),
            _ => {
                let break_value = self
                    .parse_required_expression(
                        ParseErrorKind::BreakValueInvalidStart,
                        break_keyword_position,
                    )
                    .unwrap_or_fallback(self);
                let span = TokenSpan::range(break_keyword_position, break_value.span().end);
                (Some(break_value), span)
            }
        };
        self.arena.alloc_node(Expression::Break(break_value), span)
    }

    /// Parses the continuation of a `goto` expression after its keyword.
    ///
    /// Accepts either a name literal or an identifier as the target label.
    #[must_use]
    pub(super) fn parse_goto_tail(
        &mut self,
        goto_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        // Labels may be written either as UnrealScript name literals or
        // as bare names.
        if let Some((label_token, label_position)) = self.peek_token_and_position()
            && (label_token == Token::NameLiteral || label_token.is_valid_identifier_name())
        {
            self.advance();
            return self.arena.alloc_node_between(
                Expression::Goto(label_position),
                goto_keyword_position,
                label_position,
            );
        }
        let error_position = self.peek_position_or_eof();
        self.make_error_at_last_consumed(ParseErrorKind::GotoMissingLabel)
            .widen_error_span_from(goto_keyword_position)
            .blame_token(error_position)
            .related_token("goto_keyword", goto_keyword_position)
            .report_error(self);
        self.make_error_expression_at(goto_keyword_position)
    }
}
