//! Parsing of declaration specifiers used in `var(...) ...` syntax for
//! Fermented UnrealScript.

use std::ops::ControlFlow;

use crate::arena::ArenaVec;
use crate::ast::{VarEditorSpecifier, VarEditorSpecifierRef, VarModifier};
use crate::lexer::{Token, TokenPosition, TokenSpan};
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};

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

struct VarEditorSpecifierListParseState<'arena, SpecifierRef> {
    editor_specifiers: ArenaVec<'arena, SpecifierRef>,
    open_parenthesis_position: TokenPosition,
    pending_separator_position: Option<TokenPosition>,
    previous_specifier_span: Option<TokenSpan>,
    parse_step: VarEditorSpecifierListParseStep,
}

impl<'arena, SpecifierRef> VarEditorSpecifierListParseState<'arena, SpecifierRef> {
    #[must_use]
    fn new<'src>(parser: &Parser<'src, 'arena>, open_parenthesis_position: TokenPosition) -> Self {
        Self {
            editor_specifiers: parser.arena.vec(),
            open_parenthesis_position,
            pending_separator_position: None,
            previous_specifier_span: None,
            parse_step: VarEditorSpecifierListParseStep::ExpectingSpecifier,
        }
    }

    fn set_specifier_expected(&mut self, separator_position: TokenPosition) {
        self.pending_separator_position = Some(separator_position);
        self.parse_step = VarEditorSpecifierListParseStep::ExpectingSpecifier;
    }

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

    fn push_specifier(&mut self, specifier: SpecifierRef, specifier_span: TokenSpan) {
        self.previous_specifier_span = Some(specifier_span);
        self.editor_specifiers.push(specifier);
        self.set_separator_expected();
    }
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a consecutive run of variable declaration modifiers.
    ///
    /// This is used for declarations such as
    /// `var transient config editconst int X;`.
    ///
    /// Parsing stops when the next token is not a recognized [`VarModifier`].
    /// That token is left unconsumed for the caller.
    ///
    /// Returns the parsed modifiers in source order, or an empty vector if the
    /// current token does not begin a modifier list.
    #[must_use]
    pub(crate) fn parse_var_declaration_modifiers(&mut self) -> ArenaVec<'arena, VarModifier> {
        let mut modifiers = self.arena.vec();
        while let Some(current_token_and_position) = self.peek_token_and_position()
            && let Ok(parsed_modifier) = VarModifier::try_from(current_token_and_position)
        {
            self.advance();
            modifiers.push(parsed_modifier);
        }
        modifiers
    }

    // TODO: should this have "optional" in the name?
    /// Parses the optional parenthesized editor specifier list in `var(...)`.
    ///
    /// Assumes that `var` has already been consumed.
    ///
    /// Returns `None` if the current token is not `(`. Returns `Some(...)` once
    /// `(` is present, including for an empty list.
    #[must_use]
    pub(crate) fn parse_var_editor_specifier_list(
        &mut self,
    ) -> Option<ArenaVec<'arena, VarEditorSpecifierRef<'src, 'arena>>> {
        use VarEditorSpecifierListParseStep::{ExpectingSeparator, ExpectingSpecifier};

        let Some(left_parenthesis_position) = self.eat_with_position(Token::LeftParenthesis) else {
            return None;
        };
        let mut state = VarEditorSpecifierListParseState::new(self, left_parenthesis_position);
        // Progress invariant: every branch that continues the loop consumes at
        // least one token. Branches that only report an error must stop the
        // loop or synchronize before continuing.
        while let Some((next_token, next_token_position)) = self.peek_token_and_position() {
            match (state.parse_step, next_token) {
                (ExpectingSpecifier, Token::RightParenthesis) => {
                    if state.pending_separator_position.is_some() {
                        self.report_missing_var_editor_specifier_at_current_token(&state);
                    }
                    break;
                }
                (ExpectingSpecifier, Token::Comma) => {
                    if self
                        .recover_empty_var_editor_specifier_run(next_token_position, &mut state)
                        .is_break()
                    {
                        break;
                    }
                }
                (ExpectingSpecifier, _) => {
                    if let Some(parsed_specifier) = self.try_parse_current_var_editor_specifier() {
                        let parsed_specifier_span = *parsed_specifier.span();
                        state.push_specifier(parsed_specifier, parsed_specifier_span);
                    } else {
                        self.report_missing_var_editor_specifier_at_current_token(&state);
                        break;
                    }
                }
                (ExpectingSeparator, Token::Comma) => {
                    self.advance(); // ','
                    state.set_specifier_expected(next_token_position);
                }
                (ExpectingSeparator, Token::RightParenthesis) => break,
                (ExpectingSeparator, _) => {
                    if let Some(parsed_specifier) = self.try_parse_current_var_editor_specifier() {
                        let parsed_specifier_span = *parsed_specifier.span();
                        self.make_error_at_last_consumed(
                            ParseErrorKind::VarEditorSpecifierListMissingSeparator,
                        )
                        .widen_error_span_from(next_token_position)
                        .related_token("open_parenthesis", state.open_parenthesis_position)
                        .maybe_related("previous_specifier", state.previous_specifier_span)
                        .report_error(self);

                        state.push_specifier(parsed_specifier, parsed_specifier_span);
                    } else {
                        self.report_unexpected_token_after_var_editor_specifier(&state);
                        break;
                    }
                }
            }
            // Guard against parser bugs that would otherwise leave declaration
            // parsing stuck on the same token.
            self.ensure_forward_progress(next_token_position);
        }
        self.expect(
            Token::RightParenthesis,
            ParseErrorKind::VarEditorSpecifierListMissingClosingParenthesis,
        )
        .related_token("open_parenthesis", state.open_parenthesis_position)
        .maybe_related("previous_specifier", state.previous_specifier_span)
        .sync_error_at_matching_delimiter(self, left_parenthesis_position)
        .report_error(self);

        Some(state.editor_specifiers)
    }

    /// Tries to parse the current token as one `var(...)` editor specifier.
    ///
    /// This is the only recognizer for editor specifier starts. It returns
    /// [`None`] without consuming anything when the current token cannot start a
    /// specifier.
    fn try_parse_current_var_editor_specifier(
        &mut self,
    ) -> Option<VarEditorSpecifierRef<'src, 'arena>> {
        let (next_token, next_token_lexeme, next_token_position) =
            self.peek_token_lexeme_and_position()?;
        if next_token == Token::StringLiteral {
            let string_value = self.unescape_string_literal(next_token_lexeme);
            self.advance();
            return Some(self.arena.alloc_node_at(
                VarEditorSpecifier::String(string_value),
                next_token_position,
            ));
        }
        let specifier_identifier =
            Self::identifier_token_from_token(next_token, next_token_position)?;
        self.advance();
        Some(self.arena.alloc_node_at(
            VarEditorSpecifier::Identifier(specifier_identifier),
            next_token_position,
        ))
    }

    /// Reports a missing editor specifier at the current token.
    fn report_missing_var_editor_specifier_at_current_token(
        &mut self,
        state: &VarEditorSpecifierListParseState<'arena, VarEditorSpecifierRef<'src, 'arena>>,
    ) {
        let position = self.peek_position_or_eof();
        let missing_first_specifier =
            state.editor_specifiers.is_empty() && state.pending_separator_position.is_none();
        let mut error = self
            .make_error_at(ParseErrorKind::VarEditorSpecifierExpected, position)
            .related_token("open_parenthesis", state.open_parenthesis_position)
            .maybe_related_token("separator", state.pending_separator_position)
            .maybe_related("previous_specifier", state.previous_specifier_span);
        if missing_first_specifier {
            error = error.with_flag("first_specifier");
        }
        error.report(self);
    }

    /// Recovers from one or more empty editor specifier slots produced by
    /// commas.
    ///
    /// Called when the current token is `,`. Returns [`ControlFlow::Continue`]
    /// only when either the list can continue or a following specifier was
    /// consumed as part of recovery.
    fn recover_empty_var_editor_specifier_run(
        &mut self,
        error_start_position: TokenPosition,
        state: &mut VarEditorSpecifierListParseState<'arena, VarEditorSpecifierRef<'src, 'arena>>,
    ) -> ControlFlow<()> {
        let missing_first_specifier =
            state.editor_specifiers.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::VarEditorSpecifierExpected)
            .widen_error_span_from(error_start_position)
            .extend_blame_start_to_covered_start()
            .related_token("open_parenthesis", state.open_parenthesis_position)
            .maybe_related_token("separator", state.pending_separator_position)
            .maybe_related("previous_specifier", state.previous_specifier_span)
            .with_flag("empty_specifier_run");
        if missing_first_specifier {
            error = error.with_flag("first_specifier");
        }
        error.report_error(self);
        if self.peek_token() == Some(Token::RightParenthesis) {
            return ControlFlow::Break(());
        }
        if let Some(parsed_specifier) = self.try_parse_current_var_editor_specifier() {
            let parsed_specifier_span = *parsed_specifier.span();
            state.push_specifier(parsed_specifier, parsed_specifier_span);
            ControlFlow::Continue(())
        } else {
            ControlFlow::Break(())
        }
    }

    /// Reports a token that appears after an editor specifier where only `,`
    /// or `)` can continue the list.
    fn report_unexpected_token_after_var_editor_specifier(
        &mut self,
        state: &VarEditorSpecifierListParseState<'arena, VarEditorSpecifierRef<'src, 'arena>>,
    ) {
        let position = self.peek_position_or_eof();

        self.make_error_at(
            ParseErrorKind::VarEditorSpecifierListExpectedCommaOrClosingParenthesis,
            position,
        )
        .related_token("open_parenthesis", state.open_parenthesis_position)
        .maybe_related("previous_specifier", state.previous_specifier_span)
        .sync_error_until(self, SyncLevel::CloseParenthesis)
        .report_error(self);
    }
}
