//! Parsing of callable definitions for Fermented UnrealScript
//! (functions, events, delegates, operators).

use crate::arena::ArenaVec;

use crate::ast::{
    CallableDefinition, CallableDefinitionRef, CallableKind, CallableModifier,
    CallableModifierKind, CallableName, IdentifierToken, InfixOperator, InfixOperatorName,
    ParameterRef, PostfixOperator, PostfixOperatorName, PrefixOperator, PrefixOperatorName,
    TypeSpecifierRef,
};
use crate::lexer::{Keyword, Token, TokenPosition, TokenSpan};
use crate::parser::{
    ParseError, ParseErrorKind, ParseResult, Parser, ResultRecoveryExt, SyncLevel,
    recovery::RecoveryFallback,
};

/// Temporary parsed representation of a callable header without its body.
#[derive(Debug)]
pub(super) struct ParsedCallableHeader<'src, 'arena> {
    pub start_position: TokenPosition,
    pub modifiers: crate::arena::ArenaVec<'arena, CallableModifier>,
    pub kind: CallableKind,
    pub return_type_specifier: Option<TypeSpecifierRef<'src, 'arena>>,
    pub name: CallableName,
    pub parameters: crate::arena::ArenaVec<'arena, ParameterRef<'src, 'arena>>,
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for ParsedCallableHeader<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, error: &ParseError) -> Self {
        let fallback_position = error.covered_span.start;
        ParsedCallableHeader {
            start_position: fallback_position,
            modifiers: parser.arena.vec(),
            kind: CallableKind::Function,
            return_type_specifier: None,
            name: CallableName::Identifier(IdentifierToken(fallback_position)),
            parameters: parser.arena.vec(),
        }
    }
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a callable definition.
    ///
    /// Assumes [`Parser::is_callable_header_ahead`] has already confirmed that
    /// a callable declaration begins at the current position. This affects
    /// the diagnostics produced for malformed input.
    #[must_use]
    pub(crate) fn parse_callable_definition(&mut self) -> CallableDefinitionRef<'src, 'arena> {
        let header = self.parse_callable_header().unwrap_or_fallback(self);

        let body = if self.eat(Token::LeftBrace) {
            Some(self.parse_braced_block_statements_tail(self.last_consumed_position_or_start()))
        } else {
            self.expect(
                Token::Semicolon,
                ParseErrorKind::CallableMissingBodyOrSemicolon,
            )
            .report_error(self);
            None
        };

        let span = TokenSpan::range(
            header.start_position,
            self.last_consumed_position_or_start(),
        );

        self.arena.alloc_node(
            CallableDefinition {
                name: header.name,
                kind: header.kind,
                return_type_specifier: header.return_type_specifier,
                modifiers: header.modifiers,
                parameters: header.parameters,
                body,
            },
            span,
        )
    }

    /// Parses a callable header without the body.
    fn parse_callable_header(
        &mut self,
    ) -> ParseResult<'src, 'arena, ParsedCallableHeader<'src, 'arena>> {
        let start_position = self.require_position(ParseErrorKind::CallableExpectedHeader)?;
        let mut modifiers = self.arena.vec();
        self.collect_callable_modifiers(&mut modifiers);
        let kind = self.parse_callable_kind()?;
        self.collect_callable_modifiers(&mut modifiers);

        // `(` cannot appear inside a return type in this grammar,
        // so seeing it here means the callable has no return type specifier.
        let return_type_specifier = match self.peek_token_at(1) {
            Some(Token::LeftParenthesis) => None,
            _ => Some(self.parse_type_specifier()?),
        };
        let name = self.parse_callable_name(kind)?;

        self.expect(
            Token::LeftParenthesis,
            ParseErrorKind::CallableParamsMissingOpeningParenthesis,
        )
        .report_error(self);
        let parameters = self.parse_parameter_list();
        self.expect(
            Token::RightParenthesis,
            ParseErrorKind::CallableParamsMissingClosingParenthesis,
        )
        .sync_error_at(self, SyncLevel::CloseParenthesis)
        .report_error(self);

        Ok(ParsedCallableHeader {
            start_position,
            modifiers,
            kind,
            return_type_specifier,
            name,
            parameters,
        })
    }

    fn parse_callable_kind(&mut self) -> ParseResult<'src, 'arena, CallableKind> {
        if let Some(keyword) = self.peek_keyword() {
            // Handle this separately because only infix operators can carry
            // an optional precedence and cannot, therefore, be handled by
            // a simple converter.
            if keyword == Keyword::Operator {
                self.advance();
                let precedence = self.parse_optional_parenthesized_integer(
                    ParseErrorKind::CallableOperatorInvalidPrecedence,
                );
                return Ok(CallableKind::InfixOperator(precedence));
            }
            if let Ok(kind) = CallableKind::try_from(keyword) {
                self.advance();
                return Ok(kind);
            }
        }
        Err(self.make_error_at_last_consumed(ParseErrorKind::CallableExpectedKind))
    }

    fn parse_callable_name(
        &mut self,
        kind: CallableKind,
    ) -> ParseResult<'src, 'arena, CallableName> {
        match kind {
            CallableKind::Function | CallableKind::Event | CallableKind::Delegate => self
                .parse_identifier(ParseErrorKind::CallableNameNotIdentifier)
                .map(CallableName::Identifier),
            CallableKind::PrefixOperator => {
                let (token, operator_position) = self.require_token_and_position(
                    ParseErrorKind::CallablePrefixOperatorInvalidSymbol,
                )?;
                let operator = PrefixOperator::try_from(token).map_err(|()| {
                    self.make_error_at_last_consumed(ParseErrorKind::CallablePrefixOperatorInvalidSymbol)
                })?;
                self.advance();
                Ok(CallableName::PrefixOperator(PrefixOperatorName {
                    kind: operator,
                    position: operator_position,
                }))
            }
            CallableKind::InfixOperator(_) => {
                let (token, operator_position) = self.require_token_and_position(
                    ParseErrorKind::CallableInfixOperatorInvalidSymbol,
                )?;
                let operator = InfixOperator::try_from(token).map_err(|()| {
                    self.make_error_at_last_consumed(ParseErrorKind::CallableInfixOperatorInvalidSymbol)
                })?;
                self.advance();
                Ok(CallableName::InfixOperator(InfixOperatorName {
                    kind: operator,
                    position: operator_position,
                }))
            }
            CallableKind::PostfixOperator => {
                let (token, operator_position) = self.require_token_and_position(
                    ParseErrorKind::CallablePostfixOperatorInvalidSymbol,
                )?;
                let operator = PostfixOperator::try_from(token).map_err(|()| {
                    self.make_error_at_last_consumed(ParseErrorKind::CallablePostfixOperatorInvalidSymbol)
                })?;
                self.advance();
                Ok(CallableName::PostfixOperator(PostfixOperatorName {
                    kind: operator,
                    position: operator_position,
                }))
            }
        }
    }

    /// Parses an uninterrupted sequence of function modifiers into
    /// given vector.
    pub(crate) fn collect_callable_modifiers(
        &mut self,
        modifiers: &mut ArenaVec<'arena, CallableModifier>,
    ) {
        while let Some(next_mod) = self.parse_function_modifier() {
            modifiers.push(next_mod);
        }
    }

    fn parse_function_modifier(&mut self) -> Option<CallableModifier> {
        let (keyword, start) = self.peek_keyword_and_position()?;

        let kind = match keyword {
            Keyword::Native => {
                self.advance();
                let native_id = self.parse_optional_parenthesized_integer(
                    ParseErrorKind::NativeModifierIdNotIntegerLiteral,
                );
                CallableModifierKind::Native(native_id)
            }
            Keyword::Config => {
                self.advance();
                let ident = self
                    .parse_required_parenthesized_identifier(
                        ParseErrorKind::ParenthesisedIdentifierMissingClosingParenthesis,
                        ParseErrorKind::ParenthesisedIdentifierMissingClosingParenthesis,
                    )
                    .unwrap_or(IdentifierToken(start));
                CallableModifierKind::Config(ident)
            }
            _ => {
                let simple = CallableModifierKind::try_from(keyword).ok()?;
                // Only advance after confirming it is the modifier
                self.advance();
                simple
            }
        };

        let span = TokenSpan::range(start, self.last_consumed_position_or_start());
        Some(CallableModifier { kind, span })
    }

    fn parse_optional_parenthesized_integer(&mut self, close_err: ParseErrorKind) -> Option<u128> {
        if !self.eat(Token::LeftParenthesis) {
            return None;
        }

        let value = match self.peek_token_lexeme_and_position() {
            Some((Token::IntegerLiteral, lex, token_position)) => {
                self.advance();
                self.decode_integer_literal(lex, token_position).ok_or_report(self)
            }
            Some(_) => {
                self.report_error_here(ParseErrorKind::OperatorPrecedenceNotIntegerLiteral);
                self.advance();
                None
            }
            None => {
                self.report_error_here(ParseErrorKind::OperatorPrecedenceNotIntegerLiteral);
                None
            }
        };

        self.expect(Token::RightParenthesis, close_err)
            .sync_error_at(self, SyncLevel::CloseParenthesis)
            .report_error(self);

        value
    }

    fn parse_required_parenthesized_identifier(
        &mut self,
        close_err: ParseErrorKind,
        ident_err: ParseErrorKind,
    ) -> Option<IdentifierToken> {
        if !self.eat(Token::LeftParenthesis) {
            self.report_error_here(ident_err);
            return None;
        }

        let ident = match self.peek_token_lexeme_and_position() {
            Some((tok, _, pos)) if tok.is_valid_identifier_name() => {
                self.advance();
                Some(IdentifierToken(pos))
            }
            Some(_) => {
                self.report_error_here(ident_err);
                self.advance();
                None
            }
            None => {
                self.report_error_here(ident_err);
                None
            }
        };

        self.expect(Token::RightParenthesis, close_err)
            .sync_error_at(self, SyncLevel::CloseParenthesis)
            .report_error(self);

        ident
    }
}
