//! Block-body parsing for Fermented UnrealScript.
//!
//! Provides shared routines for parsing `{ ... }`-delimited bodies used in
//! function, loop, state, and similar constructs after the opening `{`
//! has been consumed.

use crate::ast::{BlockBody, Expression, ExpressionRef, Statement, StatementList, StatementRef};
use crate::lexer::{Token, TokenPosition, TokenSpan};
use crate::parser::{ParseErrorKind, ParseResult, Parser, ResultRecoveryExt, SyncLevel};

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a braced block body into an [`Expression::Block`].
    ///
    /// The opening `{` must already have been consumed. The returned block's
    /// span covers the whole block, from `left_brace_position` through
    /// the closing `}`.
    ///
    /// On premature end-of-file, reports the missing `}` and returns
    /// a best-effort block.
    #[must_use]
    pub(crate) fn parse_block_body_tail(
        &mut self,
        left_brace_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let BlockBody { statements, span } =
            self.parse_braced_block_statements_tail(left_brace_position);
        self.arena.alloc_node(Expression::Block(statements), span)
    }

    /// Parses the statements in a braced block body.
    ///
    /// The opening `{` must already have been consumed. Returns the parsed
    /// statements and a span covering the whole block, from
    /// `left_brace_position` through the closing `}`.
    ///
    /// On premature end-of-file, reports the missing `}` and returns
    /// a best-effort body.
    #[must_use]
    pub(crate) fn parse_braced_block_statements_tail(
        &mut self,
        left_brace_position: TokenPosition,
    ) -> BlockBody<'src, 'arena> {
        let mut statements = self.arena.vec();
        while let Some((token, token_position)) = self.peek_token_and_position() {
            if token == Token::RightBrace {
                self.advance(); // '}'
                let span = TokenSpan::range(left_brace_position, token_position);
                return BlockBody { statements, span };
            }
            match self.parse_and_append_next_block_item(&mut statements) {
                Ok(()) => {
                    // Guard against parser bugs that would otherwise leave
                    // block parsing stuck on the same token.
                    self.ensure_forward_progress(token_position);
                }
                Err(error) => {
                    // Item-level recovery failed,
                    // so escalate to the block boundary.
                    let error = error.sync_error_at_matching_delimiter(self, left_brace_position);
                    let error_statement = error.fallback(self);
                    statements.push(error_statement);
                    let span = TokenSpan::range(
                        left_brace_position,
                        self.last_consumed_position_or_start(),
                    );
                    return BlockBody { statements, span };
                }
            }
        }
        let eof_position = self.peek_position_or_eof();
        self.make_error_at(ParseErrorKind::BlockMissingClosingBrace, eof_position)
            .related_token("left_brace", left_brace_position)
            .report(self);
        let span = TokenSpan::range(left_brace_position, self.last_consumed_position_or_start());

        BlockBody { statements, span }
    }

    /// Parses one statement-like item inside a `{ ... }` block and appends it
    /// to `statements`.
    ///
    /// This method never consumes the closing `}` and is only meant to be
    /// called while parsing inside a block.
    ///
    /// On success, it appends exactly one statement and advances past at least
    /// one token. If statement cannot be recovered locally, it returns
    /// an unreported error so the enclosing block parser can recover
    /// at block level.
    pub(crate) fn parse_and_append_next_block_item(
        &mut self,
        statements: &mut StatementList<'src, 'arena>,
    ) -> ParseResult<'src, 'arena, ()> {
        let mut statement = match self.parse_statement() {
            Some(statement) => statement,
            None => {
                // Non-statement starters are parsed as expression statements
                self.parse_expression_statement_in_block()?
            }
        };
        if block_item_requires_semicolon(&statement) {
            match self.peek_token_and_position() {
                Some((Token::Semicolon, semicolon_position)) => {
                    statement.span_mut().extend_to(semicolon_position);
                    self.advance(); // ';'
                }
                // A final expression before `}` may omit `;`; this makes it
                // the block's tail value.
                //
                // On end-of-file, suppress the missing-`;` diagnostic as well.
                // The block parser will report the missing `}`,
                // and an extra semicolon error would just cascade.
                None | Some((Token::RightBrace, _)) => (),
                Some((_, unexpected_token_position)) => {
                    self.make_error_at_last_consumed(
                        ParseErrorKind::BlockMissingSemicolonAfterExpression,
                    )
                    .widen_error_span_from(statement.span().start)
                    .sync_error_until(self, SyncLevel::StatementStart)
                    .blame_token(unexpected_token_position)
                    .related("expression_span", *statement.span())
                    .report(self);
                }
            }
        }
        statements.push(statement);
        Ok(())
    }

    /// Parses a non-statement starter as an expression statement inside
    /// a block.
    ///
    /// On success, returns an expression statement whose expression parser has
    /// consumed at least one token. If expression parsing fails and recovery
    /// cannot consume anything locally, returns the unreported error instead of
    /// producing a zero-width fallback statement.
    fn parse_expression_statement_in_block(
        &mut self,
    ) -> ParseResult<'src, 'arena, StatementRef<'src, 'arena>> {
        let expression_start_position = self.peek_position_or_eof();
        let expected_block_item_after_position = self.last_consumed_position_or_start();
        let expression_result = self.parse_required_expression(
            ParseErrorKind::BlockExpectedItem,
            expected_block_item_after_position,
        );
        let expression = match expression_result {
            Ok(expression) => expression,
            Err(error) => {
                let expression_recovery_made_no_progress =
                    self.peek_position_or_eof() == expression_start_position;
                // Without progress, a fallback statement would violate this
                // function's success contract and could leave the enclosing
                // block loop stuck.
                if expression_recovery_made_no_progress {
                    return self.recover_bad_block_item_start_as_error_statement(error);
                }
                error
                    .sync_error_until(self, SyncLevel::StatementStart)
                    .fallback(self)
            }
        };
        let expression_span = *expression.span();
        Ok(self
            .arena
            .alloc_node(Statement::Expression(expression), expression_span))
    }

    fn recover_bad_block_item_start_as_error_statement(
        &mut self,
        error: crate::parser::ParseError,
    ) -> ParseResult<'src, 'arena, StatementRef<'src, 'arena>> {
        let position_before_statement_recovery = self.peek_position_or_eof();
        // Recover one damaged block item if possible;
        // otherwise let the enclosing block recover at its own boundary.
        let error = error.sync_error_at(self, SyncLevel::StatementTerminator);
        if self.peek_position_or_eof() != position_before_statement_recovery {
            return Ok(error.fallback(self));
        }
        Err(error)
    }
}

fn block_item_requires_semicolon(statement: &Statement) -> bool {
    // Control-flow and block expressions do not require a trailing semicolon
    // when used as block items.
    if let Statement::Expression(expression) = statement {
        !matches!(
            **expression,
            Expression::Block { .. }
                | Expression::If { .. }
                | Expression::While { .. }
                | Expression::DoUntil { .. }
                | Expression::ForEach { .. }
                | Expression::For { .. }
                | Expression::Switch { .. }
                | Expression::Error
        )
    } else {
        false
    }
}
