//! Control expression parsing for Fermented `UnrealScript`.
//!
//! ## Condition parsing and legacy compatibility
//!
//! Fermented `UnrealScript` allows omitting parentheses `(...)` around the
//! condition expression of `if`/`while`/etc. For compatibility with older
//! `UnrealScript` code, we also apply a special rule:
//!
//! If a condition starts with `(`, we parse the condition as exactly the
//! matching parenthesized subexpression and stop at its corresponding `)`.
//! In other words, `( ... )` must cover the whole condition; trailing tokens
//! like `* c == d` are not allowed to continue the condition.
//!
//! 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);
//! ```
//!
//! Trade-off: you cannot write `if (a + b) * c == d`;
//! write `if ((a + b) * c == d)` or `if d == (a + b) * c` instead.
//!
//! ## Disambiguation of `for` as loop vs expression
//!
//! Unlike other control-flow keywords, `for` is disambiguated from a functions
//! or variables with the same name. This is done syntactically in
//! [`Parser::is_for_loop_header_ahead`]: a `for` token followed by
//! a `(` whose contents contain a top-level `;` is unambiguously a loop header.
//!
//! This rule is lightweight, local, and robust, and mirrors the fixed grammar
//! `for (init; condition; step)` without requiring name resolution.
//!
//! ### Why this is not done for `if` / `while` / `do`
//!
//! No similarly reliable way to discriminate `if`, `while`, or related
//! keywords at this stage of parsing: their parenthesized forms are
//! indistinguishable from single argument function calls.
//!
//! Supporting these keywords as identifiers would complicate parsing
//! disproportionately and we always treat them as openers for conditional and
//! cycle expressions. This matches common `UnrealScript` usage and intentionally
//! drops support for moronic design choices where such names were reused
//! as variables or functions (like what author did by declaring
//! a `For` function in Acedia).
//!
//! ### But what about `switch`?
//!
//! `switch` is handled separately because, in existing `UnrealScript` code,
//! it may appear either as a keyword-led construct or as an identifier.
//!
//! Its disambiguation rule is simpler than for `for`: if the next token is
//! `(`, `switch` is parsed as a `switch` expression; otherwise it remains
//! available as an identifier.
//!
//! This rule is local and purely syntactic, matching the behavior expected by
//! the existing codebase we support. The actual parsing of `switch` expressions
//! lives in a separate module because the construct itself is more involved
//! than the control-flow forms handled here.

use crate::ast::{AstSpan, BranchBody, Expression, ExpressionRef};
use crate::lexer::{Keyword, Token, TokenPosition};
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a control-flow condition.
    ///
    /// If the next token is `(`, attempts to consume one parenthesized
    /// subexpression and returns it wrapped as [`Expression::Parentheses`].
    /// Otherwise consumes a general expression.
    fn parse_condition(&mut self) -> ExpressionRef<'src, 'arena> {
        if let Some((Token::LeftParenthesis, left_parenthesis_position)) =
            self.peek_token_and_position()
        {
            self.advance(); // '('
            let condition_expression = self.parse_expression();
            let right_parenthesis_position = self
                .expect(
                    Token::RightParenthesis,
                    ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis {
                        left_parenthesis_position,
                    },
                )
                .widen_error_span_from(left_parenthesis_position)
                .sync_error_at(self, SyncLevel::CloseParenthesis)
                .unwrap_or_fallback(self);
            self.arena.alloc_node_between(
                Expression::Parentheses(condition_expression),
                left_parenthesis_position,
                right_parenthesis_position,
            )
        } else {
            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) -> BranchBody<'src, 'arena> {
        let Some((first_token, first_token_position)) = self.peek_token_and_position() else {
            let error = self.make_error_here(ParseErrorKind::MissingBranchBody);
            self.report_error(error);
            return BranchBody {
                expression: None,
                semicolon_position: None,
                end_anchor_token_position: error.covered_span.token_to,
            };
        };
        // `if (is_condition);`
        if first_token == Token::Semicolon {
            self.advance(); // ';'
            return BranchBody {
                expression: None,
                semicolon_position: Some(first_token_position),
                end_anchor_token_position: first_token_position,
            };
        }
        // `{ ... if (is_condition) }`
        if first_token == Token::RightBrace {
            return BranchBody {
                expression: None,
                semicolon_position: None,
                // `unwrap` actually triggering is effectively impossible,
                // because by the time a branch body is parsed, some prior token
                // (e.g. `if`, `)`, etc.) has already been consumed,
                // so the parser should have a last-consumed position
                end_anchor_token_position: self
                    .last_consumed_position()
                    .unwrap_or(first_token_position),
            };
        }
        let branch_expression = self.parse_expression();
        let end_anchor_token_position = branch_expression.span().token_to;
        // 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,
            };
        }
        // For single-expression bodies, consume a trailing semicolon if present
        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),
        }
    }

    /// 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(crate) fn parse_if_tail(
        &mut self,
        if_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let condition = self.parse_condition();
        let body = self.parse_branch_body();

        let (else_body, if_end_position) = if self.peek_keyword() == Some(Keyword::Else) {
            self.advance(); // 'else'
            let else_body = self.parse_branch_body();
            let else_body_end = else_body.end_anchor_token_position;
            (Some(else_body), else_body_end)
        } else {
            (None, body.end_anchor_token_position)
        };

        let span = AstSpan::range(if_keyword_position, if_end_position);
        self.arena.alloc_node(
            Expression::If {
                condition,
                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(crate) fn parse_while_tail(
        &mut self,
        while_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let condition = self.parse_condition();
        let body = self.parse_branch_body();
        let span = AstSpan::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(crate) fn parse_do_until_tail(
        &mut self,
        do_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let body = self.parse_branch_body();

        let condition = if self
            .expect_keyword(Keyword::Until, ParseErrorKind::DoMissingUntil)
            .widen_error_span_from(do_keyword_position)
            .report_error(self)
        {
            crate::arena::ArenaNode::new_in(
                Expression::Error,
                AstSpan::new(body.end_anchor_token_position),
                self.arena,
            )
        } else {
            self.parse_condition()
        };
        let span = AstSpan::range(do_keyword_position, condition.span().token_to);
        self.arena
            .alloc_node(Expression::DoUntil { condition, body }, span)
    }

    /// Parses a `foreach` expression after the `foreach` keyword.
    ///
    /// The iterator part is consumed as a regular expression, followed by a
    /// branch body.
    ///
    /// The resulting [`Expression::ForEach`] spans from
    /// `foreach_keyword_position` to the end of the body.
    #[must_use]
    pub(crate) fn parse_foreach_tail(
        &mut self,
        foreach_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        // UnrealScript `foreach` iterator expressions are simple enough that
        // they do not need the special parenthesized-condition handling used by
        // `parse_condition()`.
        let iterated_expression = self.parse_expression();

        let body = self.parse_branch_body();
        let span = AstSpan::range(foreach_keyword_position, body.end_anchor_token_position);
        self.arena.alloc_node(
            Expression::ForEach {
                iterated_expression,
                body,
            },
            span,
        )
    }

    /// Returns whether the upcoming tokens have the syntactic shape of a
    /// `for (...)` header.
    ///
    /// More precisely, this returns `true` iff the next token is `(` and a
    /// top-level `;` appears before the matching `)` is closed or input ends.
    ///
    /// This is used only for loop-vs-identifier disambiguation.
    pub(crate) fn is_for_loop_header_ahead(&mut self) -> bool {
        if self.peek_token() != Some(Token::LeftParenthesis) {
            return false;
        }
        let mut nesting_depth: usize = 1;
        let mut lookahead_token_offset: usize = 1;
        while let Some(next_token) = self.peek_token_at(lookahead_token_offset) {
            match next_token {
                Token::LeftParenthesis => nesting_depth += 1,
                Token::RightParenthesis => {
                    if nesting_depth <= 1 {
                        // End of the immediate `for (...)` group without a
                        // top-level `;`: not a loop header.
                        return false;
                    }
                    nesting_depth -= 1;
                }
                Token::Semicolon if nesting_depth == 1 => return true,
                _ => (),
            }
            lookahead_token_offset += 1;
        }
        false
    }

    /// Parses a `for` expression after the `for` keyword.
    ///
    /// This method expects the standard header shape
    /// `for (initialization; condition; step)` and then parses a branch body.
    ///
    /// Each header component may be omitted. The resulting [`Expression::For`]
    /// spans from `for_keyword_position` to the end of the body.
    #[must_use]
    pub(crate) fn parse_for_tail(
        &mut self,
        for_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        // This path is expected to be entered only after
        // `is_for_loop_header_ahead()`, so the opening `(` and at least one
        // top-level `;` should already be structurally guaranteed.
        self.expect(
            Token::LeftParenthesis,
            ParseErrorKind::ForMissingOpeningParenthesis,
        )
        .widen_error_span_from(for_keyword_position)
        .report_error(self);

        let initialization = if self.peek_token() == Some(Token::Semicolon) {
            self.advance();
            None
        } else {
            let init = self.parse_expression();
            self.expect(
                Token::Semicolon,
                ParseErrorKind::ForMissingInitializationSemicolon,
            )
            .report_error(self);
            Some(init)
        };

        let condition = if self.peek_token() == Some(Token::Semicolon) {
            self.advance();
            None
        } else {
            let condition = self.parse_expression();
            self.expect(
                Token::Semicolon,
                ParseErrorKind::ForMissingConditionSemicolon,
            )
            .report_error(self);
            Some(condition)
        };

        let step = if self.peek_token() == Some(Token::RightParenthesis) {
            self.advance();
            None
        } else {
            let step = self.parse_expression();
            self.expect(
                Token::RightParenthesis,
                ParseErrorKind::ForMissingClosingParenthesis,
            )
            .widen_error_span_from(for_keyword_position)
            .sync_error_at(self, SyncLevel::CloseParenthesis)
            .report_error(self);
            Some(step)
        };

        let body = self.parse_branch_body();
        let span = AstSpan::range(for_keyword_position, body.end_anchor_token_position);
        self.arena.alloc_node(
            Expression::For {
                initialization,
                condition,
                step,
                body,
            },
            span,
        )
    }

    /// Parses the continuation of a `return` expression after its keyword.
    ///
    /// If the next token is not `;`, consumes a return value expression.
    /// The terminating `;` is not consumed here.
    #[must_use]
    pub(crate) fn parse_return_tail(
        &mut self,
        return_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (value, span) = if self.peek_token() == Some(Token::Semicolon) {
            (None, AstSpan::new(return_keyword_position))
        } else {
            let returned_value = self.parse_expression();
            let span = AstSpan::range(return_keyword_position, returned_value.span().token_to);
            (Some(returned_value), span)
        };
        self.arena.alloc_node(Expression::Return(value), span)
    }

    /// Parses the continuation of a `break` expression after its keyword.
    ///
    /// If the next token is not `;`, consumes a break value expression.
    /// The terminating `;` is not consumed here.
    #[must_use]
    pub(crate) fn parse_break_tail(
        &mut self,
        break_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (value, span) = if self.peek_token() == Some(Token::Semicolon) {
            (None, AstSpan::new(break_keyword_position))
        } else {
            let returned_value = self.parse_expression();
            let span = AstSpan::range(break_keyword_position, returned_value.span().token_to);
            (Some(returned_value), span)
        };
        self.arena.alloc_node(Expression::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(crate) fn parse_goto_tail(
        &mut self,
        goto_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        if let Some((label_token, label_position)) = self.peek_token_and_position()
            && (label_token == Token::NameLiteral || label_token == Token::Identifier)
        {
            self.advance();
            return self.arena.alloc_node_between(
                Expression::Goto(label_position),
                goto_keyword_position,
                label_position,
            );
        }
        self.make_error_here(ParseErrorKind::GotoMissingLabel)
            .widen_error_span_from(goto_keyword_position)
            .sync_error_until(self, SyncLevel::Statement)
            .report_error(self);
        crate::arena::ArenaNode::new_in(
            Expression::Error,
            AstSpan::new(goto_keyword_position),
            self.arena,
        )
    }
}
