//! Parser for primary expressions in Fermented UnrealScript.
//!
//! This module implements parsing of primary expressions via
//! [`Parser::parse_primary_from_current_token`] and its helper
//! [`Parser::try_parse_keyword_primary`].
//!
//! ## What is a "primary expression" here?
//!
//! In this module, "primary" is used somewhat more broadly than in a
//! textbook grammar, but it still has one essential property:
//!
//! A primary expression is an expression form that can be parsed
//! directly from the current token, without requiring an already
//! parsed left-hand side.
//!
//! This includes ordinary primaries such as literals, identifiers, and
//! parenthesized expressions, as well as keyword-led forms such as
//! `if`, `while`, `for`, `foreach`, `switch`, `return`, `break`,
//! `continue`, `new`, and `class<...>`.
//!
//! By contrast, selectors, postfix operators, and infix operators are
//! not primaries. They cannot stand on their own here: they are parsed
//! only as continuations of an already parsed expression.
//!
//! So "primary" here does not mean "smallest atomic expression".
//! It means "an expression form that does not need a left-hand side
//! in order to be parsed".
//!
//! ## Keyword-led primaries and identifier fallback
//!
//! Some lexer keywords are always parsed as keyword-led primary expressions
//! in expression position: `if`, `while`, `do`, `foreach`, `return`, `break`,
//! `continue`, `new`, `true`, `false`, and `none`.
//!
//! Other keywords are accepted as keyword-led forms only when the following
//! tokens commit to that syntax. Otherwise they remain available as
//! identifier-like primaries.
//!
//! - `for` is parsed as a loop only when followed by a parenthesized header
//!     containing a top-level `;`, matching `for (init; condition; step)`.
//! - `switch` is parsed as a switch expression only when followed by `(`.
//! - `goto` is parsed as a label jump only when it is not followed by `(`.
//! - `class` is parsed as a class type expression only when followed by `<`.
//!
//! These rules are local and syntactic. They avoid name resolution while still
//! supporting existing legacy code that uses some keywords as ordinary names.
//!
//! ### Why is `switch` handled differently?
//!
//! `switch` is handled differently because, in existing `UnrealScript` code,
//! it may appear either as a keyword-led construct or as an identifier.
//!
//! Its disambiguation rule is simpler than for `for`: if the next token is
//! `(`, `switch` is parsed as a `switch` expression; otherwise it remains
//! available as an identifier.

use crate::ast::{Expression, ExpressionRef, OptionalExpression, QualifiedIdentifierRef};
use crate::lexer::{Keyword, Token, TokenPosition, TokenSpan};
use crate::parser::{
    ParseError, ParseErrorKind, ParseExpressionResult, ParseResult, Parser, ResultRecoveryExt,
    SyncLevel,
};

mod new;

pub(crate) struct ParsedClassTypeArgument<'arena> {
    pub(crate) type_name: QualifiedIdentifierRef<'arena>,
    pub(crate) right_angle_bracket_position: TokenPosition,
}

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses a primary expression starting from the provided token.
    ///
    /// The provided token is assumed to be the already consumed first token of
    /// the primary expression.
    ///
    /// This includes literals, identifiers, grouped expressions, block
    /// expressions, and certain keyword-led forms.
    ///
    /// It does not parse selectors, postfix operators, or infix operators;
    /// those are handled afterwards as continuations of the parsed primary.
    ///
    /// # Errors
    ///
    /// Returns [`ParseErrorKind::ExpressionExpected`] if the provided
    /// token cannot begin any valid primary expression in this position.
    pub(super) fn parse_primary_from_current_token(
        &mut self,
        token: Token,
        token_lexeme: &'src str,
        token_position: TokenPosition,
    ) -> ParseExpressionResult<'src, 'arena> {
        Ok(match token {
            Token::IntegerLiteral => {
                let value = self.decode_integer_literal(token_lexeme, token_position)?;
                self.arena
                    .alloc_node_at(Expression::Integer(value), token_position)
            }
            Token::FloatLiteral => {
                let value = self.decode_float_literal(token_lexeme, token_position)?;
                self.arena
                    .alloc_node_at(Expression::Float(value), token_position)
            }
            Token::StringLiteral => {
                let value = self.unescape_string_literal(token_lexeme);
                self.arena
                    .alloc_node_at(Expression::String(value), token_position)
            }
            Token::NameLiteral => self.arena.alloc_node_at(
                Expression::NameLiteral {
                    tag: None,
                    name: token_lexeme,
                },
                token_position,
            ),
            Token::LeftParenthesis => self.parse_parenthesized_expression_tail(token_position),
            Token::LeftBrace => self.parse_block_body_tail(token_position),
            Token::Keyword(keyword) => {
                match self.try_parse_keyword_primary(keyword, token_position) {
                    Some(keyword_expression) => keyword_expression,
                    None => return self.parse_identifier_like_primary(token, token_position),
                }
            }
            _ => return self.parse_identifier_like_primary(token, token_position),
        })
    }

    /// Parses a keyword-led primary expression.
    ///
    /// Returns `None` if the keyword should instead be interpreted as an
    /// identifier in this position.
    fn try_parse_keyword_primary(
        &mut self,
        keyword: Keyword,
        token_position: TokenPosition,
    ) -> OptionalExpression<'src, 'arena> {
        Some(match keyword {
            Keyword::True => self
                .arena
                .alloc_node_at(Expression::Bool(true), token_position),
            Keyword::False => self
                .arena
                .alloc_node_at(Expression::Bool(false), token_position),
            Keyword::None => self.arena.alloc_node_at(Expression::None, token_position),
            Keyword::If => self.parse_if_tail(token_position),
            Keyword::While => self.parse_while_tail(token_position),
            Keyword::Do => self.parse_do_until_tail(token_position),
            Keyword::ForEach => self.parse_foreach_tail(token_position),
            Keyword::Return => self.parse_return_tail(token_position),
            Keyword::Break => self.parse_break_tail(token_position),
            Keyword::Continue => self
                .arena
                .alloc_node_at(Expression::Continue, token_position),
            Keyword::New => self.parse_new_expression_tail(token_position),
            // These keywords remain valid identifiers unless the following
            // tokens commit to the keyword-led form.
            Keyword::For
                if let Some(left_parenthesis_position) =
                    self.peek_for_loop_header_left_parenthesis_position() =>
            {
                self.advance(); // `(`
                self.parse_for_tail(token_position, left_parenthesis_position)
            }
            Keyword::Goto if !matches!(self.peek_token(), Some(Token::LeftParenthesis)) => {
                self.parse_goto_tail(token_position)
            }
            // `switch` is only treated as keyword-led when followed by `(`
            // to match the syntax accepted by the existing codebase.
            Keyword::Switch if matches!(self.peek_token(), Some(Token::LeftParenthesis)) => {
                self.parse_switch_tail(token_position)
            }
            Keyword::Class => {
                if let Some(left_angle_bracket_position) = self.eat_with_position(Token::Less) {
                    self.parse_class_type_tail(token_position, left_angle_bracket_position)
                } else {
                    return None;
                }
            }
            _ => return None,
        })
    }

    /// Attempts to parse the already-consumed token as an identifier or tagged
    /// name literal.
    ///
    /// # Errors
    ///
    /// Returns [`ParseErrorKind::ExpressionExpected`] if the token
    /// cannot be used as an identifier in this position.
    fn parse_identifier_like_primary(
        &mut self,
        primary_token: Token,
        primary_token_position: TokenPosition,
    ) -> ParseExpressionResult<'src, 'arena> {
        let identifier_token =
            Self::identifier_token_from_token(primary_token, primary_token_position).ok_or_else(
                || self.make_error_at(ParseErrorKind::ExpressionExpected, primary_token_position),
            )?;
        // A token that is valid as an identifier may still start a tagged-name
        // literal such as `Texture'Foo.Bar'`.
        let expression = if let Some((Token::NameLiteral, lexeme, name_position)) =
            self.peek_token_lexeme_and_position()
        {
            self.advance();
            self.arena.alloc_node_between(
                Expression::NameLiteral {
                    tag: Some(identifier_token),
                    name: lexeme,
                },
                primary_token_position,
                name_position,
            )
        } else {
            self.arena.alloc_node_at(
                Expression::Identifier(identifier_token),
                primary_token_position,
            )
        };
        Ok(expression)
    }

    /// Parses a parenthesized expression.
    ///
    /// Assumes the opening `(` has already been consumed.
    /// Reports and recovers from a missing closing `)`.
    pub(super) fn parse_parenthesized_expression_tail(
        &mut self,
        left_parenthesis_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        if self.next_token_definitely_cannot_start_expression() {
            return self
                .make_error_at_last_consumed(ParseErrorKind::ParenthesizedExpressionInvalidStart)
                .widen_error_span_from(left_parenthesis_position)
                .extend_blame_to_next_token(self)
                .related_token("left_parenthesis", left_parenthesis_position)
                .sync_error_at_matching_delimiter(self, left_parenthesis_position)
                .fallback(self);
        };
        let inner_expression = self.parse_expression();
        let right_parenthesis_position = self
            .expect(
                Token::RightParenthesis,
                ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis,
            )
            .widen_error_span_from(left_parenthesis_position)
            .sync_error_at_matching_delimiter(self, left_parenthesis_position)
            .extend_blame_start_to_covered_start()
            .related_token("left_parenthesis", left_parenthesis_position)
            .unwrap_or_fallback(self);
        self.arena.alloc_node_between(
            Expression::Parentheses(inner_expression),
            left_parenthesis_position,
            right_parenthesis_position,
        )
    }

    /// Parses a class type expression of the form `class<...>`.
    ///
    /// Assumes the `class` keyword and following `<` token have already been
    /// consumed. Reports and recovers from malformed type syntax locally.
    fn parse_class_type_tail(
        &mut self,
        class_keyword_position: TokenPosition,
        left_angle_bracket_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let parsed = match self
            .parse_class_type_argument_tail(class_keyword_position, left_angle_bracket_position)
        {
            Ok(parsed) => parsed,
            Err(error) => return self.report_error_with_fallback(error),
        };
        self.arena.alloc_node_between(
            Expression::ClassType(parsed.type_name),
            class_keyword_position,
            parsed.right_angle_bracket_position,
        )
    }

    /// Parses a class type argument of the form `...>`.
    ///
    /// Assumes the `class` keyword and following `<` token have already been
    /// consumed. Returns the parsed type argument and closing `>` position.
    pub(crate) fn parse_class_type_argument_tail(
        &mut self,
        class_keyword_position: TokenPosition,
        left_angle_bracket_position: TokenPosition,
    ) -> ParseResult<'src, 'arena, ParsedClassTypeArgument<'arena>> {
        match self.peek_token_and_position() {
            Some((Token::Greater, right_angle_bracket_position)) => {
                self.advance();

                Err(self.make_missing_class_type_argument_error(
                    class_keyword_position,
                    left_angle_bracket_position,
                    right_angle_bracket_position,
                ))
            }
            Some((first_token, _)) if first_token.is_valid_identifier_name() => self
                .parse_nonempty_class_type_argument_tail(
                    class_keyword_position,
                    left_angle_bracket_position,
                ),
            Some((_, bad_position)) => Err(self.make_invalid_class_type_start_error(
                class_keyword_position,
                left_angle_bracket_position,
                bad_position,
            )),
            None => Err(self.make_invalid_class_type_start_error(
                class_keyword_position,
                left_angle_bracket_position,
                self.file.eof(),
            )),
        }
    }

    /// Parses a nonempty class type argument followed by its closing `>`.
    ///
    /// Assumes the next token can start a qualified type name.
    fn parse_nonempty_class_type_argument_tail(
        &mut self,
        class_keyword_position: TokenPosition,
        left_angle_bracket_position: TokenPosition,
    ) -> ParseResult<'src, 'arena, ParsedClassTypeArgument<'arena>> {
        let type_name = self
            .parse_qualified_identifier(ParseErrorKind::ClassTypeExpectedQualifiedTypeName)
            .widen_error_span_from(class_keyword_position)
            .sync_error_at(self, SyncLevel::CloseAngleBracket)
            .related_token("class_keyword", class_keyword_position)?;

        let right_angle_bracket_position = self
            .expect(
                Token::Greater,
                ParseErrorKind::ClassTypeMissingClosingAngleBracket,
            )
            .widen_error_span_from(class_keyword_position)
            .related_token("left_angle_bracket", left_angle_bracket_position)
            .related_token("class_keyword", class_keyword_position)?;

        Ok(ParsedClassTypeArgument {
            type_name,
            right_angle_bracket_position,
        })
    }

    fn make_missing_class_type_argument_error(
        &mut self,
        class_keyword_position: TokenPosition,
        left_angle_bracket_position: TokenPosition,
        right_angle_bracket_position: TokenPosition,
    ) -> ParseError {
        self.make_error_at_last_consumed(ParseErrorKind::ClassTypeMissingTypeArgument)
            .widen_error_span_from(class_keyword_position)
            .blame(TokenSpan::range(
                left_angle_bracket_position,
                right_angle_bracket_position,
            ))
            .related_token("left_angle_bracket", left_angle_bracket_position)
            .related_token("class_keyword", class_keyword_position)
    }

    fn make_invalid_class_type_start_error(
        &mut self,
        class_keyword_position: TokenPosition,
        left_angle_bracket_position: TokenPosition,
        bad_position: TokenPosition,
    ) -> ParseError {
        self.make_error_at_last_consumed(ParseErrorKind::ClassTypeInvalidStart)
            .widen_error_span_from(class_keyword_position)
            .sync_error_at(self, SyncLevel::CloseAngleBracket)
            .blame_token(bad_position)
            .related_token("left_angle_bracket", left_angle_bracket_position)
            .related_token("class_keyword", class_keyword_position)
    }

    /// Returns `true` iff the next token is definitely not a valid start of an
    /// expression.
    ///
    /// This is intentionally conservative:
    /// - `true` means parsing an expression here is pointless;
    /// - `false` means "might be valid", so the normal expression parser should
    ///   decide and potentially emit a more specific error.
    #[must_use]
    pub(super) fn next_token_definitely_cannot_start_expression(&mut self) -> bool {
        self.peek_token()
            .map_or(true, Token::is_definitely_not_expression_start)
    }
}
