mod array_element_list;
mod array_expression;
mod array_pattern;
mod arrow_function_expression;
mod as_or_satisfies_expression;
mod assignment_pattern_property_list;
mod binary_like_expression;
mod binding_property_list;
mod block_statement;
mod call_like_expression;
mod class;
mod decorators;
mod export_declarations;
mod fragment;
mod function;
mod function_type;
mod import_declaration;
mod intersection_type;
mod jsx;
mod mapped_type;
mod member_expression;
mod object_like;
mod object_pattern_like;
mod parameters;
mod program;
mod return_or_throw_statement;
mod semicolon;
mod sequence_expression;
mod switch_statement;
mod template;
mod try_statement;
mod tuple_type;
mod type_parameters;
mod union_type;
mod variable_declaration;

pub use arrow_function_expression::{
    FormatJsArrowFunctionExpression, FormatJsArrowFunctionExpressionOptions,
};
pub use binary_like_expression::{BinaryLikeExpression, should_flatten};
pub use fragment::{FormatFunctionParams, FormatTypeParameters};
pub use function::FormatFunctionOptions;

use cow_utils::CowUtils;

use oxc_allocator::ArenaVec;
use oxc_ast::ast::*;
use oxc_formatter_core::arena_cow_str;
use oxc_span::GetSpan;

use crate::{
    ast_nodes::{AstNode, AstNodes},
    best_fitting, format_args,
    formatter::{
        Format, JsFormatter,
        prelude::*,
        separated::FormatSeparatedIter,
        token::number::{format_number_token, format_trimmed_number, is_simple_number},
        trivia::{
            DanglingIndentMode, FormatDanglingComments, FormatLeadingComments,
            FormatTrailingComments,
        },
    },
    options::{FormatTrailingCommas, Semicolons, TrailingSeparator},
    parentheses::NeedsParentheses,
    print::parameters::can_avoid_parentheses,
    utils::{
        array::write_array_node,
        assignment_like::AssignmentLike,
        conditional::ConditionalLike,
        expression::ExpressionLeftSide,
        format_node_without_trailing_comments::FormatNodeWithoutTrailingComments,
        is_keyword_property_key,
        object::{
            format_property_key, is_quoted_new_method_signature, should_preserve_quote,
            should_preserve_quote_for_enum_member,
        },
        statement_body::FormatStatementBody,
        string::{FormatLiteralStringToken, StringLiteralParentKind},
        suppressed::FormatSuppressedNode,
        tailwindcss::{tailwind_context_for_string_literal, write_tailwind_string_literal},
        typecast::classify_type_cast,
    },
    write,
};

use self::{
    array_expression::FormatArrayExpression,
    block_statement::is_empty_block,
    class::format_grouped_parameters_with_return_type_for_method,
    object_like::ObjectLike,
    object_pattern_like::ObjectPatternLike,
    program::FormatStatementsWithImports,
    return_or_throw_statement::FormatAdjacentArgument,
    semicolon::{
        FormatContentWithSemicolon, OptionalSemicolon, keeps_trailing_comment_inside_parens,
        write_trailing_comments_inside_parens,
    },
    type_parameters::{FormatTSTypeParameters, FormatTSTypeParametersOptions},
};

pub trait FormatWrite<'ast, T = ()> {
    fn write(&self, f: &mut JsFormatter<'_, 'ast>);
    fn write_with_options(&self, _options: T, _f: &mut JsFormatter<'_, 'ast>) {
        unreachable!("Please implement it first.");
    }
    /// Formats the node when it is suppressed (`oxfmt-ignore` / `prettier-ignore`).
    /// Only called for statements whose ignored range must exclude the trailing semicolon,
    /// so the formatter prints its own terminator, like Prettier;
    /// every other node prints its whole span verbatim in the generated `fmt`.
    fn write_suppressed(&self, _f: &mut JsFormatter<'_, 'ast>) {
        unreachable!(
            "Implement `write_suppressed` for every node listed in \
             `AST_NODE_WITH_CUSTOM_SUPPRESSED_FORMATTING` (tasks/ast_tools)."
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, IdentifierName<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let text = text_without_whitespace(self.name().as_str());
        let is_property_key_parent = matches!(
            self.parent(),
            AstNodes::ObjectProperty(_)
                | AstNodes::TSPropertySignature(_)
                | AstNodes::TSMethodSignature(_)
                | AstNodes::MethodDefinition(_)
                | AstNodes::PropertyDefinition(_)
                | AstNodes::AccessorProperty(_)
                | AstNodes::ImportAttribute(_)
                | AstNodes::TSEnumMember(_)
        );
        if is_property_key_parent && f.context().is_quote_needed() {
            let quote_str = f.options().quote_style.as_str();
            write!(f, [quote_str, text, quote_str]);
        } else {
            write!(f, text);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, IdentifierReference<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, text_without_whitespace(self.name().as_str()));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BindingIdentifier<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, text_without_whitespace(self.name().as_str()));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, LabelIdentifier<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, text_without_whitespace(self.name().as_str()));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ThisExpression> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "this");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ArrayExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        FormatArrayExpression::new(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, Elision> {
    fn write(&self, _f: &mut JsFormatter<'_, 'a>) {}
}

impl<'a> FormatWrite<'a> for AstNode<'a, ObjectExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if f.options().quote_properties.is_consistent() {
            let quote_needed = self.properties.iter().any(|kind| {
                kind.as_property().is_some_and(|property| should_preserve_quote(&property.key, f))
            });
            f.context_mut().push_quote_needed(quote_needed);
        }

        ObjectLike::ObjectExpression(self).fmt(f);

        if f.options().quote_properties.is_consistent() {
            f.context_mut().pop_quote_needed();
        }
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, ArenaVec<'a, ObjectPropertyKind<'a>>> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        let trailing_separator = FormatTrailingCommas::ES5.trailing_separator(f.options());
        f.join_nodes_with_soft_line().entries_with_trailing_separator(
            self.iter(),
            ",",
            trailing_separator,
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ObjectProperty<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if f.comments().has_trailing_suppression_comment(self.span().end) {
            write!(f, [FormatSuppressedNode(self.span())]);
            return;
        }

        let is_accessor = match &self.kind() {
            PropertyKind::Init => false,
            PropertyKind::Get => {
                write!(f, ["get", space()]);
                true
            }
            PropertyKind::Set => {
                write!(f, ["set", space()]);
                true
            }
        };

        if self.method || is_accessor {
            let AstNodes::Function(value) = self.value().as_ast_nodes() else {
                unreachable!(
                    "The `value` always be a function node if `method` or `accessor` is true"
                )
            };

            if value.r#async() {
                write!(f, ["async", space()]);
            }
            if value.generator() {
                write!(f, "*");
            }
            format_property_key(self.key(), self.computed, f);

            format_grouped_parameters_with_return_type_for_method(
                value.type_parameters(),
                value.this_param.as_deref(),
                value.params(),
                value.return_type(),
                f,
            );

            if let Some(body) = &value.body() {
                write!(f, [space(), body]);
            }
        } else {
            write!(f, AssignmentLike::ObjectProperty(self));
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, MetaProperty<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.meta(), ".", self.property()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, SpreadElement<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["...", self.argument()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, UpdateExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.prefix() {
            write!(f, self.operator().as_str());
        }
        write!(f, self.argument());
        if !self.prefix() {
            write!(f, self.operator().as_str());
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, UnaryExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, self.operator().as_str());
        if self.operator().is_keyword() {
            write!(f, space());
        }
        let Span { start, end, .. } = self.argument.span();
        if f.comments().has_comment_before(start)
            || f.comments().has_comment_in_range(end, self.span().end)
        {
            write!(
                f,
                [group(&format_args!(token("("), soft_block_indent(self.argument()), token(")")))]
            );
        } else {
            write!(f, self.argument());
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BinaryExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        BinaryLikeExpression::BinaryExpression(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, PrivateInExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.left(), space(), "in", space(), self.right()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, LogicalExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        BinaryLikeExpression::LogicalExpression(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ConditionalExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        ConditionalLike::ConditionalExpression(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        AssignmentLike::AssignmentExpression(self).fmt(f);
        write_trailing_comments_inside_parens(f, self.parent(), self.span().end, false);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ArrayAssignmentTarget<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "[");

        if self.elements.is_empty() && self.rest.is_none() {
            write!(f, [format_dangling_comments(self.span()).with_soft_block_indent()]);
        } else {
            write!(
                f,
                group(&soft_block_indent(&format_with(|f| {
                    let has_element = !self.elements.is_empty();
                    if has_element {
                        write_array_node(
                            self.elements.len() + usize::from(self.rest.is_some()),
                            self.elements().iter().map(AstNode::as_ref),
                            f,
                        );
                    }
                    if let Some(rest) = self.rest() {
                        write!(f, [has_element.then_some(soft_line_break_or_space()), rest]);
                    }
                })))
            );
        }

        write!(f, "]");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ObjectAssignmentTarget<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        ObjectPatternLike::ObjectAssignmentTarget(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentTargetRest<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["...", self.target()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentTargetWithDefault<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.binding(), space(), "=", space(), self.init()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentTargetPropertyIdentifier<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, self.binding());
        if let Some(expr) = &self.init() {
            write!(f, [space(), "=", space(), expr]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentTargetPropertyProperty<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.computed() {
            write!(f, "[");
        }
        write!(f, self.name());
        if self.computed() {
            write!(f, "]");
        }
        write!(f, [":", space(), self.binding()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, Super> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "super");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AwaitExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let format_inner = format_with(|f| write!(f, ["await", space(), self.argument()]));

        let is_callee_or_object = match self.parent() {
            AstNodes::StaticMemberExpression(_) => true,
            AstNodes::ComputedMemberExpression(member) => member.object.span() == self.span(),
            _ => self.parent().is_call_like_callee_span(self.span),
        };

        if is_callee_or_object {
            let mut await_expression = None;
            for ancestor in self.ancestors().skip(1) {
                match ancestor {
                    AstNodes::BlockStatement(_)
                    | AstNodes::FunctionBody(_)
                    | AstNodes::SwitchCase(_)
                    | AstNodes::Program(_)
                    | AstNodes::TSModuleBlock(_) => break,
                    AstNodes::AwaitExpression(expr) => await_expression = Some(expr),
                    _ => {}
                }
            }

            let indented = format_with(|f| write!(f, [soft_block_indent(&format_inner)]));

            return if let Some(expr) = await_expression.take() {
                if !expr.needs_parentheses(f)
                    && ExpressionLeftSide::leftmost(expr.argument()).span() != self.span()
                {
                    return write!(f, [group(&indented)]);
                }

                write!(f, [indented]);
            } else {
                write!(f, [group(&indented)]);
            };
        }

        write!(f, [format_inner]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ChainExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        self.expression().fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ParenthesizedExpression<'a>> {
    fn write(&self, _f: &mut JsFormatter<'_, 'a>) {
        unreachable!("No `ParenthesizedExpression` as we disabled `preserve_parens` in the parser")
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, EmptyStatement> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if matches!(
            self.parent(),
            AstNodes::DoWhileStatement(_)
                | AstNodes::IfStatement(_)
                | AstNodes::WhileStatement(_)
                | AstNodes::ForStatement(_)
                | AstNodes::ForInStatement(_)
                | AstNodes::ForOfStatement(_)
                | AstNodes::WithStatement(_)
        ) {
            write!(f, ";");
        }
    }
}

/// Returns `true` if the expression needs a leading semicolon to prevent ASI issues
fn expression_statement_needs_semicolon<'a>(
    stmt: &AstNode<'a, ExpressionStatement<'a>>,
    f: &JsFormatter<'_, 'a>,
) -> bool {
    if matches!(
        stmt.parent(),
        // `if (true) (() => {})`
        AstNodes::IfStatement(_)
        // `do ({} => {}) while (true)`
        | AstNodes::DoWhileStatement(_)
        // `while (true) (() => {})`
        | AstNodes::WhileStatement(_)
        // `for (;;) (() => {})`
        | AstNodes::ForStatement(_)
        // `for (i in o) (() => {})`
        | AstNodes::ForInStatement(_)
        // `for (i of o) (() => {})`
        | AstNodes::ForOfStatement(_)
        // `with(true) (() => {})`
        | AstNodes::WithStatement(_)
        // `label: (() => {})`
        | AstNodes::LabeledStatement(_)
    ) {
        return false;
    }
    // Arrow functions need semicolon only if they will have parentheses
    // e.g., `(a) => {}` needs `;(a) => {}` but `a => {}` doesn't need semicolon
    if let Expression::ArrowFunctionExpression(arrow) = &stmt.expression {
        return !can_avoid_parentheses(arrow, f);
    }

    // Expressions starting with keywords don't need ASI protection
    if matches!(
        &stmt.expression,
        Expression::NewExpression(_)
            | Expression::AwaitExpression(_)
            | Expression::YieldExpression(_)
    ) {
        return false;
    }

    // First check if the expression itself needs protection
    let expr = stmt.expression();

    // Get the leftmost expression to check what the line starts with
    ExpressionLeftSide::from(expr).iter().any(|current| match current {
        ExpressionLeftSide::Expression(expr) => {
            expr.needs_parentheses(f)
                || match expr.as_ref() {
                    Expression::ArrayExpression(_)
                    | Expression::RegExpLiteral(_)
                    | Expression::TSTypeAssertion(_)
                    | Expression::ArrowFunctionExpression(_)
                    | Expression::JSXElement(_)
                    | Expression::JSXFragment(_)
                    | Expression::TemplateLiteral(_) => true,
                    Expression::UnaryExpression(unary) => {
                        matches!(
                            unary.operator,
                            UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation
                        )
                    }
                    _ => false,
                }
                // A type cast comment forces parentheses around the expression,
                // so the line starts with `(` even though `needs_parentheses` is false:
                // `/** @type {string} */ (this.s).length`
                // Checked last: this scans source text/comments, unlike the checks above.
                || classify_type_cast(expr.span(), f).is_target()
        }
        ExpressionLeftSide::AssignmentTarget(assignment) => {
            matches!(
                assignment.as_ref(),
                AssignmentTarget::ArrayAssignmentTarget(_)
                    | AssignmentTarget::TSTypeAssertion(_)
                    | AssignmentTarget::TSAsExpression(_)
                    | AssignmentTarget::TSSatisfiesExpression(_)
            )
        }
        ExpressionLeftSide::SimpleAssignmentTarget(assignment) => {
            matches!(
                assignment.as_ref(),
                SimpleAssignmentTarget::TSTypeAssertion(_)
                    | SimpleAssignmentTarget::TSAsExpression(_)
                    | SimpleAssignmentTarget::TSSatisfiesExpression(_)
            )
        }
    })
}

impl<'a> FormatWrite<'a> for AstNode<'a, ExpressionStatement<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let span = self.span();
        // Check if we need a leading semicolon to prevent ASI issues.
        // Leading comments are printed here rather than in the generated `fmt`
        // so the semicolon can go before a type cast comment;
        // otherwise the cast is detached from its expression and the parentheses get dropped on the next format:
        // `;/** @type {string[]} */ ([]).forEach(...)`
        let needs_semicolon = f.options().semicolons == Semicolons::AsNeeded
            && expression_statement_needs_semicolon(self, f);
        let leading_comments = f.context().comments().comments_before(span.start);
        // Split off a trailing type cast comment so the semicolon lands before it
        let split = if needs_semicolon
            && leading_comments.last().is_some_and(|last| f.comments().is_type_cast_comment(last))
        {
            leading_comments.len() - 1
        } else {
            leading_comments.len()
        };
        write!(f, FormatLeadingComments::Comments(&leading_comments[..split]));
        if needs_semicolon {
            write!(f, ";");
        }
        write!(f, FormatLeadingComments::Comments(&leading_comments[split..]));

        if f.comments().has_trailing_suppression_comment(span.end) {
            // Preserve original text when the statement has an inline suppression comment:
            // `stmt(); // prettier-ignore` or `stmt(); /* prettier-ignore */`
            write!(f, [FormatSuppressedNode(span)]);
            return;
        }

        let expression = self.expression();
        // A trailing comment right before the closing paren of the rightmost
        // sequence/assignment stays inside the parentheses;
        // extend the content past the closing paren so the comment is not moved behind the semicolon
        // (the sub-expression prints it, see `keeps_trailing_comment_inside_parens`).
        let expression_end = expression.span().end;
        let content_end = if keeps_trailing_comment_inside_parens(expression.as_ref(), false) {
            f.comments().end_including_source_parens(expression_end, self.span().end)
        } else {
            expression_end
        };
        write!(f, FormatContentWithSemicolon::new(expression, content_end, self.span().end));
    }
}

/// Prints a suppressed statement whose ignored range excludes the trailing semicolon:
/// the source from `start` up to `content_end` verbatim,
/// then the formatter's own terminator, like Prettier (prettier#18678).
/// Comments between `content_end` and the end of the statement are left
/// for the next node's leading-comments pass.
fn write_suppressed_statement_with_semicolon(
    start: u32,
    content_end: u32,
    f: &mut JsFormatter<'_, '_>,
) {
    write!(f, [FormatSuppressedNode(Span::new(start, content_end)), OptionalSemicolon]);
}

/// Prints a suppressed statement whose ignored range may or may not have had
/// its trailing `;` stripped (see [`suppressed_statement_content_end`]).
fn write_suppressed_statement(
    span: Span,
    content_end: u32,
    print_semicolon: bool,
    f: &mut JsFormatter<'_, '_>,
) {
    if print_semicolon {
        write_suppressed_statement_with_semicolon(span.start, content_end, f);
    } else {
        debug_assert_eq!(content_end, span.end);
        FormatSuppressedNode(span).fmt(f);
    }
}

/// The ignored range end for a statement terminated by a (possibly distant) source `;`,
/// and whether one was actually stripped (so the formatter must print its own).
fn semicolon_terminated_content_end(
    content_end: u32,
    span: Span,
    f: &JsFormatter<'_, '_>,
) -> (u32, bool) {
    let end = f.comments().end_including_source_parens(content_end, span.end);
    (end, end < span.end)
}

#[expect(clippy::cast_possible_truncation)]
const RETURN_KEYWORD_LEN: u32 = "return".len() as u32;
#[expect(clippy::cast_possible_truncation)]
const DEBUGGER_KEYWORD_LEN: u32 = "debugger".len() as u32;
#[expect(clippy::cast_possible_truncation)]
const BREAK_KEYWORD_LEN: u32 = "break".len() as u32;
#[expect(clippy::cast_possible_truncation)]
const CONTINUE_KEYWORD_LEN: u32 = "continue".len() as u32;

/// The ignored range end for `return` (the argument or the keyword),
/// and whether a source `;` was stripped.
fn return_statement_content_end(s: &ReturnStatement<'_>, f: &JsFormatter<'_, '_>) -> (u32, bool) {
    s.argument.as_ref().map_or_else(
        || {
            let keyword_end = s.span.start + RETURN_KEYWORD_LEN;
            (keyword_end, keyword_end < s.span.end)
        },
        |argument| semicolon_terminated_content_end(argument.span().end, s.span, f),
    )
}

/// The ignored range end for `break`/`continue`: the label, or the keyword.
fn break_or_continue_content_end(
    span: Span,
    keyword_len: u32,
    label: Option<&LabelIdentifier<'_>>,
) -> u32 {
    label.map_or(span.start + keyword_len, |label| label.span.end)
}

/// The ignored range end of a suppressed statement and whether the formatter prints its own terminator after it,
/// mirroring Prettier's `locEnd` overrides (`language-js/location/overrides.js`) + `shouldIgnoredNodePrintSemicolon`:
/// the trailing `;` (and anything between it and the content) is excluded from the verbatim range,
/// keyword statements (`debugger`/`break`/`continue`) always re-add `;`,
/// content-terminated ones only when a source `;` was stripped, and statements ending in a body recurse into the rightmost body.
fn suppressed_statement_content_end(stmt: &Statement<'_>, f: &JsFormatter<'_, '_>) -> (u32, bool) {
    match stmt {
        Statement::ExpressionStatement(s) => {
            semicolon_terminated_content_end(s.expression.span().end, s.span, f)
        }
        Statement::ReturnStatement(s) => return_statement_content_end(s, f),
        Statement::ThrowStatement(s) => {
            semicolon_terminated_content_end(s.argument.span().end, s.span, f)
        }
        Statement::DoWhileStatement(s) => {
            semicolon_terminated_content_end(s.test.span().end, s.span, f)
        }
        Statement::DebuggerStatement(s) => (s.span.start + DEBUGGER_KEYWORD_LEN, true),
        Statement::BreakStatement(s) => {
            (break_or_continue_content_end(s.span, BREAK_KEYWORD_LEN, s.label.as_ref()), true)
        }
        Statement::ContinueStatement(s) => {
            (break_or_continue_content_end(s.span, CONTINUE_KEYWORD_LEN, s.label.as_ref()), true)
        }
        Statement::IfStatement(s) => {
            suppressed_statement_content_end(s.alternate.as_ref().unwrap_or(&s.consequent), f)
        }
        Statement::WhileStatement(s) => suppressed_statement_content_end(&s.body, f),
        Statement::WithStatement(s) => suppressed_statement_content_end(&s.body, f),
        Statement::ForStatement(s) => suppressed_statement_content_end(&s.body, f),
        Statement::ForInStatement(s) => suppressed_statement_content_end(&s.body, f),
        Statement::ForOfStatement(s) => suppressed_statement_content_end(&s.body, f),
        Statement::LabeledStatement(s) => suppressed_statement_content_end(&s.body, f),
        _ => (stmt.span().end, false),
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, DoWhileStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        // The ignored range ends at the closing paren
        let (content_end, print_semicolon) =
            semicolon_terminated_content_end(self.test().span().end, self.span(), f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let body = self.body();
        write!(f, group(&format_args!("do", FormatStatementBody::new(body))));
        if matches!(body.as_ref(), Statement::BlockStatement(_)) {
            write!(f, space());
        } else {
            write!(f, hard_line_break());
        }

        // Comments inside the parens belong to the test and stay there;
        // only comments between `)` and `;` move behind the semicolon.
        // Scan for the `)` only when a comment is actually around;
        // otherwise `FormatContentWithSemicolon` prints as-is from either position.
        let node_end = self.span().end;
        let test_end = self.test().span().end;
        let rparen_end = if f.comments().has_comment_in_range(test_end, node_end) {
            f.comments().end_including_source_parens(test_end, node_end)
        } else {
            test_end
        };
        let content = format_with(|f| {
            write!(f, ["while", space(), "(", group(&soft_block_indent(&self.test())), ")"]);
        });
        write!(f, FormatContentWithSemicolon::new(&content, rparen_end, node_end));
    }
}

/// Formats comments that appear before empty statements in control structures.
///
/// Example:
/// ```js
/// // Input:
/// for (init;;) /* comment */ ;
/// for (init;;update) /* comment */ ;
/// for (init of iterable) /* comment */ ;
/// for (init in iterable) /* comment */ ;
/// while (test) /* comment */ ;
/// if (test) /* comment */ ;
///
/// // Output:
/// for (init /* comment */;; );
/// for (init; ; update /* comment */);
/// for (init of iterable /* comment */) ;
/// for (init in iterable /* comment */) ;
/// while (test /* comment */) ;
/// if (test /* comment */) ;
/// ```
///
/// This ensures compatibility with [Prettier's comment handling for empty statements](https://github.com/prettier/prettier/blob/7584432401a47a26943dd7a9ca9a8e032ead7285/src/language-js/comments/printer-methods.js#L15).
struct FormatCommentForEmptyStatement<'a, 'b>(&'b AstNode<'a, Statement<'a>>);
impl<'a> Format<'a, JsFormatContext<'a>> for FormatCommentForEmptyStatement<'a, '_> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        if let AstNodes::EmptyStatement(empty) = self.0.as_ast_nodes() {
            let comments = f.context().comments().comments_before(empty.span.start);
            FormatTrailingComments::Comments(comments).fmt(f);
            empty.format_trailing_comments(f);
        }
    }
}

struct FormatTestOfIfAndWhileStatement<'a, 'b>(&'b AstNode<'a, Expression<'a>>);
impl<'a> Format<'a, JsFormatContext<'a>> for FormatTestOfIfAndWhileStatement<'a, '_> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        // FormatNodeWithoutTrailingComments already handles suppression comments internally,
        // so no separate has_trailing_suppression_comment check is needed here.
        write!(f, FormatNodeWithoutTrailingComments(self.0));
        let comments = f.context().comments().comments_before_character(self.0.span().end, b')');
        if !comments.is_empty() {
            write!(f, [space(), FormatTrailingComments::Comments(comments)]);
        }
    }
}
impl<'a> FormatWrite<'a> for AstNode<'a, WhileStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(&self.body, f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let body = self.body();
        write!(
            f,
            group(&format_args!(
                "while",
                space(),
                "(",
                group(&soft_block_indent(&format_args!(
                    FormatTestOfIfAndWhileStatement(self.test()),
                    FormatCommentForEmptyStatement(self.body())
                ))),
                ")",
                FormatStatementBody::new(body)
            ))
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ForStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(&self.body, f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let init = self.init();
        let test = self.test();
        let update = self.update();
        let body = self.body();
        let format_body = FormatStatementBody::new(body);
        if init.is_none() && test.is_none() && update.is_none() {
            return write!(f, [group(&format_args!("for", space(), "(;;)", format_body))]);
        }

        let format_inner = format_with(|f| {
            write!(
                f,
                [
                    "for",
                    space(),
                    "(",
                    group(&soft_block_indent(&format_args!(
                        init,
                        (test.is_none() && update.is_none())
                            .then_some(FormatCommentForEmptyStatement(body)),
                        ";",
                        soft_line_break_or_space(),
                        test,
                        (update.is_none()).then_some(FormatCommentForEmptyStatement(body)),
                        ";",
                        update.is_some().then_some(soft_line_break_or_space()),
                        update,
                        FormatCommentForEmptyStatement(body)
                    ))),
                    ")",
                    format_body
                ]
            );
        });
        write!(f, group(&format_inner));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ForInStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(&self.body, f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let comments = f.context().comments().own_line_comments_before(self.right.span().start);
        let left = self.left();
        let body = self.body();

        if let Some(comment) = comments.first()
            && comment.span.start > left.span().end
        {
            write!(f, FormatLeadingComments::Comments(comments));
        }

        write!(
            f,
            group(&format_args!(
                "for",
                space(),
                "(",
                left,
                space(),
                "in",
                space(),
                self.right(),
                FormatCommentForEmptyStatement(body),
                ")",
                FormatStatementBody::new(body)
            ))
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ForOfStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(&self.body, f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let comments = f.context().comments().own_line_comments_before(self.right.span().start);

        let r#await = self.r#await();
        let left = self.left();
        let right = self.right();
        let body = self.body();
        let format_inner = format_with(|f| {
            write!(f, "for");
            if r#await {
                write!(f, [space(), "await"]);
            }
            write!(
                f,
                [
                    space(),
                    "(",
                    left,
                    space(),
                    "of",
                    space(),
                    right,
                    ")",
                    FormatStatementBody::new(body)
                ]
            );
        });

        if let Some(comment) = comments.first()
            && comment.span.start > left.span().end
        {
            write!(f, FormatLeadingComments::Comments(comments));
        }

        write!(f, group(&format_inner));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, IfStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(
            self.alternate.as_ref().unwrap_or(&self.consequent),
            f,
        );
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let test = self.test();
        let consequent = self.consequent();
        let alternate = self.alternate();

        write!(
            f,
            group(&format_args!(
                "if",
                space(),
                "(",
                group(&soft_block_indent(&FormatTestOfIfAndWhileStatement(test))),
                ")",
                FormatStatementBody::new(consequent),
            ))
        );
        if let Some(alternate) = alternate {
            let alternate_start = alternate.span().start;
            let comments = f.context().comments().comments_before(alternate_start);

            let has_line_comment = comments.iter().any(|comment| comment.is_line());
            let has_dangling_comments = comments
                .last()
                .or(f.comments().printed_comments().last())
                .is_some_and(|last_comment| {
                    // Ensure the comments are placed before the else keyword or on a new line
                    f.source_text().slice_range(last_comment.span.end, alternate_start).trim()
                        == "else"
                });

            let else_on_same_line = matches!(consequent.as_ref(), Statement::BlockStatement(_))
                && (!has_line_comment || !has_dangling_comments);

            if else_on_same_line {
                write!(f, [space(), has_dangling_comments.then(line_suffix_boundary)]);
            } else {
                write!(f, hard_line_break());
            }

            if has_dangling_comments && let Some(first_comment) = comments.first() {
                if f.lines_before(first_comment.span) > 1 {
                    write!(f, empty_line());
                }
                write!(
                    f,
                    FormatDanglingComments::Comments { comments, indent: DanglingIndentMode::None }
                );
                if has_line_comment {
                    write!(f, hard_line_break());
                } else {
                    write!(f, space());
                }
            }

            write!(
                f,
                [
                    "else",
                    line_suffix_boundary(),
                    group(&FormatStatementBody::new(alternate).with_forced_space(matches!(
                        alternate.as_ref(),
                        Statement::IfStatement(_)
                    )))
                ]
            );
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ContinueStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        // The ignored range ends at the label (or the keyword)
        let content_end =
            break_or_continue_content_end(self.span(), CONTINUE_KEYWORD_LEN, self.label.as_ref());
        write_suppressed_statement_with_semicolon(self.span().start, content_end, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if let Some(label) = self.label() {
            let content = format_with(|f| write!(f, ["continue", space(), label]));
            write!(f, FormatContentWithSemicolon::new(&content, label.span().end, self.span().end));
        } else {
            write!(f, ["continue", OptionalSemicolon]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BreakStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        // The ignored range ends at the label (or the keyword)
        let content_end =
            break_or_continue_content_end(self.span(), BREAK_KEYWORD_LEN, self.label.as_ref());
        write_suppressed_statement_with_semicolon(self.span().start, content_end, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if let Some(label) = self.label() {
            let content = format_with(|f| write!(f, ["break", space(), label]));
            write!(f, FormatContentWithSemicolon::new(&content, label.span().end, self.span().end));
        } else {
            write!(f, ["break", OptionalSemicolon]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, WithStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(&self.body, f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(
            f,
            group(&format_args!(
                "with",
                space(),
                "(",
                self.object(),
                ")",
                FormatStatementBody::new(self.body())
            ))
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, LabeledStatement<'a>> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        let (content_end, print_semicolon) = suppressed_statement_content_end(&self.body, f);
        write_suppressed_statement(self.span(), content_end, print_semicolon, f);
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let comments = f.context().comments().line_comments_before(self.body.span().start);
        FormatLeadingComments::Comments(comments).fmt(f);

        let label = self.label();
        let body = self.body();
        write!(f, [label, ":"]);
        if matches!(body.as_ref(), Statement::EmptyStatement(_)) {
            let empty_comments = f.context().comments().comments_before(self.span.end);
            write!(
                f,
                [
                    FormatTrailingComments::Comments(empty_comments),
                    maybe_space(!empty_comments.is_empty()),
                    // If the body is an empty statement, force semicolon insertion
                    ";"
                ]
            );
        } else {
            write!(f, [space(), body]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, DebuggerStatement> {
    fn write_suppressed(&self, f: &mut JsFormatter<'_, 'a>) {
        // The ignored range ends at the keyword
        write_suppressed_statement_with_semicolon(
            self.span.start,
            self.span.start + DEBUGGER_KEYWORD_LEN,
            f,
        );
    }

    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["debugger", OptionalSemicolon]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BindingPattern<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        Format::fmt(self, f);
        if let AstNodes::VariableDeclarator(declarator) = self.parent() {
            write!(f, declarator.definite.then_some("!"));
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, FormalParameterRest<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.rest()]);
        write!(f, self.type_annotation());
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentPattern<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let left = self.left().memoized();
        // Format `left` early before writing leading comments, so that comments
        // inside `left` are not treated as leading comments of `= right`
        left.inspect(f);
        let comments = f.context().comments().own_line_comments_before(self.right.span().start);
        write!(
            f,
            [
                FormatLeadingComments::Comments(comments),
                group(&left),
                space(),
                "=",
                space(),
                self.right()
            ]
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, ObjectPattern<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        ObjectPatternLike::ObjectPattern(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BindingProperty<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        AssignmentLike::BindingProperty(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BindingRestElement<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["...", self.argument()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, YieldExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["yield", self.delegate().then_some("*")]);
        if let Some(argument) = &self.argument() {
            write!(f, [space(), FormatAdjacentArgument(argument)]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, V8IntrinsicExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["%", self.name(), self.arguments()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BooleanLiteral> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, if self.value() { "true" } else { "false" });
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, NullLiteral> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "null");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, NumericLiteral<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let source_text = f.source_text().text_for(self);
        // JS keeps one trailing decimal zero (`x.00000` -> `x.0`); see `format_trimmed_number`.
        let keep_one_trailing_decimal_zero = true;

        // Check if this numeric literal is a property key (not a value) that should be quoted
        // when quoteProps is "consistent" and another property requires quotes.
        // We need to check that this literal's span matches the key's span, not just that the parent is a property.
        let is_property_key = match self.parent() {
            AstNodes::ObjectProperty(prop) => prop.key.span() == self.span(),
            AstNodes::TSPropertySignature(prop) => prop.key.span() == self.span(),
            AstNodes::TSMethodSignature(prop) => prop.key.span() == self.span(),
            AstNodes::MethodDefinition(prop) => prop.key.span() == self.span(),
            AstNodes::PropertyDefinition(prop) => prop.key.span() == self.span(),
            AstNodes::AccessorProperty(prop) => prop.key.span() == self.span(),
            _ => false,
        };

        if is_property_key && f.context().is_quote_needed() {
            // Get the formatted number text
            let formatted = format_trimmed_number(source_text, keep_one_trailing_decimal_zero);

            // Check if the number is "simple" (only digits, or digits.digits)
            // and the value matches the formatted representation.
            // This follows Prettier's behavior in shouldQuotePropertyKey.
            //
            // For TypeScript, quoting numbers is not safe (it changes the type),
            // so we skip this logic for TypeScript files.
            let should_quote =
                !f.context().source_type().is_typescript() && is_simple_number(&formatted) && {
                    // Check if String(value) === formatted
                    // In JavaScript: String(1.0) = "1", String(1.5) = "1.5"
                    let value = self.value;
                    let value_str = if value.fract() == 0.0 && value.abs() < 1e15 {
                        // Integer-like values: format without decimal point
                        format!("{value:.0}")
                    } else {
                        // Use default float formatting
                        format!("{value}")
                    };
                    value_str == *formatted
                };

            if should_quote {
                let quote_str = f.options().quote_style.as_str();
                write!(f, [quote_str, text(f.allocator().alloc_str(&formatted)), quote_str]);
                return;
            }
        }

        format_number_token(source_text, keep_one_trailing_decimal_zero).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, StringLiteral<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        // Check if we're in a Tailwind context via stack (O(1) lookup)
        // This handles nested string literals inside JSXAttribute/CallExpression values
        if let Some(ctx) = tailwind_context_for_string_literal(self, f) {
            // We're inside a Tailwind context - sort this string literal as Tailwind classes
            write_tailwind_string_literal(self, ctx, f);
        } else {
            // Not in Tailwind context - use normal string literal formatting
            let is_jsx = matches!(self.parent(), AstNodes::JSXAttribute(_));
            FormatLiteralStringToken::new(
                f.source_text().text_for(self),
                is_jsx,
                StringLiteralParentKind::Expression,
            )
            .fmt(f);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, BigIntLiteral<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        // Already-lowercase bigints (the common case, e.g. `123n`) stay `Cow::Borrowed`, so the
        // arena copy is avoided; only an uppercase literal (e.g. `0xFFn`) is copied.
        let lowered = self.raw().unwrap().as_str().cow_to_ascii_lowercase();
        write!(f, text(arena_cow_str(&lowered, f)));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, RegExpLiteral<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let raw = self.raw().unwrap().as_str();

        let Some((pattern, flags)) = raw.rsplit_once('/') else {
            return write!(f, text(raw));
        };

        // Regex flags are ASCII and limited to at most 8 unique: d, g, i, m, s, u, v, y
        debug_assert!(flags.len() <= 8 && flags.is_ascii());
        let mut flags_buf = [0u8; 8];
        let len = flags.len();
        flags_buf[..len].copy_from_slice(flags.as_bytes());
        flags_buf[..len].sort_unstable();
        let flags = str::from_utf8(&flags_buf[..len]).unwrap();
        let s = f.allocator().alloc_concat_strs_array([pattern, "/", flags]);
        write!(f, text(s));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSEnumDeclaration<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.declare() {
            write!(f, ["declare", space()]);
        }
        if self.r#const() {
            write!(f, ["const", space()]);
        }
        write!(f, ["enum", space(), self.id(), space(), "{", self.body(), "}"]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSEnumBody<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.members().is_empty() {
            write!(f, format_dangling_comments(self.span()).with_soft_block_indent());
        } else {
            write!(f, block_indent(self.members()));
        }
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, ArenaVec<'a, TSEnumMember<'a>>> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        if f.options().quote_properties.is_consistent() {
            let quote_needed = self
                .as_ref()
                .iter()
                .any(|member| should_preserve_quote_for_enum_member(&member.id, f));
            f.context_mut().push_quote_needed(quote_needed);
        }

        let trailing_separator = FormatTrailingCommas::ES5.trailing_separator(f.options());
        f.join_nodes_with_soft_line().entries_with_trailing_separator(
            self.iter(),
            ",",
            trailing_separator,
        );

        if f.options().quote_properties.is_consistent() {
            f.context_mut().pop_quote_needed();
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSEnumMember<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let id = self.id();
        let is_computed = matches!(id.as_ref(), TSEnumMemberName::ComputedTemplateString(_));

        if is_computed {
            write!(f, "[");
        }

        // Enum member names behave like object property keys for `quoteProps`;
        // intercept them here (like `ImportAttribute`) so the generic `StringLiteral` writer stays context-free.
        if let (
            // NOTE: `ComputedString` (`['baz']`, rejected by TSC but parsed by Oxc) is included
            TSEnumMemberName::String(_) | TSEnumMemberName::ComputedString(_),
            AstNodes::StringLiteral(string),
        ) = (id.as_ref(), id.as_ast_nodes())
        {
            let format = FormatLiteralStringToken::new(
                f.source_text().text_for(string),
                /* jsx */ false,
                StringLiteralParentKind::Member,
            )
            .clean_text(f);

            string.format_leading_comments(f);
            write!(f, format);
            string.format_trailing_comments(f);
        } else {
            write!(f, [id]);
        }

        if is_computed {
            write!(f, "]");
        }

        if let Some(init) = self.initializer() {
            write!(f, [space(), "=", space(), init]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeAnnotation<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        match self.parent() {
            AstNodes::TSFunctionType(_) | AstNodes::TSConstructorType(_) => {
                write!(f, ["=>", space(), self.type_annotation()]);
            }
            AstNodes::TSTypePredicate(_) => {
                write!(f, [self.type_annotation()]);
            }
            _ => {
                write!(f, [":", space(), self.type_annotation()]);
            }
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSLiteralType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        self.literal().fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSConditionalType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        ConditionalLike::TSConditionalType(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSParenthesizedType<'a>> {
    fn write(&self, _f: &mut JsFormatter<'_, 'a>) {
        unreachable!("No `TSParenthesizedType` as we disabled `preserve_parens` in the parser")
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeOperator<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.operator().to_str(), space(), self.type_annotation()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSArrayType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.element_type(), "[]"]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSIndexedAccessType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.object_type(), "[", self.index_type(), "]"]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSNamedTupleMember<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, self.label());
        if self.optional() {
            write!(f, "?");
        }
        write!(f, [":", space(), self.element_type()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSOptionalType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.type_annotation(), "?"]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSRestType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["...", self.type_annotation()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSAnyKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "any");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSStringKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "string");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSBooleanKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "boolean");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSNumberKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "number");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSNeverKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "never");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSIntrinsicKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "intrinsic");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSUnknownKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "unknown");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSNullKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "null");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSUndefinedKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "undefined");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSVoidKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "void");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSSymbolKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "symbol");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSThisType> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "this");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSObjectKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "object");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSBigIntKeyword> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, "bigint");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeReference<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let wrap = is_leftmost_intrinsic_in_type_alias(self);
        write!(
            f,
            [wrap.then_some("("), self.type_name(), self.type_arguments(), wrap.then_some(")")]
        );
    }
}

/// The parser treats a leading `intrinsic` identifier in a type alias annotation
/// as `TSIntrinsicKeyword` (e.g. `type t = intrinsic`).
/// Source like `type t = (intrinsic);` loses its parens (`preserve_parens: false`),
/// so without re-emitting them the output re-parses as `TSIntrinsicKeyword`.
/// (or fails to parse when followed by `|`/`&`)
///
/// See: <https://github.com/oxc-project/oxc/issues/20205>
fn is_leftmost_intrinsic_in_type_alias(reference: &AstNode<'_, TSTypeReference<'_>>) -> bool {
    let TSTypeName::IdentifierReference(ident) = &reference.type_name else {
        return false;
    };
    if ident.name != "intrinsic" || reference.type_arguments.is_some() {
        return false;
    }
    let span_start = reference.span().start;
    let mut parent = reference.parent();
    loop {
        match parent {
            AstNodes::TSTypeAliasDeclaration(_) => return true,
            AstNodes::TSUnionType(union) => {
                if union.types.first().is_none_or(|t| t.span().start != span_start) {
                    return false;
                }
                parent = union.parent();
            }
            AstNodes::TSIntersectionType(intersection) => {
                if intersection.types.first().is_none_or(|t| t.span().start != span_start) {
                    return false;
                }
                parent = intersection.parent();
            }
            AstNodes::TSConditionalType(cond) => {
                if cond.check_type().span().start != span_start {
                    return false;
                }
                parent = cond.parent();
            }
            AstNodes::TSArrayType(array) => {
                parent = array.parent();
            }
            _ => return false,
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSQualifiedName<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.left(), ".", self.right()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeParameterDeclaration<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        FormatTSTypeParameters::new(self, FormatTSTypeParametersOptions::default()).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeAliasDeclaration<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let content = AssignmentLike::TSTypeAliasDeclaration(self);
        write!(
            f,
            FormatContentWithSemicolon::new(
                &content,
                self.type_annotation.span().end,
                self.span.end
            )
        );
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSInterfaceDeclaration<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let id = self.id();
        let type_parameters = self.type_parameters();
        let extends = self.extends();
        let body = self.body();

        // Determines whether to use group mode for formatting the `extends` clause.
        // 1. If there are multiple `extends`, we always use group mode.
        // 2. If there is a single `extends` that is a member expression without type arguments, we use group mode.
        // 3. If there are comments between the `id` and the `extends`, we use group mode.
        let group_mode = extends.len() > 1
            || extends.as_ref().first().is_some_and(|first| {
                (first.expression.is_member_expression() && first.type_arguments.is_none()) || {
                    let prev_span = type_parameters.as_ref().map_or(id.span(), GetSpan::span);
                    f.comments().has_comment_in_range(prev_span.end, first.span().start)
                }
            });

        let format_id = format_with(|f| {
            if type_parameters.is_none() && extends.is_empty() {
                FormatNodeWithoutTrailingComments(id).fmt(f);
            } else {
                write!(f, [id]);
            }

            if let Some(type_parameters) = type_parameters {
                let type_parameters_group = Some(f.group_id("type_parameters"));
                write!(
                    f,
                    FormatTSTypeParameters::new(
                        type_parameters,
                        FormatTSTypeParametersOptions {
                            group_id: type_parameters_group,
                            is_type_or_interface_decl: true
                        }
                    )
                );
            }
        });

        let format_extends = format_with(|f| {
            let Some(first_extend) = extends.as_ref().first() else {
                return;
            };

            let has_leading_own_line_comment =
                f.context().comments().has_leading_own_line_comment(first_extend.span().start);

            if has_leading_own_line_comment {
                write!(
                    f,
                    FormatTrailingComments::Comments(
                        f.context().comments().comments_before(first_extend.span().start)
                    )
                );
            }

            if extends.len() > 1 {
                write!(
                    f,
                    [
                        soft_line_break_or_space(),
                        "extends",
                        group(&soft_line_indent_or_space(extends))
                    ]
                );
            } else {
                let format_extends = format_with(|f| write!(f, ["extends", space(), extends]));
                if group_mode {
                    write!(f, [soft_line_break_or_space(), group(&format_extends)]);
                } else {
                    write!(f, [space(), format_extends]);
                }
            }

            let has_leading_own_line_comment =
                f.context().comments().has_leading_own_line_comment(self.body.span().start);

            if !has_leading_own_line_comment {
                write!(f, [space()]);
                body.format_leading_comments(f);
            }
        });

        let content = format_with(|f| {
            if self.declare() {
                write!(f, ["declare", space()]);
            }

            write!(f, ["interface", space()]);

            if extends.is_empty() {
                write!(f, [format_id, format_extends]);
            } else if group_mode {
                let indented = format_with(|f| write!(f, [format_id, indent(&format_extends)]));
                let heritage_id = f.group_id("heritageGroup");
                write!(f, [group(&indented).with_group_id(Some(heritage_id)), space()]);
            } else {
                write!(f, [&format_args!(format_id, format_extends)]);
            }

            write!(f, [space()]);
            // Avoid printing leading comments of body
            body.write(f);
        });

        write!(f, group(&content));
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSInterfaceBody<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [space(), "{"]);

        if self.body.is_empty() {
            write!(f, format_dangling_comments(self.span).with_block_indent());
        } else {
            write!(f, block_indent(&self.body()));
        }

        write!(f, "}");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSPropertySignature<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.readonly() {
            write!(f, ["readonly", space()]);
        }
        format_property_key(self.key(), self.computed(), f);
        if self.optional() {
            write!(f, "?");
        }
        if let Some(type_annotation) = &self.type_annotation() {
            write!(f, type_annotation);
        }
    }
}

struct FormatTSSignature<'a, 'b> {
    signature: &'b AstNode<'a, TSSignature<'a>>,
    next_signature: Option<&'b AstNode<'a, TSSignature<'a>>>,
}

impl GetSpan for FormatTSSignature<'_, '_> {
    fn span(&self) -> Span {
        self.signature.span()
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for FormatTSSignature<'a, '_> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        if f.comments().is_suppressed(self.signature.span().start) {
            return write!(f, [self.signature]);
        }

        if f.comments().has_trailing_suppression_comment(self.signature.span().end) {
            write!(f, [FormatSuppressedNode(self.signature.span())]);
            let comments =
                f.context().comments().end_of_line_comments_after(self.signature.span().end);
            write!(f, FormatTrailingComments::Comments(comments));
            return;
        }

        write!(f, [&self.signature]);

        match f.options().semicolons {
            Semicolons::Always => {
                if self.next_signature.is_none() {
                    write!(f, [if_group_breaks(&token(";"))]);
                } else {
                    token(";").fmt(f);
                }
            }
            Semicolons::AsNeeded => {
                // Needs semicolon anyway when omitting it would change how the members parse:
                // 1. It's a property signature without type annotation named `static`, `get` or `set`,
                //    which would otherwise parse as a modifier or accessor of the following member
                // 2. It's a property signature without type annotation followed by
                //    a call signature, e.g. `a` + `(): void` -> `a(): void`
                let needs_semicolon = matches!(
                    self.signature.as_ref(), TSSignature::TSPropertySignature(property)
                    if property.type_annotation.is_none()
                    && (is_keyword_property_key(&property.key)
                        || self.next_signature.is_some_and(|next| {
                            matches!(next.as_ref(), TSSignature::TSCallSignatureDeclaration(_))
                        }))
                );

                if needs_semicolon {
                    write!(f, [";"]);
                } else if self.next_signature.is_some() {
                    write!(f, [if_group_fits_on_line(&token(";"))]);
                }
            }
        }
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, ArenaVec<'a, TSSignature<'a>>> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        if f.options().quote_properties.is_consistent() {
            let quote_needed = self.as_ref().iter().any(|signature| {
                let key = match signature {
                    TSSignature::TSPropertySignature(property) => &property.key,
                    TSSignature::TSMethodSignature(method) => {
                        // A quoted `new` method keeps its quotes, so it forces quoting the sibling members as well
                        if is_quoted_new_method_signature(method) {
                            return true;
                        }
                        &method.key
                    }
                    _ => return false,
                };
                should_preserve_quote(key, f)
            });
            f.context_mut().push_quote_needed(quote_needed);
        }

        let mut joiner = f.join_nodes_with_soft_line();

        let mut iter = self.iter().peekable();
        while let Some(signature) = iter.next() {
            joiner.entry(
                signature.span(),
                &FormatTSSignature { signature, next_signature: iter.peek().copied() },
            );
        }

        if f.options().quote_properties.is_consistent() {
            f.context_mut().pop_quote_needed();
        }
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, ArenaVec<'a, TSInterfaceHeritage<'a>>> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        let last_index = self.len().saturating_sub(1);
        let mut joiner = f.join_with(soft_line_break_or_space());

        for (i, heritage) in FormatSeparatedIter::new(self.into_iter(), ",")
            .with_trailing_separator(TrailingSeparator::Disallowed)
            .enumerate()
        {
            if i == last_index {
                // The trailing comments of the last heritage should be printed inside the interface declaration
                joiner.entry(&FormatNodeWithoutTrailingComments(&heritage));
            } else {
                joiner.entry(&heritage);
            }
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSInterfaceHeritage<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.expression(), self.type_arguments()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypePredicate<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.asserts() {
            write!(f, ["asserts", space()]);
        }
        write!(f, [self.parameter_name()]);
        if let Some(type_annotation) = self.type_annotation() {
            write!(f, [space(), "is", space(), type_annotation]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSModuleDeclaration<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.declare() {
            write!(f, ["declare", space()]);
        }

        write!(f, self.kind().as_str());

        write!(f, [space(), self.id()]);

        if let Some(body) = self.body() {
            let mut body = body;
            loop {
                match body.as_ast_nodes() {
                    AstNodes::TSModuleDeclaration(b) => {
                        write!(f, [".", b.id()]);
                        if let Some(b) = &b.body() {
                            body = b;
                        } else {
                            break;
                        }
                    }
                    AstNodes::TSModuleBlock(body) => {
                        write!(f, [space(), body]);
                        break;
                    }
                    _ => {
                        unreachable!()
                    }
                }
            }
        } else {
            write!(f, OptionalSemicolon);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSGlobalDeclaration<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.declare {
            write!(f, ["declare", space()]);
        }
        let comments_before_global = f.context().comments().comments_before(self.global_span.start);
        write!(f, FormatLeadingComments::Comments(comments_before_global));
        write!(f, ["global", space(), self.body()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSModuleBlock<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let directives = self.directives();
        let body = self.body();
        let span = self.span();

        write!(f, "{");
        if is_empty_block(&self.body) && directives.is_empty() {
            write!(f, [format_dangling_comments(span).with_block_indent()]);
        } else {
            // Use `FormatStatementsWithImports` formatter (instead of generic `AstNode<Vec<Statement>>` impl)
            // so imports inside ambient modules (`declare module "foo" { ... }`) are sorted when sorting is enabled
            write!(f, [block_indent(&format_args!(directives, FormatStatementsWithImports(body)))]);
        }
        write!(f, "}");
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeLiteral<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        ObjectLike::TSTypeLiteral(self).fmt(f);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSInferType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["infer ", self.type_parameter()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeQuery<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["typeof ", self.expr_name(), self.type_arguments()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSImportType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, ["import("]);

        let has_comment = f.context().comments().has_comment_before(self.source.span().start);

        let format_argument = self.source().memoized();
        let format_options = self.options().memoized();

        if has_comment || self.options().is_some() {
            let format_inner = format_with(|f| {
                write!(
                    f,
                    [
                        format_argument,
                        &format_with(|f| {
                            if self.options.is_some() {
                                write!(f, [",", soft_line_break_or_space(), format_options]);
                            }
                        }),
                    ]
                );
            });
            if has_comment {
                write!(f, [&soft_block_indent(&format_inner)]);
            } else if self.options().is_some() {
                write!(f, [best_fitting!(format_inner, &soft_block_indent(&format_inner))]);
            } else {
                write!(f, [format_inner]);
            }
        } else {
            write!(f, self.source());
        }

        write!(f, ")");
        if let Some(qualified_name) = &self.qualifier() {
            write!(f, [".", qualified_name]);
        }
        write!(f, self.type_arguments());
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSImportTypeQualifiedName<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.left(), ".", self.right()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeAssertion<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        let break_after_cast = !matches!(
            self.expression,
            Expression::ArrayExpression(_) | Expression::ObjectExpression(_)
        );

        let format_cast = format_with(|f| {
            write!(f, ["<", group(&soft_block_indent(&self.type_annotation())), ">",]);
        });

        if break_after_cast {
            let format_cast = format_cast.memoized();
            let format_expression = self.expression().memoized();

            write!(
                f,
                [best_fitting![
                    format_args!(format_cast, format_expression),
                    format_args!(
                        format_cast,
                        group(&format_args!(
                            token("("),
                            block_indent(&format_expression),
                            token(")")
                        ))
                    ),
                    format_args!(format_cast, format_expression)
                ]]
            );
        } else {
            write!(f, [format_cast, self.expression()]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSNonNullExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.expression(), "!"]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSInstantiationExpression<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        write!(f, [self.expression(), self.type_arguments()]);
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, JSDocNullableType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.postfix() {
            write!(f, [self.type_annotation(), "?"]);
        } else {
            write!(f, ["?", self.type_annotation()]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, JSDocNonNullableType<'a>> {
    fn write(&self, f: &mut JsFormatter<'_, 'a>) {
        if self.postfix() {
            write!(f, [self.type_annotation(), "!"]);
        } else {
            write!(f, ["!", self.type_annotation()]);
        }
    }
}

impl<'a> FormatWrite<'a> for AstNode<'a, JSDocUnknownType> {
    fn write(&self, _f: &mut JsFormatter<'_, 'a>) {}
}
