//! 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::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".

use super::selectors::ParsedCallArgumentSlot;
use crate::ast::{Expression, ExpressionRef, OptionalExpression};
use crate::lexer::{Keyword, Token, TokenPosition};
use crate::parser::{ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, SyncLevel};

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(crate) 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)?;
                self.arena
                    .alloc_node_at(Expression::Integer(value), token_position)
            }
            Token::FloatLiteral => {
                let value = self.decode_float_literal(token_lexeme)?;
                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_tail(token_position),
            Token::Keyword(keyword) => match self.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 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 self.is_for_loop_header_ahead() => self.parse_for_tail(token_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((Token::Less, left_angle_bracket_position)) =
                    self.peek_token_and_position()
                {
                    self.advance(); // '<'
                    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 =
            Parser::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 `)`.
    fn parse_parenthesized_expression_tail(
        &mut self,
        left_parenthesis_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        // Special case for an empty expression
        if let Some((Token::RightParenthesis, right_parenthesis_position)) =
            self.peek_token_and_position()
        {
            self.make_error_here(ParseErrorKind::ParenthesizedExpressionEmpty {
                left_parenthesis_position,
            })
            .widen_error_span_from(left_parenthesis_position)
            .sync_error_at(self, SyncLevel::CloseParenthesis)
            .blame_token(right_parenthesis_position)
            .report_error(self);
            return self.arena.alloc_node_between(
                Expression::Error,
                left_parenthesis_position,
                right_parenthesis_position,
            );
        }
        // Continue parsing normally
        let inner_expression = if self.next_token_definitely_cannot_start_expression() {
            let error = self
                .make_error_here(ParseErrorKind::ExpressionExpected)
                .widen_error_span_from(left_parenthesis_position)
                .sync_error_at(self, SyncLevel::Expression)
                .related_token(left_parenthesis_position);
            let error_span = error.covered_span;
            self.report_error(error);
            return crate::arena::ArenaNode::new_in(
                crate::ast::Expression::Error,
                error_span,
                self.arena,
            );
        } else {
            self.parse_expression()
        };
        let right_parenthesis_position = self
            .expect(
                Token::RightParenthesis,
                ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis {
                    left_parenthesis_position,
                },
            )
            .widen_error_span_from(left_parenthesis_position)
            .sync_error_at(self, SyncLevel::CloseParenthesis)
            .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> {
        // Special case for an empty argument
        if let Some((Token::Greater, right_angle_bracket_position)) = self.peek_token_and_position()
        {
            self.make_error_here(ParseErrorKind::ClassTypeMissingTypeArgument {
                left_angle_bracket_position,
            })
            .widen_error_span_from(left_angle_bracket_position)
            .sync_error_at(self, SyncLevel::CloseAngleBracket)
            .blame_token(right_angle_bracket_position)
            .report_error(self);
            return self.arena.alloc_node_between(
                Expression::Error,
                class_keyword_position,
                right_angle_bracket_position,
            );
        }
        // Qualified identifiers do not have a meaningful fallback option
        let class_type = match self
            .parse_qualified_identifier(ParseErrorKind::ClassTypeInvalidTypeArgument {
                left_angle_bracket_position,
            })
            .widen_error_span_from(class_keyword_position)
            .sync_error_at(self, SyncLevel::CloseAngleBracket)
        {
            Ok(class_type) => class_type,
            Err(error) => {
                self.report_error(error);
                return self.arena.alloc_node_between(
                    Expression::Error,
                    class_keyword_position,
                    self.last_consumed_position()
                        .unwrap_or(class_keyword_position),
                );
            }
        };
        let right_angle_bracket_position = self
            .expect(
                Token::Greater,
                ParseErrorKind::ClassTypeMissingClosingAngleBracket {
                    left_angle_bracket_position,
                },
            )
            .widen_error_span_from(class_keyword_position)
            .sync_error_at(self, SyncLevel::CloseAngleBracket)
            .unwrap_or_fallback(self);
        self.arena.alloc_node_between(
            Expression::ClassType(class_type),
            class_keyword_position,
            right_angle_bracket_position,
        )
    }

    /// Parses a `new` expression with an optional parenthesized argument list.
    ///
    /// Assumes the `new` keyword has already been consumed.
    /// The parenthesized argument list is optional.
    fn parse_new_expression_tail(
        &mut self,
        new_keyword_position: TokenPosition,
    ) -> ExpressionRef<'src, 'arena> {
        let (outer_argument, name_argument, flags_argument) =
            if let Some((Token::LeftParenthesis, left_parenthesis_position)) =
                self.peek_token_and_position()
            {
                self.advance();
                self.parse_new_argument_list_tail(left_parenthesis_position)
            } else {
                (None, None, None)
            };
        // The class specifier is often a literal class reference, but any
        // expression is accepted here.
        let class_specifier = if self.next_token_definitely_cannot_start_expression() {
            let error = self
                .make_error_here(ParseErrorKind::NewMissingClassSpecifier {
                    new_keyword_position,
                })
                .widen_error_span_from(new_keyword_position)
                .sync_error_at(self, SyncLevel::Expression);
            let error_span = error.covered_span;
            self.report_error(error);
            crate::arena::ArenaNode::new_in(crate::ast::Expression::Error, error_span, self.arena)
        } else {
            self.parse_expression()
        };
        let class_specifier_end_position = class_specifier.span().token_to;
        self.arena.alloc_node_between(
            Expression::New {
                outer_argument,
                name_argument,
                flags_argument,
                class_specifier,
            },
            new_keyword_position,
            class_specifier_end_position,
        )
    }

    /// Parses the optional parenthesized arguments of a `new` expression.
    ///
    /// Assumes the opening `(` has already been consumed.
    /// Returns the `outer`, `name`, and `flags` argument slots, each of which
    /// may be omitted. Reports and recovers from a missing closing `)`.
    fn parse_new_argument_list_tail(
        &mut self,
        left_parenthesis_position: TokenPosition,
    ) -> (
        OptionalExpression<'src, 'arena>,
        OptionalExpression<'src, 'arena>,
        OptionalExpression<'src, 'arena>,
    ) {
        let mut outer_argument = None;
        let mut name_argument = None;
        let mut flags_argument = None;

        for slot in [&mut outer_argument, &mut name_argument, &mut flags_argument] {
            match self.parse_call_argument_slot(left_parenthesis_position) {
                ParsedCallArgumentSlot::Argument(argument) => *slot = argument,
                ParsedCallArgumentSlot::NoMoreArguments => break,
            }
        }

        if let Some((next_token, next_token_position)) = self.peek_token_and_position()
            && next_token != Token::RightParenthesis
        {
            self.make_error_here(ParseErrorKind::NewTooManyArguments {
                left_parenthesis_position,
            })
            .widen_error_span_from(left_parenthesis_position)
            .sync_error_until(self, SyncLevel::CloseParenthesis)
            .blame_token(next_token_position)
            .extend_blame_to_covered_end()
            .report_error(self);
        }

        self.expect(
            Token::RightParenthesis,
            ParseErrorKind::NewMissingClosingParenthesis {
                left_parenthesis_position,
            },
        )
        .widen_error_span_from(left_parenthesis_position)
        .sync_error_at(self, SyncLevel::CloseParenthesis)
        .report_error(self);

        (outer_argument, name_argument, flags_argument)
    }

    /// 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(crate) fn next_token_definitely_cannot_start_expression(&mut self) -> bool {
        matches!(
            self.peek_token(),
            None
                // Closing delimiters / separators
                | Some(Token::RightParenthesis)
                | Some(Token::RightBrace)
                | Some(Token::RightBracket)
                | Some(Token::Semicolon)
                | Some(Token::Comma)
                | Some(Token::Colon)
                | Some(Token::Question)

                // Tokens that only continue a previous expression
                | Some(Token::Period)

                // Infix / postfix / assignment operators
                | Some(Token::Exponentiation)
                | Some(Token::Multiply)
                | Some(Token::Divide)
                | Some(Token::Modulo)
                | Some(Token::ConcatSpace)
                | Some(Token::Concat)
                | Some(Token::LeftShift)
                | Some(Token::LogicalRightShift)
                | Some(Token::RightShift)
                | Some(Token::Less)
                | Some(Token::LessEqual)
                | Some(Token::Greater)
                | Some(Token::GreaterEqual)
                | Some(Token::Equal)
                | Some(Token::NotEqual)
                | Some(Token::ApproximatelyEqual)
                | Some(Token::BitwiseAnd)
                | Some(Token::BitwiseOr)
                | Some(Token::BitwiseXor)
                | Some(Token::LogicalAnd)
                | Some(Token::LogicalXor)
                | Some(Token::LogicalOr)
                | Some(Token::Assign)
                | Some(Token::MultiplyAssign)
                | Some(Token::DivideAssign)
                | Some(Token::ModuloAssign)
                | Some(Token::PlusAssign)
                | Some(Token::MinusAssign)
                | Some(Token::ConcatAssign)
                | Some(Token::ConcatSpaceAssign)

                // Non-expression trivia / technical tokens
                | Some(Token::ExecDirective)
                | Some(Token::CppBlock)
                | Some(Token::Hash)
                | Some(Token::LineComment)
                | Some(Token::BlockComment)
                | Some(Token::Newline)
                | Some(Token::Whitespace)
                | Some(Token::Error)
        )
    }
}
