//! 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::arena::ArenaVec;
use crate::ast::{AstSpan, BlockBody, Expression, ExpressionRef, Statement, StatementRef};
use crate::lexer::{Token, TokenPosition};
use crate::parser::{ParseErrorKind, Parser};

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a `{ ... }` block after the opening `{` has been consumed.
    ///
    /// Consumes tokens until the matching `}` and returns an
    /// [`Expression::Block`] whose span covers the entire block, from
    /// `opening_brace_position` to the closing `}`.
    ///
    /// On premature end-of-file, returns a best-effort block.
    #[must_use]
    pub(crate) fn parse_block_tail(
        &mut self,
        opening_brace_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let BlockBody { statements, span } =
            self.parse_braced_block_statements_tail(opening_brace_position);
        self.arena.alloc_node(Expression::Block(statements), span)
    }

    /// Parses a `{ ... }` block after the opening `{` has been consumed.
    ///
    /// Consumes tokens until the matching `}` and returns the contained
    /// statements together with a span that covers the entire block, from
    /// `opening_brace_position` to the closing `}`.
    ///
    /// On premature end-of-file, returns a best-effort statement list and span.
    #[must_use]
    pub(crate) fn parse_braced_block_statements_tail(
        &mut self,
        opening_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 = AstSpan::range(opening_brace_position, token_position);
                return BlockBody { statements, span };
            }
            self.parse_next_block_item_into(&mut statements);
            self.ensure_forward_progress(token_position);
        }
        // Reached EOF without a closing `}`
        self.report_error_here(ParseErrorKind::BlockMissingClosingBrace);
        let span = AstSpan::range(
            opening_brace_position,
            self.last_consumed_position_or_start(),
        );
        BlockBody { statements, span }
    }

    /// Parses one statement 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. It always appends at least one
    /// statement, even in the presence of syntax errors.
    pub(crate) fn parse_next_block_item_into(
        &mut self,
        statements: &mut ArenaVec<'arena, StatementRef<'src, 'arena>>,
    ) {
        let mut next_statement = self.parse_statement().unwrap_or_else(|| {
            let next_expression = self.parse_expression();
            let next_expression_span = *next_expression.span();
            self.arena
                .alloc_node(Statement::Expression(next_expression), next_expression_span)
        });
        if statement_needs_semicolon(&next_statement)
            && let Some((Token::Semicolon, semicolon_position)) = self.peek_token_and_position()
        {
            next_statement.span_mut().extend_to(semicolon_position);
            self.advance(); // ';'
        }
        statements.push(next_statement);
    }
}

fn statement_needs_semicolon(statement: &Statement) -> bool {
    use Statement::{Empty, Error, Expression, Function, Label, LocalVariableDeclaration};
    match statement {
        Empty | Label(_) | Error | Function(_) => false,
        Expression(expression) => expression_needs_semicolon(expression),
        LocalVariableDeclaration { .. } => true,
    }
}

const fn expression_needs_semicolon(expression: &Expression) -> bool {
    use Expression::{Block, DoUntil, Error, For, ForEach, If, Switch, While};
    matches!(
        expression,
        Block { .. }
            | If { .. }
            | While { .. }
            | DoUntil { .. }
            | ForEach { .. }
            | For { .. }
            | Switch { .. }
            | Error
    )
}
