//! Parsing of enum definitions for Fermented UnrealScript.

use std::ops::ControlFlow;

use crate::arena::ArenaVec;
use crate::ast::{EnumDefRef, EnumDefinition, IdentifierToken};
use crate::lexer::Token;
use crate::lexer::{TokenPosition, TokenSpan};
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};

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

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses an `enum` definition after the `enum` keyword has been consumed.
    ///
    /// Returns a reference to the allocated enum definition containing its name
    /// and variants.
    pub(crate) fn parse_enum_definition_tail(
        &mut self,
        enum_keyword_position: TokenPosition,
    ) -> EnumDefRef<'src, 'arena> {
        let name = self
            .parse_identifier(ParseErrorKind::EnumMissingName)
            .unwrap_or_fallback(self);
        self.expect(Token::LeftBrace, ParseErrorKind::EnumMissingOpeningBrace)
            .related_token("enum_keyword", enum_keyword_position)
            .report_error(self);
        let variants = self.parse_enum_variants();
        if variants.is_empty() {
            self.make_error_at_last_consumed(ParseErrorKind::EnumExpectedVariant)
            .related_token("enum_keyword", enum_keyword_position)
            .report_error(self);
        }
        self.expect(Token::RightBrace, ParseErrorKind::EnumMissingClosingBrace)
            .related_token("enum_keyword", enum_keyword_position)
            .report_error(self);

        let span = TokenSpan::range(
            enum_keyword_position,
            self.last_consumed_position_or_start(),
        );
        self.arena
            .alloc_node(EnumDefinition { name, variants }, span)
    }

    /// Parses the list of enum variants inside braces, handling commas and
    /// errors.
    ///
    /// Returns a vector of successfully parsed variant identifiers.
    fn parse_enum_variants(&mut self) -> ArenaVec<'arena, IdentifierToken> {
        use EnumParseState::{ExpectingSeparator, ExpectingVariant};

        let mut variants = self.arena.vec();
        let mut parser_state = ExpectingVariant;
        while let Some((next_token, next_token_position)) = self.peek_token_and_position() {
            let should_break = match (parser_state, next_token) {
                (_, Token::RightBrace) => break,
                (ExpectingVariant, Token::Comma) => self
                    .recover_from_empty_enum_variant(next_token_position)
                    .is_break(),
                (ExpectingVariant, _) => {
                    parser_state = ExpectingSeparator;
                    self.parse_and_push_enum_variant(&mut variants).is_break()
                }
                (ExpectingSeparator, Token::Comma) => {
                    self.advance(); // `,`
                    parser_state = ExpectingVariant;
                    false
                }
                (ExpectingSeparator, _) => self
                    .parse_enum_variant_after_missing_separator(next_token_position, &mut variants)
                    .is_break(),
            };
            if should_break {
                break;
            }
            self.ensure_forward_progress(next_token_position);
        }
        variants
    }

    /// Recovers from one or more commas appearing where a variant is expected.
    ///
    /// Stops parsing if only a closing brace or end-of-file remains.
    fn recover_from_empty_enum_variant(
        &mut self,
        error_start_position: TokenPosition,
    ) -> ControlFlow<()> {
        while self.peek_token() == Some(Token::Comma) {
            self.advance();
        }
        self.make_error_at_last_consumed(ParseErrorKind::EnumEmptyVariants)
            .widen_error_span_from(error_start_position)
            .blame(TokenSpan::range(
                error_start_position,
                self.last_consumed_position_or_start(),
            ))
            .report_error(self);
        if matches!(self.peek_token(), Some(Token::RightBrace) | None) {
            ControlFlow::Break(())
        } else {
            ControlFlow::Continue(())
        }
    }

    /// Parses one enum variant and appends it to `variants`.
    ///
    /// Stops parsing if recovery does not produce a valid identifier.
    fn parse_and_push_enum_variant(
        &mut self,
        variants: &mut ArenaVec<'arena, IdentifierToken>,
    ) -> ControlFlow<()> {
        self.parse_identifier(ParseErrorKind::EnumVariantInvalidStart)
            .sync_error_until(self, SyncLevel::StatementStart)
            .ok_or_report(self)
            .map_or(ControlFlow::Break(()), |variant| {
                variants.push(variant);
                ControlFlow::Continue(())
            })
    }

    /// Parses a variant after a missing separator and reports the missing-comma
    /// diagnostic if recovery succeeds.
    fn parse_enum_variant_after_missing_separator(
        &mut self,
        error_start_position: TokenPosition,
        variants: &mut ArenaVec<'arena, IdentifierToken>,
    ) -> ControlFlow<()> {
        let Some(variant) = self
            .parse_identifier(ParseErrorKind::EnumExpectedCommaOrClosingBraceAfterVariant)
            .widen_error_span_from(error_start_position)
            .sync_error_until(self, SyncLevel::StatementStart)
            .ok_or_report(self)
        else {
            // If we don't even get a good identifier - error is different
            return ControlFlow::Break(());
        };

        let mut error = self.make_error_at_last_consumed(ParseErrorKind::EnumNoSeparatorBetweenVariants)
            .widen_error_span_from(error_start_position);
        if let Some(previous_variant) = variants.last() {
            error = error.related("previous_variant", previous_variant.span());
        }
        error.report_error(self);

        variants.push(variant);
        ControlFlow::Continue(())
    }
}
