//! Expression parsing.

use crate::error::Error;
use crate::pattern::reinterpret_expression_as_arrow_params;
use crate::precedence::{BinaryInfo, BinaryKind, MIN_PRECEDENCE, assignment_operator, binary_info};
use crate::stream::{
    Peek, expect_identifier, expect_identifier_or_keyword, expect_kind, expect_private_identifier,
    is_kind, peek, span_at,
};
use ecma_lex_cat::token::{Token, TokenKind};
use ecma_syntax_cat::expression::{
    Expression, ExpressionKind, MemberProperty, ObjectMember, ObjectPropertyKind, PropertyKey,
};
use ecma_syntax_cat::function::{ArrowBody, ArrowFunction};
use ecma_syntax_cat::literal::Literal;
use ecma_syntax_cat::operator::{UnaryOperator, UpdateOperator};
use ecma_syntax_cat::pattern::Pattern;
use ecma_syntax_cat::span::Span;

/// Parse a full expression, accepting comma-sequence at the top level.
pub fn parse_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (first, after_first) = parse_assignment_expression(tokens, pos)?;
    parse_sequence_tail(tokens, after_first, first)
}

fn parse_sequence_tail(
    tokens: &[Token],
    pos: usize,
    first: Expression,
) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::Comma) {
        collect_sequence(tokens, pos, vec![first])
    } else {
        Ok((first, pos))
    }
}

fn collect_sequence(
    tokens: &[Token],
    pos: usize,
    acc: Vec<Expression>,
) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::Comma) {
        let (next, after_next) = parse_assignment_expression(tokens, pos + 1)?;
        let extended: Vec<Expression> = acc.into_iter().chain(std::iter::once(next)).collect();
        collect_sequence(tokens, after_next, extended)
    } else {
        let span = sequence_span(&acc);
        Ok((
            Expression::new(ExpressionKind::Sequence { expressions: acc }, span),
            pos,
        ))
    }
}

fn sequence_span(exprs: &[Expression]) -> Span {
    let start = exprs.first().map_or(Span::synthetic(), Expression::span);
    let end = exprs.last().map_or(Span::synthetic(), Expression::span);
    Span::new(start.start(), end.end())
}

/// Parse an expression that does not include the top-level comma.
pub fn parse_assignment_expression(
    tokens: &[Token],
    pos: usize,
) -> Result<(Expression, usize), Error> {
    if let Some(parsed) = try_parse_arrow_starter(tokens, pos)? {
        Ok(parsed)
    } else {
        parse_assignment_tail(tokens, pos)
    }
}

fn parse_assignment_tail(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (left, after_left) = parse_conditional_expression(tokens, pos)?;
    if is_kind(tokens, after_left, &TokenKind::Arrow) {
        let params = reinterpret_expression_as_arrow_params(left)?;
        finish_arrow(tokens, after_left + 1, params, false, span_at(tokens, pos))
    } else if let Some(op) = peek_assignment_operator(tokens, after_left) {
        let (right, after_right) = parse_assignment_expression(tokens, after_left + 1)?;
        let span = combined_span(left.span(), right.span());
        Ok((
            Expression::new(
                ExpressionKind::Assignment {
                    operator: op,
                    left: Box::new(left),
                    right: Box::new(right),
                },
                span,
            ),
            after_right,
        ))
    } else {
        Ok((left, after_left))
    }
}

fn peek_assignment_operator(
    tokens: &[Token],
    pos: usize,
) -> Option<ecma_syntax_cat::operator::AssignmentOperator> {
    match peek(tokens, pos) {
        Peek::Eof => None,
        Peek::Token(tok) => assignment_operator(tok.value()),
    }
}

/// Try to parse a form that can only be an arrow function: `async <ident> =>`,
/// `async (...) =>`, or `() =>`.  Returns `Ok(Some(_))` if it consumed an
/// arrow form, `Ok(None)` if the input is not an arrow starter.
fn try_parse_arrow_starter(
    tokens: &[Token],
    pos: usize,
) -> Result<Option<(Expression, usize)>, Error> {
    if is_empty_paren_arrow(tokens, pos) {
        let after_open = pos + 1;
        let after_close = after_open + 1;
        let after_arrow = expect_kind(tokens, after_close, &TokenKind::Arrow, "`=>`")?;
        let span = span_at(tokens, pos);
        finish_arrow(tokens, after_arrow, Vec::new(), false, span).map(Some)
    } else if is_async_function(tokens, pos) {
        let after_async = pos + 1;
        parse_function_expression(tokens, after_async, true).map(Some)
    } else {
        Ok(None)
    }
}

fn is_empty_paren_arrow(tokens: &[Token], pos: usize) -> bool {
    is_kind(tokens, pos, &TokenKind::LParen)
        && is_kind(tokens, pos + 1, &TokenKind::RParen)
        && is_kind(tokens, pos + 2, &TokenKind::Arrow)
}

fn is_async_function(tokens: &[Token], pos: usize) -> bool {
    match peek(tokens, pos) {
        Peek::Eof => false,
        Peek::Token(tok) => match tok.value() {
            TokenKind::Identifier(name) if name == "async" => {
                is_kind(tokens, pos + 1, &TokenKind::KwFunction)
            }
            _other => false,
        },
    }
}

fn finish_arrow(
    tokens: &[Token],
    pos: usize,
    params: Vec<Pattern>,
    is_async: bool,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    let (body, end_pos, body_span) = parse_arrow_body(tokens, pos)?;
    let arrow = ArrowFunction::new(params, body, is_async);
    let span = Span::new(start_span.start(), body_span.end());
    Ok((
        Expression::new(ExpressionKind::ArrowFunction(Box::new(arrow)), span),
        end_pos,
    ))
}

fn parse_arrow_body(tokens: &[Token], pos: usize) -> Result<(ArrowBody, usize, Span), Error> {
    if is_kind(tokens, pos, &TokenKind::LBrace) {
        let (stmts, after_close) = crate::statement::parse_block_body(tokens, pos)?;
        let span = span_at(tokens, pos);
        Ok((ArrowBody::Block(stmts), after_close, span))
    } else {
        let (expr, after_expr) = parse_assignment_expression(tokens, pos)?;
        let span = expr.span();
        Ok((ArrowBody::Expression(Box::new(expr)), after_expr, span))
    }
}

fn parse_conditional_expression(
    tokens: &[Token],
    pos: usize,
) -> Result<(Expression, usize), Error> {
    let (test, after_test) = parse_binary_expression(tokens, pos, MIN_PRECEDENCE)?;
    if is_kind(tokens, after_test, &TokenKind::Question) {
        let (consequent, after_conseq) = parse_assignment_expression(tokens, after_test + 1)?;
        let after_colon = expect_kind(tokens, after_conseq, &TokenKind::Colon, "`:`")?;
        let (alternate, after_alt) = parse_assignment_expression(tokens, after_colon)?;
        let span = combined_span(test.span(), alternate.span());
        Ok((
            Expression::new(
                ExpressionKind::Conditional {
                    test: Box::new(test),
                    consequent: Box::new(consequent),
                    alternate: Box::new(alternate),
                },
                span,
            ),
            after_alt,
        ))
    } else {
        Ok((test, after_test))
    }
}

fn parse_binary_expression(
    tokens: &[Token],
    pos: usize,
    min_prec: u8,
) -> Result<(Expression, usize), Error> {
    let (lhs, after_lhs) = parse_unary_expression(tokens, pos)?;
    parse_binary_tail(tokens, after_lhs, lhs, min_prec)
}

fn parse_binary_tail(
    tokens: &[Token],
    pos: usize,
    lhs: Expression,
    min_prec: u8,
) -> Result<(Expression, usize), Error> {
    let info = peek_binary_info(tokens, pos);
    match info {
        None => Ok((lhs, pos)),
        Some(BinaryInfo { precedence, .. }) if precedence < min_prec => Ok((lhs, pos)),
        Some(info) => {
            let next_min = if info.right_assoc {
                info.precedence
            } else {
                info.precedence + 1
            };
            let (rhs, after_rhs) = parse_binary_expression(tokens, pos + 1, next_min)?;
            let combined = build_binary(info.op, lhs, rhs);
            parse_binary_tail(tokens, after_rhs, combined, min_prec)
        }
    }
}

fn peek_binary_info(tokens: &[Token], pos: usize) -> Option<BinaryInfo> {
    match peek(tokens, pos) {
        Peek::Eof => None,
        Peek::Token(tok) => binary_info(tok.value()),
    }
}

fn build_binary(op: BinaryKind, lhs: Expression, rhs: Expression) -> Expression {
    let span = combined_span(lhs.span(), rhs.span());
    let kind = match op {
        BinaryKind::Binary(binary_op) => ExpressionKind::Binary {
            operator: binary_op,
            left: Box::new(lhs),
            right: Box::new(rhs),
        },
        BinaryKind::Logical(logical_op) => ExpressionKind::Logical {
            operator: logical_op,
            left: Box::new(lhs),
            right: Box::new(rhs),
        },
    };
    Expression::new(kind, span)
}

fn combined_span(a: Span, b: Span) -> Span {
    Span::new(a.start(), b.end())
}

fn parse_unary_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    match peek(tokens, pos) {
        Peek::Eof => Err(Error::UnexpectedEof {
            expected: "expression",
        }),
        Peek::Token(tok) => match tok.value() {
            TokenKind::KwDelete => {
                parse_unary_prefix(tokens, pos, UnaryOperator::Delete, start_span)
            }
            TokenKind::KwVoid => parse_unary_prefix(tokens, pos, UnaryOperator::Void, start_span),
            TokenKind::KwTypeof => {
                parse_unary_prefix(tokens, pos, UnaryOperator::TypeOf, start_span)
            }
            TokenKind::Plus => parse_unary_prefix(tokens, pos, UnaryOperator::Plus, start_span),
            TokenKind::Minus => parse_unary_prefix(tokens, pos, UnaryOperator::Minus, start_span),
            TokenKind::Tilde => {
                parse_unary_prefix(tokens, pos, UnaryOperator::BitwiseNot, start_span)
            }
            TokenKind::Bang => {
                parse_unary_prefix(tokens, pos, UnaryOperator::LogicalNot, start_span)
            }
            TokenKind::PlusPlus => {
                parse_update_prefix(tokens, pos, UpdateOperator::Increment, start_span)
            }
            TokenKind::MinusMinus => {
                parse_update_prefix(tokens, pos, UpdateOperator::Decrement, start_span)
            }
            TokenKind::KwAwait => parse_await(tokens, pos, start_span),
            TokenKind::KwYield => parse_yield(tokens, pos, start_span),
            _other => parse_update_expression(tokens, pos),
        },
    }
}

fn parse_unary_prefix(
    tokens: &[Token],
    pos: usize,
    operator: UnaryOperator,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    let (operand, after) = parse_unary_expression(tokens, pos + 1)?;
    let span = Span::new(start_span.start(), operand.span().end());
    Ok((
        Expression::new(
            ExpressionKind::Unary {
                operator,
                argument: Box::new(operand),
            },
            span,
        ),
        after,
    ))
}

fn parse_update_prefix(
    tokens: &[Token],
    pos: usize,
    operator: UpdateOperator,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    let (operand, after) = parse_unary_expression(tokens, pos + 1)?;
    let span = Span::new(start_span.start(), operand.span().end());
    Ok((
        Expression::new(
            ExpressionKind::Update {
                operator,
                argument: Box::new(operand),
                prefix: true,
            },
            span,
        ),
        after,
    ))
}

fn parse_await(
    tokens: &[Token],
    pos: usize,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    let (arg, after) = parse_unary_expression(tokens, pos + 1)?;
    let span = Span::new(start_span.start(), arg.span().end());
    Ok((
        Expression::new(
            ExpressionKind::Await {
                argument: Box::new(arg),
            },
            span,
        ),
        after,
    ))
}

fn parse_yield(
    tokens: &[Token],
    pos: usize,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    let after_kw = pos + 1;
    let delegate = is_kind(tokens, after_kw, &TokenKind::Star);
    let after_star = if delegate { after_kw + 1 } else { after_kw };
    let yields_arg = starts_expression(tokens, after_star);
    let (argument, end_pos, end_span) = if yields_arg {
        let (expr, after_expr) = parse_assignment_expression(tokens, after_star)?;
        let s = expr.span();
        (Some(Box::new(expr)), after_expr, s)
    } else {
        (None, after_star, start_span)
    };
    let span = Span::new(start_span.start(), end_span.end());
    Ok((
        Expression::new(ExpressionKind::Yield { argument, delegate }, span),
        end_pos,
    ))
}

fn starts_expression(tokens: &[Token], pos: usize) -> bool {
    match peek(tokens, pos) {
        Peek::Eof => false,
        Peek::Token(tok) => match tok.value() {
            TokenKind::Semicolon
            | TokenKind::Comma
            | TokenKind::RParen
            | TokenKind::RBracket
            | TokenKind::RBrace
            | TokenKind::Colon
            | TokenKind::Eof => false,
            _other => true,
        },
    }
}

fn parse_update_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (operand, after) = parse_lhs_expression(tokens, pos)?;
    if is_kind(tokens, after, &TokenKind::PlusPlus) {
        let span = Span::new(operand.span().start(), span_at(tokens, after).end());
        Ok((
            Expression::new(
                ExpressionKind::Update {
                    operator: UpdateOperator::Increment,
                    argument: Box::new(operand),
                    prefix: false,
                },
                span,
            ),
            after + 1,
        ))
    } else if is_kind(tokens, after, &TokenKind::MinusMinus) {
        let span = Span::new(operand.span().start(), span_at(tokens, after).end());
        Ok((
            Expression::new(
                ExpressionKind::Update {
                    operator: UpdateOperator::Decrement,
                    argument: Box::new(operand),
                    prefix: false,
                },
                span,
            ),
            after + 1,
        ))
    } else {
        Ok((operand, after))
    }
}

fn parse_lhs_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (lhs, after) = parse_new_or_member(tokens, pos)?;
    parse_call_tail(tokens, after, lhs)
}

fn parse_new_or_member(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::KwNew) {
        parse_new_expression(tokens, pos)
    } else {
        parse_member_expression(tokens, pos)
    }
}

fn parse_new_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    let after_new = pos + 1;
    if is_kind(tokens, after_new, &TokenKind::Dot)
        && matches!(
            peek(tokens, after_new + 1),
            Peek::Token(t) if matches!(t.value(), TokenKind::Identifier(name) if name == "target")
        )
    {
        let span = Span::new(start_span.start(), span_at(tokens, after_new + 1).end());
        let meta = ecma_syntax_cat::identifier::Identifier::new("new")?;
        let prop = ecma_syntax_cat::identifier::Identifier::new("target")?;
        Ok((
            Expression::new(
                ExpressionKind::MetaProperty {
                    meta,
                    property: prop,
                },
                span,
            ),
            after_new + 2,
        ))
    } else {
        let (callee, after_callee) = parse_new_or_member(tokens, after_new)?;
        let (arguments, after_args) = if is_kind(tokens, after_callee, &TokenKind::LParen) {
            parse_arguments(tokens, after_callee)?
        } else {
            (Vec::new(), after_callee)
        };
        let end_span = span_at(tokens, after_args.saturating_sub(1));
        let span = Span::new(start_span.start(), end_span.end());
        let new_expr = Expression::new(
            ExpressionKind::New {
                callee: Box::new(callee),
                arguments,
            },
            span,
        );
        parse_member_tail(tokens, after_args, new_expr)
    }
}

fn parse_member_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (base, after_base) = parse_primary_expression(tokens, pos)?;
    parse_member_tail(tokens, after_base, base)
}

fn parse_member_tail(
    tokens: &[Token],
    pos: usize,
    base: Expression,
) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::Dot) {
        let (name, after_name) = expect_identifier_or_keyword(tokens, pos + 1)?;
        let span = Span::new(base.span().start(), span_at(tokens, after_name - 1).end());
        let member = Expression::new(
            ExpressionKind::Member {
                object: Box::new(base),
                property: MemberProperty::Identifier(name),
                optional: false,
            },
            span,
        );
        parse_member_tail(tokens, after_name, member)
    } else if is_kind(tokens, pos, &TokenKind::LBracket) {
        let (inner, after_inner) = parse_expression(tokens, pos + 1)?;
        let after_close = expect_kind(tokens, after_inner, &TokenKind::RBracket, "`]`")?;
        let span = Span::new(base.span().start(), span_at(tokens, after_close - 1).end());
        let member = Expression::new(
            ExpressionKind::Member {
                object: Box::new(base),
                property: MemberProperty::Computed(Box::new(inner)),
                optional: false,
            },
            span,
        );
        parse_member_tail(tokens, after_close, member)
    } else {
        Ok((base, pos))
    }
}

fn parse_call_tail(
    tokens: &[Token],
    pos: usize,
    base: Expression,
) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::LParen) {
        let (args, after_args) = parse_arguments(tokens, pos)?;
        let span = Span::new(base.span().start(), span_at(tokens, after_args - 1).end());
        let call = Expression::new(
            ExpressionKind::Call {
                callee: Box::new(base),
                arguments: args,
                optional: false,
            },
            span,
        );
        parse_call_tail(tokens, after_args, call)
    } else if is_kind(tokens, pos, &TokenKind::Dot) || is_kind(tokens, pos, &TokenKind::LBracket) {
        let (next, after_next) = parse_member_tail(tokens, pos, base)?;
        parse_call_tail(tokens, after_next, next)
    } else if is_kind(tokens, pos, &TokenKind::OptionalChain) {
        parse_optional_chain(tokens, pos, base)
    } else {
        Ok((base, pos))
    }
}

fn parse_optional_chain(
    tokens: &[Token],
    pos: usize,
    base: Expression,
) -> Result<(Expression, usize), Error> {
    let after_qmark = pos + 1;
    if is_kind(tokens, after_qmark, &TokenKind::LParen) {
        let (args, after_args) = parse_arguments(tokens, after_qmark)?;
        let span = Span::new(base.span().start(), span_at(tokens, after_args - 1).end());
        let inner = Expression::new(
            ExpressionKind::Call {
                callee: Box::new(base),
                arguments: args,
                optional: true,
            },
            span,
        );
        let chained = wrap_in_chain(inner);
        parse_call_tail(tokens, after_args, chained)
    } else if is_kind(tokens, after_qmark, &TokenKind::LBracket) {
        let (inner_expr, after_expr) = parse_expression(tokens, after_qmark + 1)?;
        let after_close = expect_kind(tokens, after_expr, &TokenKind::RBracket, "`]`")?;
        let span = Span::new(base.span().start(), span_at(tokens, after_close - 1).end());
        let member = Expression::new(
            ExpressionKind::Member {
                object: Box::new(base),
                property: MemberProperty::Computed(Box::new(inner_expr)),
                optional: true,
            },
            span,
        );
        let chained = wrap_in_chain(member);
        parse_call_tail(tokens, after_close, chained)
    } else {
        let (name, after_name) = expect_identifier_or_keyword(tokens, after_qmark)?;
        let span = Span::new(base.span().start(), span_at(tokens, after_name - 1).end());
        let member = Expression::new(
            ExpressionKind::Member {
                object: Box::new(base),
                property: MemberProperty::Identifier(name),
                optional: true,
            },
            span,
        );
        let chained = wrap_in_chain(member);
        parse_call_tail(tokens, after_name, chained)
    }
}

fn wrap_in_chain(expr: Expression) -> Expression {
    let span = expr.span();
    Expression::new(
        ExpressionKind::Chain {
            expression: Box::new(expr),
        },
        span,
    )
}

fn parse_arguments(tokens: &[Token], pos: usize) -> Result<(Vec<Expression>, usize), Error> {
    let after_open = expect_kind(tokens, pos, &TokenKind::LParen, "`(`")?;
    if is_kind(tokens, after_open, &TokenKind::RParen) {
        Ok((Vec::new(), after_open + 1))
    } else {
        collect_arguments(tokens, after_open, Vec::new())
    }
}

fn collect_arguments(
    tokens: &[Token],
    pos: usize,
    acc: Vec<Expression>,
) -> Result<(Vec<Expression>, usize), Error> {
    let (arg, after_arg) = if is_kind(tokens, pos, &TokenKind::Spread) {
        let span = span_at(tokens, pos);
        let (inner, after_inner) = parse_assignment_expression(tokens, pos + 1)?;
        let full_span = Span::new(span.start(), inner.span().end());
        let spread = Expression::new(
            ExpressionKind::Spread {
                argument: Box::new(inner),
            },
            full_span,
        );
        (spread, after_inner)
    } else {
        parse_assignment_expression(tokens, pos)?
    };
    let extended: Vec<Expression> = acc.into_iter().chain(std::iter::once(arg)).collect();
    if is_kind(tokens, after_arg, &TokenKind::Comma) {
        let next_pos = after_arg + 1;
        if is_kind(tokens, next_pos, &TokenKind::RParen) {
            Ok((extended, next_pos + 1))
        } else {
            collect_arguments(tokens, next_pos, extended)
        }
    } else {
        let after_close = expect_kind(tokens, after_arg, &TokenKind::RParen, "`)` or `,`")?;
        Ok((extended, after_close))
    }
}

fn parse_primary_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    match peek(tokens, pos) {
        Peek::Eof => Err(Error::UnexpectedEof {
            expected: "expression",
        }),
        Peek::Token(tok) => dispatch_primary(tokens, pos, tok, start_span),
    }
}

fn dispatch_primary(
    tokens: &[Token],
    pos: usize,
    tok: &Token,
    span: Span,
) -> Result<(Expression, usize), Error> {
    match tok.value() {
        TokenKind::KwThis => Ok((Expression::new(ExpressionKind::This, span), pos + 1)),
        TokenKind::KwSuper => Ok((Expression::new(ExpressionKind::Super, span), pos + 1)),
        TokenKind::KwTrue => Ok(literal_token(pos, span, Literal::boolean(true))),
        TokenKind::KwFalse => Ok(literal_token(pos, span, Literal::boolean(false))),
        TokenKind::KwNull => Ok(literal_token(pos, span, Literal::null())),
        TokenKind::Number(n) => Ok(literal_token(pos, span, Literal::number(*n))),
        TokenKind::BigInt(digits) => Ok(literal_token(pos, span, Literal::bigint(digits.clone()))),
        TokenKind::String(s) => Ok(literal_token(pos, span, Literal::string(s.clone()))),
        TokenKind::RegExp { pattern, flags } => Ok((
            Expression::new(
                ExpressionKind::Literal(Literal::regex(pattern.clone(), flags.clone())?),
                span,
            ),
            pos + 1,
        )),
        TokenKind::TemplateNoSubstitution(content) => Ok((
            Expression::new(
                ExpressionKind::Template {
                    quasis: vec![content.clone()],
                    expressions: Vec::new(),
                },
                span,
            ),
            pos + 1,
        )),
        TokenKind::TemplateHead(_) => parse_template_with_substitutions(tokens, pos),
        TokenKind::Identifier(name) => Ok((
            Expression::new(
                ExpressionKind::Identifier(ecma_syntax_cat::identifier::Identifier::new(
                    name.clone(),
                )?),
                span,
            ),
            pos + 1,
        )),
        TokenKind::PrivateIdentifier(_) => {
            let (id, after) = expect_private_identifier(tokens, pos)?;
            Ok((
                Expression::new(ExpressionKind::PrivateIdentifier(id), span),
                after,
            ))
        }
        TokenKind::LParen => parse_parenthesised(tokens, pos),
        TokenKind::LBracket => parse_array_literal(tokens, pos),
        TokenKind::LBrace => parse_object_literal(tokens, pos),
        TokenKind::KwFunction => parse_function_expression(tokens, pos, false),
        TokenKind::KwClass => parse_class_expression(tokens, pos),
        TokenKind::KwImport => parse_import_expression(tokens, pos),
        _other => Err(Error::UnexpectedToken {
            at: span,
            expected: "expression",
            found: format!("{}", tok.value()),
        }),
    }
}

fn literal_token(pos: usize, span: Span, literal: Literal) -> (Expression, usize) {
    (
        Expression::new(ExpressionKind::Literal(literal), span),
        pos + 1,
    )
}

fn parse_parenthesised(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (inner, after_inner) = parse_expression(tokens, pos + 1)?;
    let after_close = expect_kind(tokens, after_inner, &TokenKind::RParen, "`)`")?;
    let span = Span::new(
        span_at(tokens, pos).start(),
        span_at(tokens, after_close - 1).end(),
    );
    Ok((
        Expression::new(
            ExpressionKind::Parenthesized {
                expression: Box::new(inner),
            },
            span,
        ),
        after_close,
    ))
}

fn parse_array_literal(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    collect_array_elements(tokens, pos + 1, Vec::new(), start_span)
}

fn collect_array_elements(
    tokens: &[Token],
    pos: usize,
    acc: Vec<Option<Expression>>,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::RBracket) {
        let span = Span::new(start_span.start(), span_at(tokens, pos).end());
        Ok((
            Expression::new(ExpressionKind::Array { elements: acc }, span),
            pos + 1,
        ))
    } else if is_kind(tokens, pos, &TokenKind::Comma) {
        let extended = append_slot(acc, None);
        collect_array_elements(tokens, pos + 1, extended, start_span)
    } else {
        let (element, after_element) = if is_kind(tokens, pos, &TokenKind::Spread) {
            let s = span_at(tokens, pos);
            let (inner, after_inner) = parse_assignment_expression(tokens, pos + 1)?;
            let full = Span::new(s.start(), inner.span().end());
            (
                Expression::new(
                    ExpressionKind::Spread {
                        argument: Box::new(inner),
                    },
                    full,
                ),
                after_inner,
            )
        } else {
            parse_assignment_expression(tokens, pos)?
        };
        let extended = append_slot(acc, Some(element));
        if is_kind(tokens, after_element, &TokenKind::Comma) {
            collect_array_elements(tokens, after_element + 1, extended, start_span)
        } else {
            let after_close = expect_kind(tokens, after_element, &TokenKind::RBracket, "`]`")?;
            let span = Span::new(start_span.start(), span_at(tokens, after_close - 1).end());
            Ok((
                Expression::new(ExpressionKind::Array { elements: extended }, span),
                after_close,
            ))
        }
    }
}

fn append_slot(acc: Vec<Option<Expression>>, slot: Option<Expression>) -> Vec<Option<Expression>> {
    acc.into_iter().chain(std::iter::once(slot)).collect()
}

fn parse_object_literal(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    collect_object_members(tokens, pos + 1, Vec::new(), start_span)
}

fn collect_object_members(
    tokens: &[Token],
    pos: usize,
    acc: Vec<ObjectMember>,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::RBrace) {
        let span = Span::new(start_span.start(), span_at(tokens, pos).end());
        Ok((
            Expression::new(ExpressionKind::Object { properties: acc }, span),
            pos + 1,
        ))
    } else {
        let (member, after_member) = parse_object_member(tokens, pos)?;
        let extended: Vec<ObjectMember> = acc.into_iter().chain(std::iter::once(member)).collect();
        if is_kind(tokens, after_member, &TokenKind::Comma) {
            collect_object_members(tokens, after_member + 1, extended, start_span)
        } else {
            let after_close = expect_kind(tokens, after_member, &TokenKind::RBrace, "`}`")?;
            let span = Span::new(start_span.start(), span_at(tokens, after_close - 1).end());
            Ok((
                Expression::new(
                    ExpressionKind::Object {
                        properties: extended,
                    },
                    span,
                ),
                after_close,
            ))
        }
    }
}

fn parse_object_member(tokens: &[Token], pos: usize) -> Result<(ObjectMember, usize), Error> {
    if is_kind(tokens, pos, &TokenKind::Spread) {
        let (inner, after_inner) = parse_assignment_expression(tokens, pos + 1)?;
        Ok((ObjectMember::Spread { argument: inner }, after_inner))
    } else {
        let (key, computed, after_key) = parse_property_key(tokens, pos)?;
        if is_kind(tokens, after_key, &TokenKind::Colon) {
            let (value, after_value) = parse_assignment_expression(tokens, after_key + 1)?;
            Ok((
                ObjectMember::Property {
                    key,
                    value,
                    kind: ObjectPropertyKind::Init,
                    computed,
                    shorthand: false,
                },
                after_value,
            ))
        } else {
            let shorthand_span = span_at(tokens, pos);
            let value = property_key_to_shorthand_value(&key, shorthand_span)?;
            Ok((
                ObjectMember::Property {
                    key,
                    value,
                    kind: ObjectPropertyKind::Init,
                    computed,
                    shorthand: true,
                },
                after_key,
            ))
        }
    }
}

fn property_key_to_shorthand_value(key: &PropertyKey, span: Span) -> Result<Expression, Error> {
    match key {
        PropertyKey::Identifier(id) => Ok(Expression::new(
            ExpressionKind::Identifier(id.clone()),
            span,
        )),
        PropertyKey::String(_)
        | PropertyKey::Number(_)
        | PropertyKey::Computed(_)
        | PropertyKey::Private(_) => Err(Error::UnexpectedToken {
            at: span,
            expected: "`:` (computed or literal keys require an explicit value)",
            found: "shorthand-key without identifier".to_owned(),
        }),
    }
}

fn parse_property_key(tokens: &[Token], pos: usize) -> Result<(PropertyKey, bool, usize), Error> {
    match peek(tokens, pos) {
        Peek::Eof => Err(Error::UnexpectedEof {
            expected: "property key",
        }),
        Peek::Token(tok) => match tok.value() {
            TokenKind::LBracket => {
                let (inner, after_inner) = parse_assignment_expression(tokens, pos + 1)?;
                let after_close = expect_kind(tokens, after_inner, &TokenKind::RBracket, "`]`")?;
                Ok((PropertyKey::Computed(Box::new(inner)), true, after_close))
            }
            TokenKind::String(s) => Ok((PropertyKey::String(s.clone()), false, pos + 1)),
            TokenKind::Number(n) => Ok((PropertyKey::Number(*n), false, pos + 1)),
            TokenKind::PrivateIdentifier(_) => {
                let (id, after) = expect_private_identifier(tokens, pos)?;
                Ok((PropertyKey::Private(id), false, after))
            }
            _other => {
                let (id, after) = expect_identifier_or_keyword(tokens, pos)?;
                Ok((PropertyKey::Identifier(id), false, after))
            }
        },
    }
}

fn parse_template_with_substitutions(
    tokens: &[Token],
    pos: usize,
) -> Result<(Expression, usize), Error> {
    collect_template(tokens, pos, Vec::new(), Vec::new(), span_at(tokens, pos))
}

fn collect_template(
    tokens: &[Token],
    pos: usize,
    quasis: Vec<String>,
    expressions: Vec<Expression>,
    start_span: Span,
) -> Result<(Expression, usize), Error> {
    match peek(tokens, pos) {
        Peek::Eof => Err(Error::UnexpectedEof {
            expected: "template continuation",
        }),
        Peek::Token(tok) => match tok.value() {
            TokenKind::TemplateHead(content) | TokenKind::TemplateMiddle(content) => {
                let next_quasis: Vec<String> = quasis
                    .into_iter()
                    .chain(std::iter::once(content.clone()))
                    .collect();
                let (expr, after_expr) = parse_expression(tokens, pos + 1)?;
                let next_exprs: Vec<Expression> = expressions
                    .into_iter()
                    .chain(std::iter::once(expr))
                    .collect();
                collect_template(tokens, after_expr, next_quasis, next_exprs, start_span)
            }
            TokenKind::TemplateTail(content) => {
                let final_quasis: Vec<String> = quasis
                    .into_iter()
                    .chain(std::iter::once(content.clone()))
                    .collect();
                let span = Span::new(start_span.start(), span_at(tokens, pos).end());
                Ok((
                    Expression::new(
                        ExpressionKind::Template {
                            quasis: final_quasis,
                            expressions,
                        },
                        span,
                    ),
                    pos + 1,
                ))
            }
            _other => Err(Error::UnexpectedToken {
                at: span_at(tokens, pos),
                expected: "template continuation",
                found: format!("{}", tok.value()),
            }),
        },
    }
}

fn parse_function_expression(
    tokens: &[Token],
    pos: usize,
    is_async: bool,
) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    let after_kw = expect_kind(tokens, pos, &TokenKind::KwFunction, "keyword `function`")?;
    let (is_generator, after_star) = if is_kind(tokens, after_kw, &TokenKind::Star) {
        (true, after_kw + 1)
    } else {
        (false, after_kw)
    };
    let (id, after_id) = if is_kind_identifier(tokens, after_star) {
        let (name, after) = expect_identifier(tokens, after_star)?;
        (Some(name), after)
    } else {
        (None, after_star)
    };
    let (params, after_params) = crate::declaration::parse_formal_parameters(tokens, after_id)?;
    let (body, after_body) = crate::statement::parse_block_body(tokens, after_params)?;
    let span = Span::new(start_span.start(), span_at(tokens, after_body - 1).end());
    let func = ecma_syntax_cat::function::Function::new(id, params, body, is_async, is_generator);
    Ok((
        Expression::new(ExpressionKind::FunctionExpression(Box::new(func)), span),
        after_body,
    ))
}

fn is_kind_identifier(tokens: &[Token], pos: usize) -> bool {
    matches!(
        peek(tokens, pos),
        Peek::Token(t) if matches!(t.value(), TokenKind::Identifier(_))
    )
}

fn parse_class_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let (class, end_pos) = crate::declaration::parse_class(tokens, pos)?;
    let span = span_at(tokens, pos);
    Ok((
        Expression::new(
            ExpressionKind::ClassExpression(Box::new(class)),
            Span::new(span.start(), span_at(tokens, end_pos - 1).end()),
        ),
        end_pos,
    ))
}

fn parse_import_expression(tokens: &[Token], pos: usize) -> Result<(Expression, usize), Error> {
    let start_span = span_at(tokens, pos);
    let after_kw = pos + 1;
    if is_kind(tokens, after_kw, &TokenKind::Dot) {
        let (name, after_name) = expect_identifier_or_keyword(tokens, after_kw + 1)?;
        let meta = ecma_syntax_cat::identifier::Identifier::new("import")?;
        let span = Span::new(start_span.start(), span_at(tokens, after_name - 1).end());
        Ok((
            Expression::new(
                ExpressionKind::MetaProperty {
                    meta,
                    property: name,
                },
                span,
            ),
            after_name,
        ))
    } else {
        let after_open = expect_kind(tokens, after_kw, &TokenKind::LParen, "`(`")?;
        let (source, after_source) = parse_assignment_expression(tokens, after_open)?;
        let (options, after_options) = if is_kind(tokens, after_source, &TokenKind::Comma) {
            let (opts, after_opts) = parse_assignment_expression(tokens, after_source + 1)?;
            (Some(Box::new(opts)), after_opts)
        } else {
            (None, after_source)
        };
        let after_close = expect_kind(tokens, after_options, &TokenKind::RParen, "`)`")?;
        let span = Span::new(start_span.start(), span_at(tokens, after_close - 1).end());
        Ok((
            Expression::new(
                ExpressionKind::ImportExpression {
                    source: Box::new(source),
                    options,
                },
                span,
            ),
            after_close,
        ))
    }
}
