//! Parser for `for` loop expressions in Fermented UnrealScript.
//!
//! ## Disambiguation of `for` as loop vs expression
//!
//! Unlike other control-flow keywords, `for` is disambiguated from functions
//! and variables with the same name. This is done syntactically in
//! [`Parser::peek_for_loop_header_left_parenthesis_position`]: 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`
//!
//! There is 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
//! loop 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).

use crate::ast::{Expression, ExpressionRef, OptionalExpression};
use crate::lexer::{self, Token, TokenPosition};
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};

#[derive(Debug)]
struct ParsedForHeader<'src, 'arena> {
    initializer: OptionalExpression<'src, 'arena>,
    condition: OptionalExpression<'src, 'arena>,
    step: OptionalExpression<'src, 'arena>,
    right_parenthesis_position: Option<TokenPosition>,
}

impl<'src, 'arena> ParsedForHeader<'src, 'arena> {
    #[must_use]
    fn new() -> Self {
        Self {
            initializer: None,
            condition: None,
            step: None,
            right_parenthesis_position: None,
        }
    }
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Returns the position of the next `(` when it can start a `for (...)`
    /// header.
    ///
    /// Recognizes a header by a top-level `;` before the matching `)`.
    /// Incomplete headers are accepted so later parsing can produce
    /// `for`-specific diagnostics.
    ///
    /// Performs a lookahead-only check used for loop-vs-identifier
    /// disambiguation.
    pub(in super::super) fn peek_for_loop_header_left_parenthesis_position(
        &mut self,
    ) -> Option<TokenPosition> {
        let Some((Token::LeftParenthesis, left_parenthesis_position)) =
            self.peek_token_and_position()
        else {
            return None;
        };
        let mut nesting_depth: usize = 1;
        let mut lookahead_offset: usize = 1;
        while let Some(next_token) = self.peek_token_at(lookahead_offset) {
            match next_token {
                Token::LeftParenthesis => nesting_depth += 1,
                Token::RightParenthesis => {
                    if nesting_depth <= 1 {
                        // A closed immediate group without a top-level `;`
                        // is not a loop header.
                        return None;
                    }
                    nesting_depth -= 1;
                }
                Token::Semicolon if nesting_depth == 1 => return Some(left_parenthesis_position),
                _ => (),
            }
            lookahead_offset += 1;
        }
        // Prefer treating incomplete `for (` as a loop header so recovery
        // reports header diagnostics instead of call-like diagnostics.
        Some(left_parenthesis_position)
    }

    /// Parses a `for` expression after `for` and the opening `(` have been
    /// consumed.
    ///
    /// Header components may be omitted. Returns [`Expression::Error`] if the
    /// header cannot be closed; otherwise returns [`Expression::For`] spanning
    /// from `for_keyword_position` through the parsed body.
    #[must_use]
    pub(in super::super) fn parse_for_tail(
        &mut self,
        for_keyword_position: TokenPosition,
        left_parenthesis_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let header = self.parse_for_header(for_keyword_position, left_parenthesis_position);
        if header.right_parenthesis_position.is_none() {
            return self.arena.alloc_node(
                Expression::Error,
                lexer::TokenSpan::range(
                    for_keyword_position,
                    self.last_consumed_position_or_start(),
                ),
            );
        }
        let body = self.parse_branch_body(for_keyword_position);
        let span = lexer::TokenSpan::range(for_keyword_position, body.end_anchor_token_position);
        self.arena.alloc_node(
            Expression::For {
                initializer: header.initializer,
                condition: header.condition,
                step: header.step,
                body,
            },
            span,
        )
    }

    /// Parses one optional `for` header component before `terminator_token`.
    ///
    /// Uses `component_start_anchor_position` to anchor invalid-start
    /// diagnostics at the position where an expression was expected.
    fn parse_optional_for_header_expression(
        &mut self,
        invalid_start_error_kind: ParseErrorKind,
        terminator_token: Token,
        for_keyword_position: TokenPosition,
        component_start_anchor_position: TokenPosition,
    ) -> OptionalExpression<'src, 'arena> {
        if let Some(next_token) = self.peek_token()
            && next_token != terminator_token
        {
            Some(
                self.parse_required_expression_with_context(
                    invalid_start_error_kind,
                    for_keyword_position,
                    component_start_anchor_position,
                )
                // Header recovery must not consume the next `;`;
                // it belongs to the surrounding `for` header.
                // That's why `SyncLevel` match is preferable to syncing onto
                // matching delimiter - latter wouldn't stop at preceding `;`.
                .sync_error_until(self, SyncLevel::CloseParenthesis)
                .unwrap_or_fallback(self),
            )
        } else {
            None
        }
    }

    fn optional_expression_is_error(expression: &OptionalExpression<'src, 'arena>) -> bool {
        expression
            .as_ref()
            .is_some_and(|expression| matches!(**expression, Expression::Error))
    }

    /// Consumes the next header semicolon or recovers at `)`.
    ///
    /// Suppresses the missing-semicolon diagnostic when the component
    /// expression has already failed.
    fn consume_for_header_semicolon_or_recover(
        &mut self,
        for_keyword_position: TokenPosition,
        right_parenthesis_position: &mut Option<TokenPosition>,
        component_expression: &OptionalExpression<'src, 'arena>,
        missing_semicolon_error_kind: ParseErrorKind,
        component_diagnostic_label: &'static str,
    ) -> bool {
        let diagnostic_position = match self.peek_token_and_position() {
            Some((Token::Semicolon, _)) => {
                self.advance();
                return true;
            }
            Some((Token::RightParenthesis, position)) => {
                *right_parenthesis_position = Some(position);
                self.advance();
                position
            }
            Some((_, position)) => position,
            None => self.peek_position_or_eof(),
        };
        // Do not add a missing-semicolon error on top of a component
        // parse error.
        if Self::optional_expression_is_error(component_expression) {
            return false;
        }
        let mut error = self
            .make_error_at(missing_semicolon_error_kind, diagnostic_position)
            .widen_error_span_from(for_keyword_position)
            .blame_token(diagnostic_position);
        if let Some(expression) = component_expression {
            error = error.related(component_diagnostic_label, *expression.span());
        }
        error.report_error(self);
        false
    }

    /// Finishes recovery after an invalid step expression.
    ///
    /// Consumes a following `)` when present and suppresses the missing-`)`
    /// check to avoid a cascading header diagnostic. Returns `true` when header
    /// parsing should stop.
    fn recover_after_invalid_step_expression(
        &mut self,
        header: &mut ParsedForHeader<'src, 'arena>,
    ) -> bool {
        if !Self::optional_expression_is_error(&header.step) {
            return false;
        }
        if let Some((Token::RightParenthesis, right_parenthesis_position)) =
            self.peek_token_and_position()
        {
            header.right_parenthesis_position = Some(right_parenthesis_position);
            self.advance();
        }
        true
    }

    /// Consumes the closing `)` of a `for` header or reports
    /// a header-level error.
    ///
    /// Links the diagnostic back to the opening `(` so incomplete headers point
    /// to the whole header region.
    fn consume_for_header_closing_parenthesis(
        &mut self,
        for_keyword_position: TokenPosition,
        left_parenthesis_position: TokenPosition,
        header: &mut ParsedForHeader<'src, 'arena>,
    ) {
        header.right_parenthesis_position = self
            .expect(
                Token::RightParenthesis,
                ParseErrorKind::ForLoopHeaderMissingClosingParenthesis,
            )
            .widen_error_span_from(for_keyword_position)
            .related_token("for_header_start", left_parenthesis_position)
            .sync_error_at_matching_delimiter(self, left_parenthesis_position)
            .ok_or_report(self);
    }

    /// Parses the initializer, condition, and step expressions of a `for`
    /// header.
    ///
    /// Stops after the first unrecovered separator error so later recovery
    /// does not produce duplicate header diagnostics.
    fn parse_for_header(
        &mut self,
        for_keyword_position: TokenPosition,
        left_parenthesis_position: TokenPosition,
    ) -> ParsedForHeader<'src, 'arena> {
        let mut header = ParsedForHeader::new();
        header.initializer = self.parse_optional_for_header_expression(
            ParseErrorKind::ForLoopHeaderInitializerInvalidStart,
            Token::Semicolon,
            for_keyword_position,
            left_parenthesis_position,
        );
        if !self.consume_for_header_semicolon_or_recover(
            for_keyword_position,
            &mut header.right_parenthesis_position,
            &header.initializer,
            ParseErrorKind::ForLoopHeaderMissingSemicolonAfterInitializer,
            "for_header_initializer",
        ) {
            return header;
        }
        let initializer_semicolon_position = self.last_consumed_position_or_start();
        header.condition = self.parse_optional_for_header_expression(
            ParseErrorKind::ForLoopHeaderConditionInvalidStart,
            Token::Semicolon,
            for_keyword_position,
            initializer_semicolon_position,
        );
        if !self.consume_for_header_semicolon_or_recover(
            for_keyword_position,
            &mut header.right_parenthesis_position,
            &header.condition,
            ParseErrorKind::ForLoopHeaderMissingSemicolonAfterCondition,
            "for_header_condition",
        ) {
            return header;
        }
        let condition_semicolon_position = self.last_consumed_position_or_start();
        header.step = self.parse_optional_for_header_expression(
            ParseErrorKind::ForLoopHeaderStepInvalidStart,
            Token::RightParenthesis,
            for_keyword_position,
            condition_semicolon_position,
        );
        if self.recover_after_invalid_step_expression(&mut header) {
            return header;
        }
        self.consume_for_header_closing_parenthesis(
            for_keyword_position,
            left_parenthesis_position,
            &mut header,
        );
        header
    }
}
