//! Parsing of struct definitions for Fermented UnrealScript.
//!
//! ## C++ block handling
//!
//! The Fermented UnrealScript parser must support parsing several legacy
//! source files that contain `cpptext` or `cppstruct`. Our compiler does not
//! compile with C++ code and therefore does not need these blocks in
//! the resulting AST. We treat them the same as trivia and skip them.
//!
//! However, some related tokens are context-sensitive, so handling these
//! blocks in the general trivia-skipping path would complicate the separation
//! between the lexer and the parser.
//!
//! The resulting files will not be compiled, but they can still be used to
//! extract type information.

use crate::arena::ArenaVec;
use crate::ast::{
    IdentifierToken, QualifiedIdentifierRef, StructDefRef, StructDefinition, StructField,
    StructFieldRef, StructModifier, StructModifierKind, TypeSpecifierRef, VarEditorSpecifierRef,
    VarModifier,
};
use crate::lexer::{Keyword, Token, TokenPosition, TokenSpan};
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};

#[derive(Debug)]
struct ParsedStructFieldPrefix<'src, 'arena> {
    editor_specifiers: Option<ArenaVec<'arena, VarEditorSpecifierRef<'src, 'arena>>>,
    declaration_modifiers: ArenaVec<'arena, VarModifier>,
    type_specifier: TypeSpecifierRef<'src, 'arena>,
}

#[derive(Debug)]
enum StructBodyItemParseOutcome<'src, 'arena> {
    Field(StructFieldRef<'src, 'arena>),
    Skip,
    Stop,
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a `struct` definition after the `struct` keyword has been
    /// consumed.
    pub(crate) fn parse_struct_definition_tail(
        &mut self,
        struct_keyword_position: TokenPosition,
    ) -> StructDefRef<'src, 'arena> {
        let modifiers = self.parse_struct_declaration_modifiers();
        let (name, base_type_name) = self.parse_struct_name_base_and_open_brace();

        let mut fields = self.arena.vec();
        while let Some((next_token, next_position)) = self.peek_token_and_position()
            && next_token != Token::RightBrace
        {
            match self.parse_or_skip_struct_body_item() {
                StructBodyItemParseOutcome::Field(new_field) => fields.push(new_field),
                StructBodyItemParseOutcome::Skip => (),
                StructBodyItemParseOutcome::Stop => break,
            }
            self.ensure_forward_progress(next_position);
        }
        self.expect(Token::RightBrace, ParseErrorKind::StructMissingRightBrace)
            .widen_error_span_from(struct_keyword_position)
            .report_error(self);
        let span = TokenSpan::range(
            struct_keyword_position,
            self.last_consumed_position_or_start(),
        );
        self.arena.alloc_node(
            StructDefinition {
                name,
                base_type_name,
                modifiers,
                fields,
            },
            span,
        )
    }

    /// Parses one item in a struct body or skips an unsupported one.
    ///
    /// Returns [`StructBodyItemParseOutcome::Field`] for a successfully parsed
    /// field, [`StructBodyItemParseOutcome::Skip`] when recovery allows parsing
    /// to continue, and [`StructBodyItemParseOutcome::Stop`] when parsing
    /// should stop at this level.
    fn parse_or_skip_struct_body_item(&mut self) -> StructBodyItemParseOutcome<'src, 'arena> {
        let Some((token, token_position)) = self.peek_token_and_position() else {
            // This is the end of the file;
            // it will be handled by a higher-level parser.
            return StructBodyItemParseOutcome::Stop;
        };
        match token {
            Token::Keyword(Keyword::CppText | Keyword::CppStruct) => {
                self.advance();
                if !self.eat(Token::CppBlock) {
                    self.report_error_here(ParseErrorKind::CppDirectiveMissingCppBlock);
                    self.recover_until(SyncLevel::StatementStart);
                }
                StructBodyItemParseOutcome::Skip
            }
            Token::Keyword(Keyword::Var) => {
                self.advance();
                self.parse_struct_field_tail(token_position)
            }
            _ => {
                self.report_error_here(ParseErrorKind::StructBodyUnexpectedItem);
                self.recover_until(SyncLevel::BlockBoundary);
                StructBodyItemParseOutcome::Skip
            }
        }
    }

    /// Parses a struct field after the `var` keyword has been consumed.
    ///
    /// Returns [`StructBodyItemParseOutcome::Skip`] if the field cannot be
    /// parsed far enough to produce a usable AST node after recovery.
    fn parse_struct_field_tail(
        &mut self,
        var_keyword_position: TokenPosition,
    ) -> StructBodyItemParseOutcome<'src, 'arena> {
        let Some(field_prefix) = self.parse_struct_field_prefix() else {
            return StructBodyItemParseOutcome::Skip;
        };
        // TODO: correct span
        let declarators = self
            .parse_variable_declarators(var_keyword_position, TokenSpan::new(var_keyword_position));
        if !self.eat(Token::Semicolon) {
            self.report_error_here(ParseErrorKind::StructFieldMissingSemicolon);
            self.recover_until(SyncLevel::BlockBoundary);
            let _ = self.eat(Token::Semicolon);
        }
        if declarators.is_empty() {
            return StructBodyItemParseOutcome::Skip;
        }
        let span = TokenSpan::range(var_keyword_position, self.last_consumed_position_or_start());
        StructBodyItemParseOutcome::Field(self.arena.alloc_node(
            StructField {
                type_specifier: field_prefix.type_specifier,
                declaration_modifiers: field_prefix.declaration_modifiers,
                editor_specifiers: field_prefix.editor_specifiers,
                declarators,
            },
            span,
        ))
    }

    fn parse_struct_field_prefix(&mut self) -> Option<ParsedStructFieldPrefix<'src, 'arena>> {
        let editor_specifiers = self.parse_var_editor_specifier_list();
        let declaration_modifiers = self.parse_var_declaration_modifiers();
        let type_specification = self
            .parse_type_specifier()
            .sync_error_until(self, SyncLevel::BlockBoundary)
            .ok_or_report(self)?;
        Some(ParsedStructFieldPrefix {
            editor_specifiers,
            declaration_modifiers,
            type_specifier: type_specification,
        })
    }

    /// Parses the struct name, optional base type, and opening brace.
    ///
    /// Accepts anonymous structs that begin immediately with `{`.
    fn parse_struct_name_base_and_open_brace(
        &mut self,
    ) -> (
        Option<IdentifierToken>,
        Option<QualifiedIdentifierRef<'arena>>,
    ) {
        if self.eat(Token::LeftBrace) {
            return (None, None);
        }
        let name = self
            .parse_identifier(ParseErrorKind::StructExpectedNameOrBrace)
            .ok_or_report(self);
        let base_type_name =
            if let Some((Token::Keyword(Keyword::Extends), extends_keyword_position)) =
                self.peek_token_and_position()
            {
                self.advance();
                self.parse_qualified_identifier(ParseErrorKind::StructExpectedBaseName)
                    .widen_error_span_from(extends_keyword_position)
                    .ok_or_report(self)
            } else {
                None
            };
        self.expect(Token::LeftBrace, ParseErrorKind::StructMissingLeftBrace)
            .report_error(self);
        (name, base_type_name)
    }

    fn parse_struct_declaration_modifiers(&mut self) -> ArenaVec<'arena, StructModifier> {
        let mut modifiers = self.arena.vec();
        while let Some((next_keyword, next_keyword_position)) = self.peek_keyword_and_position() {
            let next_modifier_kind = match next_keyword {
                Keyword::Native => StructModifierKind::Native,
                Keyword::Init => StructModifierKind::Init,
                Keyword::Export => StructModifierKind::Export,
                Keyword::NoExport => StructModifierKind::NoExport,
                Keyword::Transient => StructModifierKind::Transient,
                Keyword::Deprecated => StructModifierKind::Deprecated,
                Keyword::Long => StructModifierKind::Long,
                _ => break,
            };
            modifiers.push(StructModifier {
                kind: next_modifier_kind,
                position: next_keyword_position,
            });
            self.advance();
        }
        modifiers
    }
}
