//! Parsing of comma-separated variable declarator lists for
//! Fermented UnrealScript.
//!
//! The Fermented dialect extends original UnrealScript with array-size
//! expressions and declarator initializers.

#![allow(clippy::option_if_let_else)]

use std::ops::ControlFlow;

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

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

#[derive(Debug)]
struct VariableDeclaratorListParseState<'src, 'arena> {
    declarators: ast::DeclaratorsList<'src, 'arena>,
    declaration_keyword_position: TokenPosition,
    variable_type_span: TokenSpan,
    pending_separator_position: Option<TokenPosition>,
    previous_declarator_span: Option<TokenSpan>,
    parse_step: VariableDeclaratorListParseStep,
}

impl<'src, 'arena> VariableDeclaratorListParseState<'src, 'arena> {
    #[must_use]
    fn new(
        parser: &Parser<'src, 'arena>,
        declaration_keyword_position: TokenPosition,
        variable_type_span: TokenSpan,
    ) -> Self {
        Self {
            declarators: parser.arena.vec(),
            declaration_keyword_position,
            variable_type_span,
            pending_separator_position: None,
            previous_declarator_span: None,
            parse_step: VariableDeclaratorListParseStep::ExpectingDeclarator,
        }
    }

    fn set_declarator_expected(&mut self, separator_position: TokenPosition) {
        self.pending_separator_position = Some(separator_position);
        self.parse_step = VariableDeclaratorListParseStep::ExpectingDeclarator;
    }

    fn set_separator_expected(&mut self) {
        self.pending_separator_position = None;
        self.parse_step = VariableDeclaratorListParseStep::ExpectingSeparator;
    }

    fn push_declarator(&mut self, declarator: VariableDeclaratorRef<'src, 'arena>) {
        self.previous_declarator_span = Some(*declarator.span());
        self.declarators.push(declarator);
        self.set_separator_expected();
    }

    fn missing_declarator_error_kind(&self) -> ParseErrorKind {
        if self.declarators.is_empty() {
            ParseErrorKind::VariableDeclaratorListEmpty
        } else {
            ParseErrorKind::VariableDeclaratorExpected
        }
    }
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a comma-separated variable declarator list after a declaration
    /// keyword and type.
    ///
    /// Accepts optional array-size expressions and `=` initializers.
    ///
    /// Stops before `;` or before a token that cannot continue the list. The
    /// semicolon is left for the caller to consume or diagnose as the enclosing
    /// statement terminator.
    #[must_use]
    pub(crate) fn parse_variable_declarators(
        &mut self,
        declaration_keyword_position: TokenPosition,
        variable_type_span: TokenSpan,
    ) -> ast::DeclaratorsList<'src, 'arena> {
        use VariableDeclaratorListParseStep::{ExpectingDeclarator, ExpectingSeparator};

        let mut state = VariableDeclaratorListParseState::new(
            self,
            declaration_keyword_position,
            variable_type_span,
        );
        // Progress invariant: every branch that continues the loop consumes at
        // least one token. Branches that only report an error must stop
        // the loop, so the parser remains finite even if
        // `ensure_forward_progress` is only a debug/safety check.
        while let Some((next_token, next_token_position)) = self.peek_token_and_position() {
            match (state.parse_step, next_token) {
                (ExpectingDeclarator, Token::Semicolon) => {
                    self.report_missing_variable_declarator_at_current_token(&state);
                    return state.declarators;
                }
                (ExpectingDeclarator, Token::Comma) => {
                    if self
                        .recover_empty_variable_declarator_run(next_token_position, &state)
                        .is_break()
                    {
                        return state.declarators;
                    }
                }
                (ExpectingDeclarator, _) if next_token.is_valid_identifier_name() => {
                    if self
                        .parse_and_push_variable_declarator(&mut state)
                        .is_break()
                    {
                        break;
                    }
                }
                // A token that cannot start a declarator ends the list.
                // Report whether the whole list is missing or only the current
                // declarator slot is missing, then leave the enclosing
                // terminator for the caller to report.
                (ExpectingDeclarator, _) => {
                    self.report_missing_variable_declarator_at_current_token(&state);
                    break;
                }
                (ExpectingSeparator, Token::Comma) => {
                    self.advance(); // ','
                    state.set_declarator_expected(next_token_position);
                }
                (ExpectingSeparator, Token::Semicolon) => break,
                // Missing-separator recovery is intentionally stricter than
                // normal declarator parsing, so identifier-like keywords are
                // not treated as continuations (no `is_valid_identifier_name`).
                (ExpectingSeparator, Token::Identifier) => {
                    if self
                        .recover_missing_separator_before_variable_declarator(
                            next_token_position,
                            &mut state,
                        )
                        .is_break()
                    {
                        break;
                    }
                }
                // Any other invalid cases in place of separator should be
                // handled as errors by the caller to avoid error cascade.
                (ExpectingSeparator, _) => break,
            }
            // Guard against parser bugs that would otherwise leave
            // block parsing stuck on the same token.
            self.ensure_forward_progress(next_token_position);
        }
        // At end-of-file, the caller is expected to report the more relevant
        // enclosing error, so this parser does not add another diagnostic.
        state.declarators
    }

    /// Reports a missing variable declarator and attaches declaration context.
    fn report_missing_variable_declarator_at_current_token(
        &mut self,
        state: &VariableDeclaratorListParseState<'src, 'arena>,
    ) {
        let error_position = self.peek_position_or_eof();
        self.make_error_at(state.missing_declarator_error_kind(), error_position)
            .related_token("declaration_keyword", state.declaration_keyword_position)
            .related("variable_type", state.variable_type_span)
            .maybe_related_token("separator", state.pending_separator_position)
            .report(self);
    }

    /// Recovers from one or more empty declarator slots produced by commas.
    ///
    /// Called when the current token is `,`. Returns [`ControlFlow::Continue`]
    /// only when the next remaining token can start a declarator.
    fn recover_empty_variable_declarator_run(
        &mut self,
        error_start_position: TokenPosition,
        state: &VariableDeclaratorListParseState<'src, 'arena>,
    ) -> ControlFlow<()> {
        let missing_first_declarator =
            state.declarators.is_empty() && state.pending_separator_position.is_none();

        while let Some(Token::Comma) = self.peek_token() {
            self.advance();
        }

        let mut error = self
            .make_error_at_last_consumed(ParseErrorKind::VariableDeclaratorExpected)
            .widen_error_span_from(error_start_position)
            .extend_blame_start_to_covered_start()
            .related_token("declaration_keyword", state.declaration_keyword_position)
            .related("variable_type", state.variable_type_span)
            .maybe_related_token("separator", state.pending_separator_position)
            .with_flag("empty_declarator_run");
        if missing_first_declarator {
            error = error.with_flag("first_declarator");
        }
        error.report_error(self);
        if self
            .peek_token()
            .is_some_and(|token| token.is_valid_identifier_name())
        {
            ControlFlow::Continue(())
        } else {
            ControlFlow::Break(())
        }
    }

    /// Parses one variable declarator and appends it to `state`.
    ///
    /// Returns [`ControlFlow::Continue`] only after consuming at least the
    /// declarator name and advancing `state` to the separator phase. Returns
    /// [`ControlFlow::Break`] on parse failure so the caller does not retry at
    /// the same token.
    fn parse_and_push_variable_declarator(
        &mut self,
        state: &mut VariableDeclaratorListParseState<'src, 'arena>,
    ) -> ControlFlow<()> {
        if let Some(parsed_declarator) = self
            .parse_variable_declarator(state.declaration_keyword_position)
            .sync_error_until(self, SyncLevel::StatementStart)
            .ok_or_report(self)
        {
            state.push_declarator(parsed_declarator);
            ControlFlow::Continue(())
        } else {
            // Keep the fallback so changes in declarator recovery do not turn
            // this helper into an unconditional continuation.
            ControlFlow::Break(())
        }
    }

    /// Recovers from a declarator that starts without a preceding comma.
    ///
    /// Returns [`ControlFlow::Continue`] only after consuming the declarator
    /// and reporting a missing-separator diagnostic.
    /// Returns [`ControlFlow::Break`] on parse failure so the caller does not
    /// retry at the same token.
    fn recover_missing_separator_before_variable_declarator(
        &mut self,
        error_start_position: TokenPosition,
        state: &mut VariableDeclaratorListParseState<'src, 'arena>,
    ) -> ControlFlow<()> {
        if let Some(parsed_declarator) = self
            .parse_variable_declarator(state.declaration_keyword_position)
            .widen_error_span_from(error_start_position)
            .sync_error_until(self, SyncLevel::StatementStart)
            .ok_or_report(self)
        {
            self.make_error_at_last_consumed(
                ParseErrorKind::VariableDeclaratorListMissingSeparator,
            )
            .widen_error_span_from(error_start_position)
            .related_token("declaration_keyword", state.declaration_keyword_position)
            .maybe_related("last_declarator", state.previous_declarator_span)
            .report_error(self);
            state.push_declarator(parsed_declarator);
            ControlFlow::Continue(())
        } else {
            // Keep the fallback so changes in declarator recovery do not turn
            // this helper into an unconditional continuation.
            ControlFlow::Break(())
        }
    }

    /// Parses a single variable declarator.
    fn parse_variable_declarator(
        &mut self,
        declaration_keyword_position: TokenPosition,
    ) -> ParseResult<'src, 'arena, VariableDeclaratorRef<'src, 'arena>> {
        let variable_name = self.parse_identifier(ParseErrorKind::VariableDeclaratorExpected)?;
        let array_size =
            self.parse_optional_array_size(declaration_keyword_position, variable_name.0);
        let initializer = self.parse_optional_variable_initializer(variable_name.0);
        let declarator_span =
            TokenSpan::range(variable_name.0, self.last_consumed_position_or_start());
        Ok(self.arena.alloc_node(
            VariableDeclarator {
                name: variable_name,
                initializer,
                array_size,
            },
            declarator_span,
        ))
    }

    /// Parses an optional array-size expression.
    ///
    /// Returns [`None`] when no array size is present or when the size
    /// expression cannot be recovered.
    fn parse_optional_array_size(
        &mut self,
        declaration_keyword_position: TokenPosition,
        variable_name_position: TokenPosition,
    ) -> OptionalExpression<'src, 'arena> {
        if !self.eat(Token::LeftBracket) {
            return None;
        }
        let left_bracket_position = self.last_consumed_position_or_start();
        let array_size_expression = self
            .parse_required_expression_with_context(
                ParseErrorKind::VariableDeclaratorArraySizeExpected,
                variable_name_position,
                left_bracket_position,
            )
            .sync_error_until(self, SyncLevel::CloseBracket)
            .ok_or_report(self);
        self.expect(
            Token::RightBracket,
            ParseErrorKind::VariableDeclaratorArraySizeMissingClosingBracket,
        )
        .related_token("declaration_keyword", declaration_keyword_position)
        .related_token("open_bracket", left_bracket_position)
        .sync_error_at_matching_delimiter(self, left_bracket_position)
        .report_error(self);
        array_size_expression
    }

    /// Parses an optional initializer.
    ///
    /// Returns [`None`] both when the initializer is absent and when its
    /// expression cannot be recovered. This makes the recovered AST ambiguous,
    /// but that is acceptable because callers should treat it only as a
    /// best-effort result after a reported syntax error.
    fn parse_optional_variable_initializer(
        &mut self,
        variable_name_position: TokenPosition,
    ) -> OptionalExpression<'src, 'arena> {
        if self.eat(Token::Assign) {
            let assignment_operator_position = self.last_consumed_position_or_start();
            self.parse_required_expression_with_context(
                ParseErrorKind::VariableDeclaratorInitializerExpected,
                variable_name_position,
                assignment_operator_position,
            )
            .sync_error_until(self, SyncLevel::ListSeparator)
            .ok_or_report(self)
        } else {
            None
        }
    }
}
