//! Parsing of type specifiers for Fermented UnrealScript.

use crate::ast::{TypeSpecifier, TypeSpecifierRef};
use crate::lexer::{Keyword, Token, TokenPosition, TokenSpan};
use crate::parser::{ParseErrorKind, ParseResult, Parser, ResultRecoveryExt, SyncLevel};

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a type specifier used in variable declarations.
    ///
    /// Accepts named types, `class<...>` types, `array<...>` types, and inline
    /// `enum` and `struct` definitions.
    ///
    /// Returns an error if the next tokens do not form a valid type specifier.
    pub(crate) fn parse_type_specifier(
        &mut self,
    ) -> ParseResult<'src, 'arena, TypeSpecifierRef<'src, 'arena>> {
        let (starting_token, starting_token_position) =
            self.require_token_and_position(ParseErrorKind::TypeSpecifierExpected)?;

        match starting_token {
            Token::Keyword(Keyword::Enum) => {
                self.advance();
                Ok(self.parse_inline_enum_tail(starting_token_position))
            }
            Token::Keyword(Keyword::Struct) => {
                self.advance();
                Ok(self.parse_inline_struct_tail(starting_token_position))
            }
            Token::Keyword(Keyword::Array) => {
                self.advance();
                self.parse_array_type_specification_tail(starting_token_position)
            }
            Token::Keyword(Keyword::Class) => {
                self.advance();
                self.parse_class_type_specification_tail(starting_token_position)
            }
            _ if starting_token.is_valid_type_name() => {
                let type_name =
                    self.parse_qualified_identifier(ParseErrorKind::NamedTypeInvalidName)?;
                let full_span = *type_name.span();
                Ok(self
                    .arena
                    .alloc_node(TypeSpecifier::Named(type_name), full_span))
            }
            _ => Err(self.make_error_at(
                ParseErrorKind::TypeSpecifierExpected,
                starting_token_position,
            )),
        }
    }

    fn parse_inline_enum_tail(
        &mut self,
        starting_token_position: TokenPosition,
    ) -> TypeSpecifierRef<'src, 'arena> {
        let enum_definition = self.parse_enum_definition_tail(starting_token_position);
        let enum_span = TokenSpan::range(starting_token_position, enum_definition.span().end);
        self.arena
            .alloc_node(TypeSpecifier::InlineEnum(enum_definition), enum_span)
    }

    fn parse_inline_struct_tail(
        &mut self,
        starting_token_position: TokenPosition,
    ) -> TypeSpecifierRef<'src, 'arena> {
        let struct_definition = self.parse_struct_definition_tail(starting_token_position);
        let struct_span = TokenSpan::range(starting_token_position, struct_definition.span().end);
        self.arena
            .alloc_node(TypeSpecifier::InlineStruct(struct_definition), struct_span)
    }

    fn parse_array_type_specification_tail(
        &mut self,
        starting_token_position: TokenPosition,
    ) -> ParseResult<'src, 'arena, TypeSpecifierRef<'src, 'arena>> {
        let left_angle_bracket = self.expect(
            Token::Less,
            ParseErrorKind::ArrayTypeMissingOpeningAngleBracket,
        )?;
        if self.eat(Token::Greater) {
            return Err(
                self.make_error_at_last_consumed(ParseErrorKind::ArrayTypeMissingElementType)
            );
        }
        let element_modifiers = self.parse_var_declaration_modifiers();
        // Don't sync on this error, because for a better recovery in likely
        // case of missing `>` we need to check whether inserting it would fix
        // the issue.
        let element_type = self.parse_type_specifier().unwrap_or_fallback(self);
        // A type-closing `>` can be followed up by either another `>` or
        // identifier, so on failure check whether we have an identifier next.
        let closing_angle_bracket_position = match self.expect(
            Token::Greater,
            ParseErrorKind::ArrayTypeMissingClosingAngleBracket,
        ) {
            Ok(position) => position,
            Err(error) => {
                let next_token_can_start_declarator = self
                    .peek_token()
                    .is_some_and(|token| token.is_valid_identifier_name());

                let error = if next_token_can_start_declarator {
                    error
                } else {
                    error.sync_error_at(self, SyncLevel::CloseAngleBracket)
                };
                Err(error).unwrap_or_fallback(self)
            }
        };
        let array_span = TokenSpan::range(starting_token_position, closing_angle_bracket_position);
        Ok(self.arena.alloc_node(
            TypeSpecifier::Array {
                element_type,
                element_modifiers,
            },
            array_span,
        ))
    }

    fn parse_class_type_specification_tail(
        &mut self,
        starting_token_position: TokenPosition,
    ) -> ParseResult<'src, 'arena, TypeSpecifierRef<'src, 'arena>> {
        let (inner_type_name, class_type_end) =
            if let Some(left_angle_bracket_position) = self.eat_with_position(Token::Less) {
                let parsed = self.parse_class_type_argument_tail(
                    starting_token_position,
                    left_angle_bracket_position,
                )?;
                (Some(parsed.type_name), parsed.right_angle_bracket_position)
            } else {
                (None, starting_token_position)
            };
        let span = TokenSpan::range(starting_token_position, class_type_end);
        Ok(self
            .arena
            .alloc_node(TypeSpecifier::Class(inner_type_name), span))
    }
}
