/*
 * Copyright (c) 2026-present, the Ladybird developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

//! Statement parsing: if, for, while, switch, try, etc.

use crate::fast_hash::HashSet;

use crate::ast::*;
use crate::parser::{
    Associativity, ForbiddenTokens, PRECEDENCE_COMMA, Parser, Position, is_strict_reserved_word,
    is_unconditional_reserved_word,
};
use crate::token::TokenType;

/// Used locally during for-statement parsing before converting to `ast::ForInit`.
enum LocalForInit {
    Declaration(Statement),
    Expression(Expression),
}

impl Parser<'_> {
    pub(crate) fn parse_statement(&mut self, allow_labelled_function: bool) -> Statement {
        let start = self.position();
        let tt = self.current_token_type();

        match tt {
            TokenType::CurlyOpen => self.parse_block_statement(),
            TokenType::Return => self.parse_return_statement(),
            TokenType::Var => self.parse_variable_declaration(false),
            TokenType::For => self.parse_for_statement(),
            TokenType::If => self.parse_if_statement(),
            TokenType::Throw => self.parse_throw_statement(),
            TokenType::Try => self.parse_try_statement(),
            TokenType::Break => self.parse_break_statement(),
            TokenType::Continue => self.parse_continue_statement(),
            TokenType::Switch => self.parse_switch_statement(),
            TokenType::Do => self.parse_do_while_statement(),
            TokenType::While => self.parse_while_statement(),
            TokenType::With => {
                if self.flags.strict_mode {
                    self.syntax_error("'with' statement not allowed in strict mode");
                }
                self.parse_with_statement()
            }
            TokenType::Debugger => self.parse_debugger_statement(),
            TokenType::Semicolon => {
                self.consume();
                self.statement(start, StatementKind::Empty)
            }
            TokenType::Slash | TokenType::SlashEquals => {
                let token = self.lexer.force_slash_as_regex();
                self.current_token = token;
                self.parse_expression_statement()
            }
            _ => {
                if self.match_invalid_escaped_keyword() {
                    self.syntax_error("Keyword must not contain escaped characters");
                }
                if self.match_identifier_name()
                    && let Some(labelled) = self.try_parse_labelled_statement(allow_labelled_function)
                {
                    return labelled;
                }
                if self.match_expression() {
                    self.parse_expression_statement()
                } else {
                    self.expected("statement");
                    self.consume();
                    self.statement(start, StatementKind::Empty)
                }
            }
        }
    }

    pub(crate) fn parse_block_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::CurlyOpen);

        self.scope_collector.open_block_scope(None);

        let mut children = Vec::new();

        while !self.match_token(TokenType::CurlyClose) && !self.done() {
            if self.match_declaration() {
                children.push(self.parse_declaration());
            } else {
                children.push(self.parse_statement(true));
            }
        }

        self.consume_token(TokenType::CurlyClose);
        let scope = self.make_scope(children);
        self.scope_collector.set_scope_node(scope);
        self.scope_collector.close_scope();
        self.statement(start, StatementKind::Block(scope))
    }

    fn parse_expression_statement(&mut self) -> Statement {
        let start = self.position();

        if self.match_token(TokenType::Async) {
            let lookahead = self.next_token();
            if lookahead.token_type == TokenType::Function && !lookahead.trivia_has_line_terminator {
                self.syntax_error("Async function declaration not allowed in single-statement context");
            }
        } else if self.match_token(TokenType::Function) || self.match_token(TokenType::Class) {
            let name = self.current_token.token_type.name();
            self.syntax_error(&format!("{name} declaration not allowed in single-statement context"));
        } else if self.match_token(TokenType::Let) && self.next_token().token_type == TokenType::BracketOpen {
            self.syntax_error("let followed by [ is not allowed in single-statement context");
        }

        let expression = self.parse_expression_any();
        self.consume_or_insert_semicolon();
        self.statement(start, StatementKind::Expression(Box::new(expression)))
    }

    // https://tc39.es/ecma262/#sec-return-statement
    // ReturnStatement : `return` [no LineTerminator here] Expression? `;`
    fn parse_return_statement(&mut self) -> Statement {
        let start = self.position();
        if !self.flags.in_function_context {
            self.syntax_error("'return' not allowed outside of a function");
        }
        self.consume_token(TokenType::Return);

        // [no LineTerminator here]: if a line terminator follows `return`,
        // ASI inserts a semicolon and the return has no argument.
        // NB: Don't consume the next token — it may be `;` which should
        // become an EmptyStatement, not be swallowed by the return.
        if self.current_token.trivia_has_line_terminator {
            return self.statement(start, StatementKind::Return(None));
        }
        if self.match_token(TokenType::Semicolon) || self.match_token(TokenType::CurlyClose) || self.done() {
            self.consume_or_insert_semicolon();
            return self.statement(start, StatementKind::Return(None));
        }

        let argument = self.parse_expression_any();
        self.consume_or_insert_semicolon();
        self.statement(start, StatementKind::Return(Some(Box::new(argument))))
    }

    // https://tc39.es/ecma262/#sec-throw-statement
    // ThrowStatement : `throw` [no LineTerminator here] Expression `;`
    fn parse_throw_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::Throw);

        // [no LineTerminator here]: unlike `return`, a line terminator after
        // `throw` is always an error because `throw;` is never valid.
        if self.current_token.trivia_has_line_terminator {
            self.syntax_error("No line break is allowed between 'throw' and its expression");
        }

        let argument = self.parse_expression_any();
        self.consume_or_insert_semicolon();
        self.statement(start, StatementKind::Throw(Box::new(argument)))
    }

    // https://tc39.es/ecma262/#sec-break-statement
    // BreakStatement : `break` [no LineTerminator here] LabelIdentifier? `;`
    fn parse_break_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::Break);

        let label = if self.match_token(TokenType::Semicolon) {
            self.consume();
            None
        } else if !self.current_token.trivia_has_line_terminator
            && !self.match_token(TokenType::CurlyClose)
            && !self.done()
            && self.match_identifier()
        {
            let token = self.consume();
            let label_value = Utf16String::from(self.token_value(&token));

            if !self.labels_in_scope.contains_key(label_value.as_slice()) {
                let label_str = String::from_utf16_lossy(&label_value);
                self.syntax_error(&format!("Label '{label_str}' not found"));
            }

            self.consume_or_insert_semicolon();
            Some(label_value)
        } else {
            self.consume_or_insert_semicolon();
            None
        };

        if label.is_none() && !self.flags.in_break_context {
            self.syntax_error("Unlabeled 'break' not allowed outside of a loop or switch statement");
        }

        self.statement(start, StatementKind::Break { target_label: label })
    }

    // https://tc39.es/ecma262/#sec-continue-statement
    // ContinueStatement : `continue` [no LineTerminator here] LabelIdentifier? `;`
    fn parse_continue_statement(&mut self) -> Statement {
        let start = self.position();
        if !self.flags.in_continue_context {
            self.syntax_error("'continue' not allowed outside of a loop");
        }
        self.consume_token(TokenType::Continue);

        let label = if self.match_token(TokenType::Semicolon) {
            None
        } else if !self.current_token.trivia_has_line_terminator
            && !self.match_token(TokenType::CurlyClose)
            && !self.done()
            && self.match_identifier()
        {
            let label_line = self.current_token.line_number;
            let label_col = self.current_token.line_column;
            let token = self.consume();
            let label_value = Utf16String::from(self.token_value(&token));

            if let Some(entry) = self.labels_in_scope.get_mut(label_value.as_slice()) {
                *entry = Some((label_line, label_col));
            } else {
                let label_str = String::from_utf16_lossy(&label_value);
                self.syntax_error(&format!("Label '{label_str}' not found or invalid"));
            }

            Some(label_value)
        } else {
            None
        };

        self.consume_or_insert_semicolon();

        self.statement(start, StatementKind::Continue { target_label: label })
    }

    fn parse_debugger_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::Debugger);
        self.consume_or_insert_semicolon();
        self.statement(start, StatementKind::Debugger)
    }

    fn parse_if_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::If);
        self.consume_token(TokenType::ParenOpen);
        let predicate = self.parse_expression_any();
        self.consume_token(TokenType::ParenClose);

        // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses
        // Annex B allows FunctionDeclarations (but NOT GeneratorDeclarations) in
        // IfStatement bodies in sloppy mode.
        let consequent = if !self.flags.strict_mode
            && self.match_token(TokenType::Function)
            && self.next_token().token_type != TokenType::Asterisk
        {
            self.parse_function_declaration_as_block_statement(start)
        } else {
            self.parse_statement(false)
        };

        let alternate = if self.match_token(TokenType::Else) {
            self.consume();
            if !self.flags.strict_mode
                && self.match_token(TokenType::Function)
                && self.next_token().token_type != TokenType::Asterisk
            {
                Some(Box::new(self.parse_function_declaration_as_block_statement(start)))
            } else {
                Some(Box::new(self.parse_statement(false)))
            }
        } else {
            None
        };

        self.statement(
            start,
            StatementKind::If(Box::new(IfStatementData {
                test: Box::new(predicate),
                consequent: Box::new(consequent),
                alternate,
            })),
        )
    }

    /// Annex B: Parse a function declaration as if wrapped in a synthetic block.
    /// See https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses
    fn parse_function_declaration_as_block_statement(&mut self, if_start: Position) -> Statement {
        // C++ uses rule_start from the enclosing if-statement for the block position.
        let start = if_start;
        self.scope_collector.open_block_scope(None);
        let declaration = self.parse_function_declaration();
        let scope = self.make_scope(vec![declaration]);
        self.scope_collector.set_scope_node(scope);
        self.scope_collector.close_scope();
        self.statement(start, StatementKind::Block(scope))
    }

    /// Parse a statement in a loop body context (break and continue allowed).
    fn parse_loop_body(&mut self) -> Statement {
        let break_before = self.flags.in_break_context;
        let continue_before = self.flags.in_continue_context;
        self.flags.in_break_context = true;
        self.flags.in_continue_context = true;
        let body = self.parse_statement(false);
        self.flags.in_break_context = break_before;
        self.flags.in_continue_context = continue_before;
        body
    }

    fn parse_while_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::While);
        self.consume_token(TokenType::ParenOpen);
        let test = self.parse_expression_any();
        self.consume_token(TokenType::ParenClose);

        let body = self.parse_loop_body();

        self.statement(
            start,
            StatementKind::While(Box::new(WhileStatementData {
                test: Box::new(test),
                body: Box::new(body),
            })),
        )
    }

    fn parse_do_while_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::Do);

        let body = self.parse_loop_body();

        self.consume_token(TokenType::While);
        self.consume_token(TokenType::ParenOpen);
        let test = self.parse_expression_any();
        self.consume_token(TokenType::ParenClose);

        // Since ES 2015 a missing semicolon is inserted here, despite
        // the regular ASI rules not applying.
        self.eat(TokenType::Semicolon);

        self.statement(
            start,
            StatementKind::DoWhile(Box::new(WhileStatementData {
                test: Box::new(test),
                body: Box::new(body),
            })),
        )
    }

    // https://tc39.es/ecma262/#sec-for-statement
    // https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
    fn parse_for_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::For);

        // Open for-loop scope (for let/const/using declarations).
        // scope_data set after the for-loop body is parsed.
        self.scope_collector.open_for_loop_scope(None);

        let is_await = if self.match_token(TokenType::Await) {
            if !self.flags.await_expression_is_valid {
                self.syntax_error("for-await-of not allowed outside of async context");
            }
            self.consume();
            true
        } else {
            false
        };

        self.consume_token(TokenType::ParenOpen);

        if self.match_token(TokenType::Semicolon) && !is_await {
            self.consume();
            let result = self.parse_standard_for_loop(start, None);
            return self.close_for_loop_scope(start, result);
        }

        let init_start = self.position();
        let init_starts_with_async_keyword = self.match_token(TokenType::Async);
        let is_var_init = self.match_token(TokenType::Var);
        let is_using = self.match_for_using_declaration();
        let is_let = self.match_token(TokenType::Let) && (self.flags.strict_mode || self.try_match_let_declaration());
        let is_declaration = is_var_init || is_using || is_let || self.match_token(TokenType::Const);
        let init = if is_using {
            LocalForInit::Declaration(self.parse_using_declaration(true))
        } else if is_declaration {
            LocalForInit::Declaration(self.parse_variable_declaration(true))
        } else {
            let forbidden = ForbiddenTokens::with_in();
            LocalForInit::Expression(self.parse_expression(PRECEDENCE_COMMA, Associativity::Right, forbidden))
        };

        // Check for in
        // https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
        // It is a Syntax Error if IsDestructuring of ForBinding is false and
        // Initializer is present. (Only a single, non-initialized ForBinding
        // is allowed; except in Annex B sloppy var with one declarator.)
        if self.match_token(TokenType::In) && !is_await {
            // C++ captures ForInStatement position at the `in` keyword.
            let forin_start = self.position();
            if is_using {
                self.syntax_error("Using declaration not allowed in for-in loop");
            } else if is_declaration {
                if self.for_loop_declaration_count > 1 {
                    self.syntax_error("Multiple declarations not allowed in for..in/of");
                }
                if self.for_loop_declaration_has_init {
                    // https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
                    // Annex B: In sloppy mode, a single `var` with a simple
                    // BindingIdentifier and an initializer is permitted.
                    // Binding patterns with initializers are never allowed.
                    if !self.for_loop_declaration_is_var
                        || self.for_loop_declaration_count != 1
                        || self.flags.strict_mode
                        || self.for_loop_declaration_is_pattern
                    {
                        self.syntax_error("Variable initializer not allowed in for..in/of");
                    }
                }
            } else {
                self.validate_for_in_of_lhs(&init);
            }
            self.consume();
            let rhs = self.parse_expression_any();
            self.consume_token(TokenType::ParenClose);

            let body = self.parse_loop_body();

            let lhs = self.synthesize_for_in_of_lhs(init, init_start);
            let result = self.statement(
                forin_start,
                StatementKind::ForInOf(Box::new(ForInOfStatementData {
                    kind: ForInOfKind::ForIn,
                    lhs,
                    rhs: Box::new(rhs),
                    body: Box::new(body),
                })),
            );
            return self.close_for_loop_scope(start, result);
        }

        // Check for of (keyword must not contain escapes)
        if self.match_identifier_name() {
            let value = self.token_original_value(&self.current_token);
            if value == utf16!("of") {
                // C++ captures ForOfStatement position at the `of` keyword.
                let forof_start = self.position();
                if is_declaration {
                    if self.for_loop_declaration_count > 1 {
                        self.syntax_error("Multiple declarations not allowed in for..in/of");
                    }
                    if self.for_loop_declaration_has_init {
                        self.syntax_error("Variable initializer not allowed in for..of");
                    }
                } else {
                    self.validate_for_in_of_lhs(&init);
                    // https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
                    // The ordinary for-of grammar excludes `async of`, while the for-await-of grammar only excludes
                    // `let`, so escaped identifiers (e.g. `\u0061sync`) and `for await (async of ...)` are valid here.
                    if !is_await
                        && init_starts_with_async_keyword
                        && let LocalForInit::Expression(ref expression) = init
                        && let ExpressionKind::Identifier(ident) = expression.inner
                        && self.arena.name_of(ident).as_slice() == utf16!("async")
                    {
                        self.syntax_error("for-of statement may not use 'async' as the left-hand side");
                    }
                    // https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
                    if let LocalForInit::Expression(ref expression) = init
                        && let ExpressionKind::Member(ref data) = expression.inner
                        && let ExpressionKind::Identifier(ident) = data.object.inner
                        && self.arena.name_of(ident).as_slice() == utf16!("let")
                    {
                        self.syntax_error("For of statement may not start with let.");
                    }
                }
                self.consume();
                let rhs = self.parse_assignment_expression();
                self.consume_token(TokenType::ParenClose);

                let body = self.parse_loop_body();

                let lhs = self.synthesize_for_in_of_lhs(init, init_start);
                let for_of_kind = if is_await {
                    ForInOfKind::ForAwaitOf
                } else {
                    ForInOfKind::ForOf
                };
                let result = self.statement(
                    forof_start,
                    StatementKind::ForInOf(Box::new(ForInOfStatementData {
                        kind: for_of_kind,
                        lhs,
                        rhs: Box::new(rhs),
                        body: Box::new(body),
                    })),
                );
                return self.close_for_loop_scope(start, result);
            }
        }

        // Standard for loop — const requires initializer.
        if let LocalForInit::Declaration(ref declaration) = init
            && let StatementKind::VariableDeclaration(ref vd) = declaration.inner
            && vd.kind == DeclarationKind::Const
        {
            for d in &vd.declarations {
                if d.init.is_none() {
                    self.syntax_error("Missing initializer in const declaration");
                }
            }
        }
        if is_await {
            // https://tc39.es/ecma262/#prod-ForInOfStatement
            // `for await` only has `of` productions.
            self.syntax_error("for-await-of statement must use 'of'");
        }
        self.consume_token(TokenType::Semicolon);
        let for_init = match init {
            LocalForInit::Declaration(declaration) => Some(ForInit::Declaration(Box::new(declaration))),
            LocalForInit::Expression(expression) => Some(ForInit::Expression(Box::new(expression))),
        };
        let result = self.parse_standard_for_loop(start, for_init);
        self.close_for_loop_scope(start, result)
    }

    /// Close the for-loop scope and wrap the for-loop statement in a Block
    /// with scope data.
    fn close_for_loop_scope(&mut self, start: Position, inner: Statement) -> Statement {
        let scope = self.make_scope(vec![inner]);
        self.scope_collector.set_scope_node(scope);
        self.scope_collector.close_scope();
        self.statement(start, StatementKind::Block(scope))
    }

    fn parse_standard_for_loop(&mut self, start: Position, init: Option<ForInit>) -> Statement {
        let test = if self.match_token(TokenType::Semicolon) {
            None
        } else {
            Some(Box::new(self.parse_expression_any()))
        };
        self.consume_token(TokenType::Semicolon);

        let update = if self.match_token(TokenType::ParenClose) {
            None
        } else {
            Some(Box::new(self.parse_expression_any()))
        };
        self.consume_token(TokenType::ParenClose);

        let body = self.parse_loop_body();

        self.statement(
            start,
            StatementKind::For(Box::new(ForStatementData {
                init,
                test,
                update,
                body: Box::new(body),
            })),
        )
    }

    // https://tc39.es/ecma262/#sec-with-statement
    // NOTE: The with statement is forbidden in strict mode code.
    fn parse_with_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::With);
        self.consume_token(TokenType::ParenOpen);
        let object = self.parse_expression_any();
        self.consume_token(TokenType::ParenClose);
        self.scope_collector.open_with_scope(None);
        let body = self.parse_statement(false);
        self.scope_collector.close_scope();
        self.statement(
            start,
            StatementKind::With(Box::new(WithStatementData {
                object: Box::new(object),
                body: Box::new(body),
            })),
        )
    }

    // https://tc39.es/ecma262/#sec-switch-statement
    // All case clauses in a switch statement share a single block scope.
    fn parse_switch_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::Switch);
        self.consume_token(TokenType::ParenOpen);
        let discriminant = self.parse_expression_any();
        self.consume_token(TokenType::ParenClose);

        self.consume_token(TokenType::CurlyOpen);

        self.scope_collector.open_block_scope(None);

        let break_before = self.flags.in_break_context;
        self.flags.in_break_context = true;

        let mut cases = Vec::new();
        let mut has_default = false;
        while !self.match_token(TokenType::CurlyClose) && !self.done() {
            let case = self.parse_switch_case();
            if case.test.is_none() {
                if has_default {
                    self.syntax_error("Multiple 'default' clauses in switch statement");
                }
                has_default = true;
            }
            cases.push(case);
        }

        self.flags.in_break_context = break_before;

        self.consume_token(TokenType::CurlyClose);

        let scope = self.make_empty_scope();
        self.scope_collector.set_scope_node(scope);
        self.scope_collector.close_scope();

        self.statement(
            start,
            StatementKind::Switch(Box::new(SwitchStatementData {
                scope,
                discriminant: Box::new(discriminant),
                cases,
            })),
        )
    }

    fn parse_switch_case(&mut self) -> SwitchCase {
        let start = self.position();
        let test = if self.match_token(TokenType::Case) {
            self.consume();
            Some(self.parse_expression_any())
        } else if self.match_token(TokenType::Default) {
            self.consume();
            None
        } else {
            self.expected("'case' or 'default'");
            None
        };

        self.consume_token(TokenType::Colon);

        let mut children = Vec::new();
        while !self.match_token(TokenType::CurlyClose)
            && !self.match_token(TokenType::Case)
            && !self.match_token(TokenType::Default)
            && !self.done()
        {
            if self.match_declaration() {
                children.push(self.parse_declaration());
            } else {
                children.push(self.parse_statement(true));
            }
        }

        SwitchCase {
            range: self.range_from(start),
            scope: self.make_scope(children),
            test,
        }
    }

    // https://tc39.es/ecma262/#sec-try-statement
    // TryStatement :
    //   `try` Block Catch
    //   `try` Block Finally
    //   `try` Block Catch Finally
    fn parse_try_statement(&mut self) -> Statement {
        let start = self.position();
        self.consume_token(TokenType::Try);

        let block = self.parse_block_statement();

        let handler = if self.match_token(TokenType::Catch) {
            Some(self.parse_catch_clause())
        } else {
            None
        };

        let finalizer = if self.match_token(TokenType::Finally) {
            self.consume();
            Some(Box::new(self.parse_block_statement()))
        } else {
            None
        };

        if handler.is_none() && finalizer.is_none() {
            self.syntax_error("try statement must have a catch or finally clause");
        }

        self.statement(
            start,
            StatementKind::Try(Box::new(TryStatementData {
                block: Box::new(block),
                handler,
                finalizer,
            })),
        )
    }

    // https://tc39.es/ecma262/#sec-try-statement
    // Catch : `catch` `(` CatchParameter `)` Block
    //       | `catch` Block
    // The catch parameter creates its own scope that wraps the block body.
    fn parse_catch_clause(&mut self) -> CatchClause {
        let start = self.position();
        self.consume_token(TokenType::Catch);

        self.scope_collector.open_catch_scope();

        let parameter = if self.match_token(TokenType::ParenOpen) {
            self.consume();
            let parameter = if self.match_token(TokenType::CurlyOpen) || self.match_token(TokenType::BracketOpen) {
                self.pattern_bound_names.clear();
                let pattern = self.parse_binding_pattern();
                // Materialize Utf16Strings for slice-based scope-collector calls.
                let names_to_check: Vec<Utf16String> = self
                    .pattern_bound_names
                    .iter()
                    .map(|(n, _)| self.arena.strings[*n].clone())
                    .collect();
                // https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
                // It is a Syntax Error if BoundNames of CatchParameter
                // contains any duplicate elements.
                {
                    let mut seen: HashSet<&[u16]> = HashSet::default();
                    for name in &names_to_check {
                        if !seen.insert(name.as_slice()) {
                            let name_str = String::from_utf16_lossy(name);
                            self.syntax_error(&format!("Duplicate binding '{name_str}' in catch parameter"));
                        }
                    }
                }
                for name in &names_to_check {
                    self.check_identifier_name_for_assignment_validity(name.as_slice(), false);
                }
                let bound_names: Vec<&[u16]> = names_to_check.iter().map(|n| n.as_slice()).collect();
                self.scope_collector.add_catch_parameter_pattern(&bound_names);
                // Register each binding pattern identifier for scope analysis
                // so they get is_local() annotations (matching variable declarations).
                let pattern_bound_ids: Vec<IdentifierId> = self.pattern_bound_names.iter().map(|(_, id)| *id).collect();
                let Self {
                    scope_collector, arena, ..
                } = self;
                for id in pattern_bound_ids {
                    scope_collector.register_identifier(
                        id,
                        None,
                        &mut arena.identifiers,
                        &arena.strings,
                        &mut arena.scopes,
                    );
                }
                Some(CatchBinding::BindingPattern(pattern))
            } else if self.match_identifier() {
                let parameter_start = self.position();
                let token = self.consume();
                let value = self.token_value(&token).to_vec();
                self.check_identifier_name_for_assignment_validity(&value, false);
                let name = self.token_identifier_name(&token);
                let id = self
                    .arena
                    .identifiers
                    .insert(Identifier::new(self.range_from(parameter_start), name));
                let Self {
                    scope_collector, arena, ..
                } = self;
                scope_collector.register_identifier(
                    id,
                    None,
                    &mut arena.identifiers,
                    &arena.strings,
                    &mut arena.scopes,
                );
                scope_collector.add_catch_parameter_identifier(&value, id);
                Some(CatchBinding::Identifier(id))
            } else {
                self.expected("catch parameter");
                None
            };
            self.consume_token(TokenType::ParenClose);
            parameter
        } else {
            None
        };

        // Collect catch parameter names (as StringIds) for post-body validation.
        let catch_names: Vec<StringId> = match &parameter {
            Some(CatchBinding::Identifier(id)) => vec![self.arena.identifiers[*id].name],
            Some(CatchBinding::BindingPattern(_)) => self.pattern_bound_names.iter().map(|(n, _)| *n).collect(),
            None => Vec::new(),
        };

        let body = self.parse_block_statement();

        // https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
        // It is a Syntax Error if any element of the BoundNames of
        // CatchParameter also occurs in the LexicallyDeclaredNames of Block.
        if !catch_names.is_empty()
            && let StatementKind::Block(scope_id) = body.inner
        {
            for child in &self.arena.scopes[scope_id].children.clone() {
                match &child.inner {
                    StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => {
                        for decl in &vd.declarations {
                            if let VariableDeclaratorTarget::Identifier(id) = decl.target {
                                let id_name = self.arena.identifiers[id].name;
                                for cn in &catch_names {
                                    if *cn == id_name {
                                        let n = String::from_utf16_lossy(self.arena.strings[*cn].as_slice());
                                        self.syntax_error(&format!(
                                            "Identifier '{n}' already declared as catch parameter"
                                        ));
                                    }
                                }
                            }
                        }
                    }
                    StatementKind::FunctionDeclaration(fd) if fd.name.is_some() => {
                        let id = fd.name.unwrap();
                        let id_name = self.arena.identifiers[id].name;
                        for cn in &catch_names {
                            if *cn == id_name {
                                let n = String::from_utf16_lossy(self.arena.strings[*cn].as_slice());
                                self.syntax_error(&format!("Identifier '{n}' already declared as catch parameter"));
                            }
                        }
                    }
                    StatementKind::ClassDeclaration(data) => {
                        if let Some(id) = data.name {
                            let id_name = self.arena.identifiers[id].name;
                            for cn in &catch_names {
                                if *cn == id_name {
                                    let n = String::from_utf16_lossy(self.arena.strings[*cn].as_slice());
                                    self.syntax_error(&format!("Identifier '{n}' already declared as catch parameter"));
                                }
                            }
                        }
                    }
                    _ => {}
                }
            }
        }

        self.scope_collector.close_scope();

        CatchClause {
            range: self.range_from(start),
            parameter,
            body: Box::new(body),
        }
    }

    // https://tc39.es/ecma262/#sec-labelled-statements
    // It is a Syntax Error if any source text is matched by this production
    // and a `continue` statement with label targets that production.
    // (i.e., `continue` with a label can only target an iteration statement.)
    fn try_parse_labelled_statement(&mut self, allow_labelled_function: bool) -> Option<Statement> {
        let start = self.position();

        if !self.match_identifier_name() {
            return None;
        }

        self.save_state();
        let token = self.consume();
        let label = Utf16String::from(self.token_value(&token));

        if !self.match_token(TokenType::Colon) {
            self.load_state();
            return None;
        }
        self.discard_saved_state();
        self.consume(); // consume :

        // https://tc39.es/ecma262/#sec-identifiers
        // LabelIdentifier[Yield, Await] : Identifier
        // Identifier : IdentifierName but not ReservedWord
        if is_unconditional_reserved_word(&label) {
            self.syntax_error("Reserved word cannot be used as a label");
        }

        if self.flags.strict_mode && (label == utf16!("let") || is_strict_reserved_word(&label)) {
            self.syntax_error("Strict mode reserved word is not allowed in label");
        }
        if self.flags.in_generator_function_context && label == utf16!("yield") {
            self.syntax_error("'yield' label is not allowed in generator function context");
        }
        if (self.flags.await_expression_is_valid
            || self.flags.in_class_static_init_block
            || self.program_type == crate::parser::ProgramType::Module)
            && label == utf16!("await")
        {
            self.syntax_error("'await' is not allowed as a label in this context");
        }

        if self.labels_in_scope.contains_key(label.as_slice()) {
            let label_str = String::from_utf16_lossy(&label);
            self.syntax_error(&format!("Label '{label_str}' has already been declared"));
        }

        if self.match_token(TokenType::Function) && (!allow_labelled_function || self.flags.strict_mode) {
            self.syntax_error("Not allowed to declare a function here");
        }
        if self.match_token(TokenType::Async) {
            let next = self.next_token();
            if next.token_type == TokenType::Function && !next.trivia_has_line_terminator {
                self.syntax_error("Async functions cannot be defined in labelled statements");
            }
        }

        self.labels_in_scope.insert(label.clone(), None);

        let break_before = self.flags.in_break_context;
        self.flags.in_break_context = true;

        let body_starts_iteration = self.match_iteration_start();
        self.last_inner_label_is_iteration = false;
        let body = if self.match_token(TokenType::Function) {
            let function_start = self.position();
            let fn_decl = self.parse_function_declaration();
            if let StatementKind::FunctionDeclaration(ref fd) = fn_decl.inner {
                if let Some(name) = fd.name {
                    let name = self.arena.name_of(name).clone();
                    self.scope_collector.check_labelled_function_declaration(
                        name.as_slice(),
                        function_start.line,
                        function_start.column,
                    );
                }
                match fd.kind {
                    FunctionKind::Generator | FunctionKind::AsyncGenerator => {
                        self.syntax_error("Generator functions cannot be defined in labelled statements");
                    }
                    FunctionKind::Async => {
                        self.syntax_error("Async functions cannot be defined in labelled statements");
                    }
                    _ => {}
                }
            }
            fn_decl
        } else {
            self.parse_statement(allow_labelled_function)
        };

        let is_iteration = body_starts_iteration || self.last_inner_label_is_iteration;
        if !is_iteration && let Some(Some((line, col))) = self.labels_in_scope.get(label.as_slice()) {
            self.syntax_error_at(
                "labelled continue statement cannot use non iterating statement",
                *line,
                *col,
            );
        }

        self.labels_in_scope.remove(label.as_slice());
        self.flags.in_break_context = break_before;
        self.last_inner_label_is_iteration = is_iteration;

        Some(self.statement(
            start,
            StatementKind::Labelled(Box::new(LabelledStatementData {
                label,
                item: Box::new(body),
            })),
        ))
    }

    fn match_for_using_declaration(&mut self) -> bool {
        if !self.match_token(TokenType::Identifier) {
            return false;
        }
        if self.token_value(&self.current_token) != utf16!("using") {
            return false;
        }
        let next = self.next_token();
        if next.trivia_has_line_terminator {
            return false;
        }
        if next.token_type == TokenType::Identifier {
            let next_val = self.token_original_value(&next);
            if next_val == utf16!("of") {
                return false;
            }
        }
        // Must be an actual identifier, not just an identifier-name keyword
        // like `in`. This matches C++ token_is_identifier().
        self.token_is_identifier(&next)
    }

    /// Validate that an expression-form LHS is valid for for-in/for-of.
    fn validate_for_in_of_lhs(&mut self, init: &LocalForInit) {
        if let LocalForInit::Expression(ref expression) = *init {
            match &expression.inner {
                // NewExpression is never a valid assignment target.
                ExpressionKind::New(_) => {
                    self.syntax_error("Invalid left-hand side in for-loop");
                }
                // CallExpression is a valid assignment target only in non-strict mode (web compat).
                ExpressionKind::Call(_) if self.flags.strict_mode => {
                    self.syntax_error("Invalid left-hand side in for-loop");
                }
                ExpressionKind::Identifier(_)
                | ExpressionKind::Member(_)
                | ExpressionKind::Call(_)
                | ExpressionKind::Object(_)
                | ExpressionKind::Array(_) => {}
                _ => {
                    self.syntax_error("Invalid left-hand side in for-loop");
                }
            }
        }
    }

    /// Convert a `LocalForInit` into a `ForInOfLhs`, synthesizing a binding
    /// pattern when the LHS is an array or object expression.
    fn synthesize_for_in_of_lhs(&mut self, init: LocalForInit, init_start: Position) -> ForInOfLhs {
        match init {
            LocalForInit::Declaration(declaration) => ForInOfLhs::Declaration(Box::new(declaration)),
            LocalForInit::Expression(expression) => {
                if Self::is_array_expression(&expression) || Self::is_object_expression(&expression) {
                    let pattern = self.synthesize_binding_pattern(init_start);

                    let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect();
                    for (name, _id) in &bound_names {
                        let name_str = self.arena.strings[*name].clone();
                        self.check_identifier_name_for_assignment_validity(name_str.as_slice(), false);
                    }
                    let Self {
                        scope_collector, arena, ..
                    } = self;
                    for (_name, id) in &bound_names {
                        scope_collector.register_identifier(
                            *id,
                            None,
                            &mut arena.identifiers,
                            &arena.strings,
                            &mut arena.scopes,
                        );
                    }
                    ForInOfLhs::Pattern(pattern)
                } else {
                    ForInOfLhs::Expression(Box::new(expression))
                }
            }
        }
    }
}
