//! Parsing of comma-separated variable declarator lists for
//! Fermented `UnrealScript`.
//!
//! Extends original `UnrealScript` by allowing array-size expressions and
//! declarator initializers.

#![allow(clippy::option_if_let_else)]

use std::ops::ControlFlow;

use crate::arena::ArenaVec;
use crate::ast::{AstSpan, OptionalExpression, VariableDeclarator, VariableDeclaratorRef};
use crate::lexer::{Token, TokenPosition};
use crate::parser::{ParseErrorKind, ParseResult, Parser, ResultRecoveryExt, SyncLevel};

#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
enum VariableDeclaratorParseState {
    ExpectingDeclarator,
    ExpectingSeparator,
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a comma-separated list of variable declarators.
    ///
    /// Accepts optional array-size expressions and `=` initializers.
    #[must_use]
    pub(crate) fn parse_variable_declarators(
        &mut self,
    ) -> ArenaVec<'arena, VariableDeclaratorRef<'src, 'arena>> {
        use VariableDeclaratorParseState::{ExpectingDeclarator, ExpectingSeparator};

        let mut declarators = self.arena.vec();
        let mut parser_state = ExpectingDeclarator;
        while let Some((next_token, next_token_position)) = self.peek_token_and_position() {
            match (parser_state, next_token) {
                (ExpectingDeclarator, Token::Semicolon) => {
                    self.report_error_here(ParseErrorKind::DeclEmptyVariableDeclarations);
                    return declarators;
                }
                (ExpectingDeclarator, Token::Comma) => {
                    if self
                        .recover_empty_variable_declarator(next_token_position)
                        .is_break()
                    {
                        return declarators;
                    }
                }
                (ExpectingDeclarator, _) => {
                    if self
                        .parse_variable_declarator_into(&mut declarators)
                        .is_break()
                    {
                        // Breaking means we've failed to parse declarator
                        self.report_error_here(ParseErrorKind::DeclEmptyVariableDeclarations);
                        break;
                    }
                    parser_state = ExpectingSeparator;
                }
                (ExpectingSeparator, Token::Comma) => {
                    self.advance();
                    parser_state = ExpectingDeclarator;
                }
                (ExpectingSeparator, Token::Semicolon) => break,
                (ExpectingSeparator, _) => {
                    if self
                        .recover_missing_variable_declarator_separator(
                            next_token_position,
                            &mut declarators,
                        )
                        .is_break()
                    {
                        break;
                    }
                }
            }
            self.ensure_forward_progress(next_token_position);
        }
        // In case of reaching EOF here, it does not matter if we emit
        // an additional diagnostic.
        // The caller is expected to report the more relevant enclosing error.
        declarators
    }

    fn recover_empty_variable_declarator(
        &mut self,
        error_start_position: TokenPosition,
    ) -> ControlFlow<()> {
        while self.peek_token() == Some(Token::Comma) {
            self.advance();
        }
        self.make_error_here(ParseErrorKind::DeclEmptyVariableDeclarations)
            .widen_error_span_from(error_start_position)
            .report_error(self);
        if matches!(self.peek_token(), Some(Token::Semicolon) | None) {
            ControlFlow::Break(())
        } else {
            ControlFlow::Continue(())
        }
    }

    fn parse_variable_declarator_into(
        &mut self,
        declarators: &mut ArenaVec<'arena, VariableDeclaratorRef<'src, 'arena>>,
    ) -> ControlFlow<()> {
        if let Some(parsed_declarator) = self
            .parse_variable_declarator()
            .sync_error_until(self, SyncLevel::Statement)
            .ok_or_report(self)
        {
            declarators.push(parsed_declarator);
            ControlFlow::Continue(())
        } else {
            ControlFlow::Break(())
        }
    }

    fn recover_missing_variable_declarator_separator(
        &mut self,
        error_start_position: TokenPosition,
        declarators: &mut ArenaVec<'arena, VariableDeclaratorRef<'src, 'arena>>,
    ) -> ControlFlow<()> {
        if let Some(parsed_declarator) = self
            .parse_variable_declarator()
            .widen_error_span_from(error_start_position)
            .sync_error_until(self, SyncLevel::Statement)
            .ok_or_report(self)
        {
            self.make_error_here(ParseErrorKind::DeclNoSeparatorBetweenVariableDeclarations)
                .widen_error_span_from(error_start_position)
                .report_error(self);
            declarators.push(parsed_declarator);
            ControlFlow::Continue(())
        } else {
            ControlFlow::Break(())
        }
    }

    fn parse_variable_declarator(
        &mut self,
    ) -> ParseResult<'src, 'arena, VariableDeclaratorRef<'src, 'arena>> {
        let name = self.parse_identifier(ParseErrorKind::DeclBadVariableIdentifier)?;
        let array_size = self.parse_optional_array_size();
        let initializer = self.parse_optional_variable_initializer();
        let span = AstSpan::range(name.0, self.last_consumed_position_or_start());
        Ok(self.arena.alloc_node(
            VariableDeclarator {
                name,
                initializer,
                array_size,
            },
            span,
        ))
    }

    fn parse_optional_array_size(&mut self) -> OptionalExpression<'src, 'arena> {
        if !self.eat(Token::LeftBracket) {
            return None;
        }
        let array_size_expression = self.parse_expression();
        self.expect(
            Token::RightBracket,
            ParseErrorKind::DeclExpectedRightBracketAfterArraySize,
        )
        .sync_error_at(self, SyncLevel::CloseBracket)
        .report_error(self);
        Some(array_size_expression)
    }

    fn parse_optional_variable_initializer(&mut self) -> OptionalExpression<'src, 'arena> {
        self.eat(Token::Assign).then(|| self.parse_expression())
    }
}
