const std = @import("std");
const logger = bun.logger;
const js_lexer = bun.js_lexer;
const importRecord = @import("import_record.zig");
const js_ast = bun.JSAst;
const options = @import("options.zig");
const rename = @import("renamer.zig");
const runtime = @import("runtime.zig");
const Api = @import("./api/schema.zig").Api;
const fs = @import("fs.zig");
const bun = @import("bun");
const string = bun.string;
const Output = bun.Output;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const default_allocator = bun.default_allocator;

const Ref = @import("ast/base.zig").Ref;
const StoredFileDescriptorType = bun.StoredFileDescriptorType;
const FeatureFlags = bun.FeatureFlags;
const FileDescriptorType = bun.FileDescriptor;

const S = js_ast.S;
const B = js_ast.B;
const G = js_ast.G;
const E = js_ast.E;
const Stmt = js_ast.Stmt;
const Expr = js_ast.Expr;
const Binding = js_ast.Binding;
const Symbol = js_ast.Symbol;
const Level = js_ast.Op.Level;
const Op = js_ast.Op;
const Ast = js_ast.Ast;

const hex_chars = "0123456789ABCDEF";
const first_ascii = 0x20;
const last_ascii = 0x7E;
const first_high_surrogate = 0xD800;
const first_low_surrogate = 0xDC00;
const last_low_surrogate = 0xDFFF;
const CodepointIterator = @import("./string_immutable.zig").UnsignedCodepointIterator;
const assert = bun.assert;

const ImportRecord = bun.ImportRecord;
const SourceMap = @import("./sourcemap/sourcemap.zig");

/// For support JavaScriptCore
const ascii_only_always_on_unless_minifying = true;

fn formatUnsignedIntegerBetween(comptime len: u16, buf: *[len]u8, val: u64) void {
    comptime var i: u16 = len;
    var remainder = val;

    // Write out the number from the end to the front
    inline while (i > 0) {
        comptime i -= 1;
        buf[comptime i] = @as(u8, @intCast((remainder % 10))) + '0';
        remainder /= 10;
    }
}

pub fn writeModuleId(comptime Writer: type, writer: Writer, module_id: u32) void {
    bun.assert(module_id != 0); // either module_id is forgotten or it should be disabled
    _ = writer.writeAll("$") catch unreachable;
    std.fmt.formatInt(module_id, 16, .lower, .{}, writer) catch unreachable;
}

pub fn canPrintWithoutEscape(comptime CodePointType: type, c: CodePointType, comptime ascii_only: bool) bool {
    if (c <= last_ascii) {
        return c >= first_ascii and c != '\\' and c != '"' and c != '\'' and c != '`' and c != '$';
    } else {
        return !ascii_only and c != 0xFEFF and c != 0x2028 and c != 0x2029 and (c < first_high_surrogate or c > last_low_surrogate);
    }
}

const indentation_space_buf = [_]u8{' '} ** 128;
const indentation_tab_buf = [_]u8{'\t'} ** 128;

pub fn bestQuoteCharForString(comptime Type: type, str: []const Type, allow_backtick: bool) u8 {
    var single_cost: usize = 0;
    var double_cost: usize = 0;
    var backtick_cost: usize = 0;
    var i: usize = 0;
    while (i < @min(str.len, 1024)) {
        switch (str[i]) {
            '\'' => {
                single_cost += 1;
            },
            '"' => {
                double_cost += 1;
            },
            '`' => {
                backtick_cost += 1;
            },
            '\n' => {
                single_cost += 1;
                double_cost += 1;
            },
            '\\' => {
                i += 1;
            },
            '$' => {
                if (i + 1 < str.len and str[i + 1] == '{') {
                    backtick_cost += 1;
                }
            },
            else => {},
        }
        i += 1;
    }

    if (allow_backtick and backtick_cost < @min(single_cost, double_cost)) {
        return '`';
    }
    if (single_cost < double_cost) {
        return '\'';
    }
    return '"';
}

const Whitespacer = struct {
    normal: []const u8,
    minify: []const u8,

    pub fn append(this: Whitespacer, comptime str: []const u8) Whitespacer {
        return .{ .normal = this.normal ++ str, .minify = this.minify ++ str };
    }
};

fn ws(comptime str: []const u8) Whitespacer {
    const Static = struct {
        pub const with = str;

        pub const without = brk: {
            var buf = std.mem.zeroes([str.len]u8);
            var buf_i: usize = 0;
            for (str) |c| {
                if (c != ' ') {
                    buf[buf_i] = c;
                    buf_i += 1;
                }
            }
            const final = buf[0..buf_i].*;
            break :brk &final;
        };
    };

    return .{ .normal = Static.with, .minify = Static.without };
}

pub fn estimateLengthForUTF8(input: []const u8, comptime ascii_only: bool, comptime quote_char: u8) usize {
    var remaining = input;
    var len: usize = 2; // for quotes

    while (strings.indexOfNeedsEscapeForJavaScriptString(remaining, quote_char)) |i| {
        len += i;
        remaining = remaining[i..];
        const char_len = strings.wtf8ByteSequenceLengthWithInvalid(remaining[0]);
        const c = strings.decodeWTF8RuneT(
            &switch (char_len) {
                // 0 is not returned by `wtf8ByteSequenceLengthWithInvalid`
                1 => .{ remaining[0], 0, 0, 0 },
                2 => remaining[0..2].* ++ .{ 0, 0 },
                3 => remaining[0..3].* ++ .{0},
                4 => remaining[0..4].*,
                else => unreachable,
            },
            char_len,
            i32,
            0,
        );
        if (canPrintWithoutEscape(i32, c, ascii_only)) {
            len += @as(usize, char_len);
        } else if (c <= 0xFFFF) {
            len += 6;
        } else {
            len += 12;
        }
        remaining = remaining[char_len..];
    } else {
        return remaining.len + 2;
    }

    return len;
}

pub fn quoteForJSON(text: []const u8, output_: MutableString, comptime ascii_only: bool) !MutableString {
    var bytes = output_;
    try quoteForJSONBuffer(text, &bytes, ascii_only);
    return bytes;
}

pub fn writePreQuotedString(text_in: []const u8, comptime Writer: type, writer: Writer, comptime quote_char: u8, comptime ascii_only: bool, comptime json: bool, comptime encoding: strings.Encoding) !void {
    const text = if (comptime encoding == .utf16) @as([]const u16, @alignCast(std.mem.bytesAsSlice(u16, text_in))) else text_in;
    if (comptime json and quote_char != '"') @compileError("for json, quote_char must be '\"'");
    var i: usize = 0;
    const n: usize = text.len;
    while (i < n) {
        const width = switch (comptime encoding) {
            .latin1, .ascii => 1,
            .utf8 => strings.wtf8ByteSequenceLengthWithInvalid(text[i]),
            .utf16 => 1,
        };
        const clamped_width = @min(@as(usize, width), n -| i);
        const c = switch (encoding) {
            .utf8 => strings.decodeWTF8RuneT(
                &switch (clamped_width) {
                    // 0 is not returned by `wtf8ByteSequenceLengthWithInvalid`
                    1 => .{ text[i], 0, 0, 0 },
                    2 => text[i..][0..2].* ++ .{ 0, 0 },
                    3 => text[i..][0..3].* ++ .{0},
                    4 => text[i..][0..4].*,
                    else => unreachable,
                },
                width,
                i32,
                0,
            ),
            .ascii => brk: {
                std.debug.assert(text[i] <= 0x7F);
                break :brk text[i];
            },
            .latin1 => brk: {
                if (text[i] <= 0x7F) break :brk text[i];
                break :brk strings.latin1ToCodepointAssumeNotASCII(text[i], i32);
            },
            .utf16 => brk: {
                // TODO: if this is a part of a surrogate pair, we could parse the whole codepoint in order
                // to emit it as a single \u{result} rather than two paired \uLOW\uHIGH.
                // eg: "\u{10334}" will convert to "\uD800\uDF34" without this.
                break :brk @as(i32, text[i]);
            },
        };
        if (canPrintWithoutEscape(i32, c, ascii_only)) {
            const remain = text[i + clamped_width ..];

            switch (encoding) {
                .ascii, .utf8 => {
                    if (strings.indexOfNeedsEscapeForJavaScriptString(remain, quote_char)) |j| {
                        const text_chunk = text[i .. i + clamped_width];
                        try writer.writeAll(text_chunk);
                        i += clamped_width;
                        try writer.writeAll(remain[0..j]);
                        i += j;
                    } else {
                        try writer.writeAll(text[i..]);
                        i = n;
                        break;
                    }
                },
                .latin1, .utf16 => {
                    var codepoint_bytes: [4]u8 = undefined;
                    const codepoint_len = strings.encodeWTF8Rune(codepoint_bytes[0..4], c);
                    try writer.writeAll(codepoint_bytes[0..codepoint_len]);
                    i += clamped_width;
                },
            }
            continue;
        }
        switch (c) {
            0x07 => {
                try writer.writeAll("\\x07");
                i += 1;
            },
            0x08 => {
                try writer.writeAll("\\b");
                i += 1;
            },
            0x0C => {
                try writer.writeAll("\\f");
                i += 1;
            },
            '\n' => {
                if (quote_char == '`') {
                    try writer.writeAll("\n");
                } else {
                    try writer.writeAll("\\n");
                }
                i += 1;
            },
            std.ascii.control_code.cr => {
                try writer.writeAll("\\r");
                i += 1;
            },
            // \v
            std.ascii.control_code.vt => {
                try writer.writeAll("\\v");
                i += 1;
            },
            // "\\"
            '\\' => {
                try writer.writeAll("\\\\");
                i += 1;
            },
            '"' => {
                if (quote_char == '"') {
                    try writer.writeAll("\\\"");
                } else {
                    try writer.writeAll("\"");
                }
                i += 1;
            },
            '\'' => {
                if (quote_char == '\'') {
                    try writer.writeAll("\\'");
                } else {
                    try writer.writeAll("'");
                }
                i += 1;
            },
            '`' => {
                if (quote_char == '`') {
                    try writer.writeAll("\\`");
                } else {
                    try writer.writeAll("`");
                }
                i += 1;
            },
            '$' => {
                if (quote_char == '`') {
                    const remain = text[i + clamped_width ..];
                    if (remain.len > 0 and remain[0] == '{') {
                        try writer.writeAll("\\$");
                    } else {
                        try writer.writeAll("$");
                    }
                } else {
                    try writer.writeAll("$");
                }
                i += 1;
            },

            '\t' => {
                if (quote_char == '`') {
                    try writer.writeAll("\t");
                } else {
                    try writer.writeAll("\\t");
                }
                i += 1;
            },

            else => {
                i += @as(usize, width);

                if (c <= 0xFF and !json) {
                    const k = @as(usize, @intCast(c));

                    try writer.writeAll(&[_]u8{
                        '\\',
                        'x',
                        hex_chars[(k >> 4) & 0xF],
                        hex_chars[k & 0xF],
                    });
                } else if (c <= 0xFFFF) {
                    const k = @as(usize, @intCast(c));

                    try writer.writeAll(&[_]u8{
                        '\\',
                        'u',
                        hex_chars[(k >> 12) & 0xF],
                        hex_chars[(k >> 8) & 0xF],
                        hex_chars[(k >> 4) & 0xF],
                        hex_chars[k & 0xF],
                    });
                } else {
                    const k = c - 0x10000;
                    const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
                    const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));

                    try writer.writeAll(&[_]u8{
                        '\\',
                        'u',
                        hex_chars[lo >> 12],
                        hex_chars[(lo >> 8) & 15],
                        hex_chars[(lo >> 4) & 15],
                        hex_chars[lo & 15],
                        '\\',
                        'u',
                        hex_chars[hi >> 12],
                        hex_chars[(hi >> 8) & 15],
                        hex_chars[(hi >> 4) & 15],
                        hex_chars[hi & 15],
                    });
                }
            },
        }
    }
}
pub fn quoteForJSONBuffer(text: []const u8, bytes: *MutableString, comptime ascii_only: bool) !void {
    const writer = bytes.writer();

    try bytes.growIfNeeded(estimateLengthForUTF8(text, ascii_only, '"'));
    try bytes.appendChar('"');
    try writePreQuotedString(text, @TypeOf(writer), writer, '"', ascii_only, true, .utf8);
    bytes.appendChar('"') catch unreachable;
}

pub fn writeJSONString(input: []const u8, comptime Writer: type, writer: Writer, comptime encoding: strings.Encoding) !void {
    try writer.writeAll("\"");
    try writePreQuotedString(input, Writer, writer, '"', false, true, encoding);
    try writer.writeAll("\"");
}

pub const SourceMapHandler = struct {
    ctx: *anyopaque,
    callback: Callback,

    const Callback = *const fn (*anyopaque, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void;
    pub fn onSourceMapChunk(self: *const @This(), chunk: SourceMap.Chunk, source: logger.Source) anyerror!void {
        try self.callback(self.ctx, chunk, source);
    }

    pub fn For(comptime Type: type, comptime handler: (fn (t: *Type, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void)) type {
        return struct {
            pub fn onChunk(self: *anyopaque, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void {
                try handler(@as(*Type, @ptrCast(@alignCast(self))), chunk, source);
            }

            pub fn init(self: *Type) SourceMapHandler {
                return SourceMapHandler{ .ctx = self, .callback = onChunk };
            }
        };
    }
};

pub const Options = struct {
    bundling: bool = false,
    transform_imports: bool = true,
    to_commonjs_ref: Ref = Ref.None,
    to_esm_ref: Ref = Ref.None,
    require_ref: ?Ref = null,
    import_meta_ref: Ref = Ref.None,
    hmr_ref: Ref = Ref.None,
    indent: Indentation = .{},
    runtime_imports: runtime.Runtime.Imports = runtime.Runtime.Imports{},
    module_hash: u32 = 0,
    source_path: ?fs.Path = null,
    allocator: std.mem.Allocator = default_allocator,
    source_map_allocator: ?std.mem.Allocator = null,
    source_map_handler: ?SourceMapHandler = null,
    source_map_builder: ?*bun.sourcemap.Chunk.Builder = null,
    css_import_behavior: Api.CssInJsBehavior = Api.CssInJsBehavior.facade,
    target: options.Target = .browser,

    runtime_transpiler_cache: ?*bun.JSC.RuntimeTranspilerCache = null,
    input_files_for_dev_server: ?[]logger.Source = null,

    commonjs_named_exports: js_ast.Ast.CommonJSNamedExports = .{},
    commonjs_named_exports_deoptimized: bool = false,
    commonjs_module_exports_assigned_deoptimized: bool = false,
    commonjs_named_exports_ref: Ref = Ref.None,
    commonjs_module_ref: Ref = Ref.None,

    minify_whitespace: bool = false,
    minify_identifiers: bool = false,
    minify_syntax: bool = false,
    print_dce_annotations: bool = true,

    transform_only: bool = false,
    inline_require_and_import_errors: bool = true,
    has_run_symbol_renamer: bool = false,

    require_or_import_meta_for_source_callback: RequireOrImportMeta.Callback = .{},

    module_type: options.Format = .esm,

    // /// Used for cross-module inlining of import items when bundling
    // const_values: Ast.ConstValuesMap = .{},
    ts_enums: Ast.TsEnumsMap = .{},

    // If we're writing out a source map, this table of line start indices lets
    // us do binary search on to figure out what line a given AST node came from
    line_offset_tables: ?SourceMap.LineOffsetTable.List = null,

    mangled_props: ?*const bun.bundle_v2.MangledProps,

    // Default indentation is 2 spaces
    pub const Indentation = struct {
        scalar: usize = 2,
        count: usize = 0,
        character: Character = .space,

        pub const Character = enum { tab, space };
    };

    pub fn requireOrImportMetaForSource(
        self: *const Options,
        id: u32,
        was_unwrapped_require: bool,
    ) RequireOrImportMeta {
        if (self.require_or_import_meta_for_source_callback.ctx == null)
            return .{};

        return self.require_or_import_meta_for_source_callback.call(id, was_unwrapped_require);
    }
};

pub const RequireOrImportMeta = struct {
    // CommonJS files will return the "require_*" wrapper function and an invalid
    // exports object reference. Lazily-initialized ESM files will return the
    // "init_*" wrapper function and the exports object for that file.
    wrapper_ref: Ref = Ref.None,
    exports_ref: Ref = Ref.None,
    is_wrapper_async: bool = false,
    was_unwrapped_require: bool = false,

    pub const Callback = struct {
        const Fn = fn (*anyopaque, u32, bool) RequireOrImportMeta;

        ctx: ?*anyopaque = null,
        callback: *const Fn = undefined,

        pub fn call(self: Callback, id: u32, was_unwrapped_require: bool) RequireOrImportMeta {
            return self.callback(self.ctx.?, id, was_unwrapped_require);
        }

        pub fn init(
            comptime Type: type,
            comptime callback: (fn (t: *Type, id: u32, was_unwrapped_require: bool) RequireOrImportMeta),
            ctx: *Type,
        ) Callback {
            return Callback{
                .ctx = bun.cast(*anyopaque, ctx),
                .callback = @as(*const Fn, @ptrCast(&callback)),
            };
        }
    };
};

pub const PrintResult = union(enum) {
    result: struct {
        code: []u8,
        source_map: ?SourceMap.Chunk = null,
    },
    err: anyerror,

    pub fn clone(
        this: PrintResult,
        allocator: std.mem.Allocator,
    ) !PrintResult {
        return switch (this) {
            .result => PrintResult{
                .result = .{
                    .code = try allocator.dupe(u8, this.result.code),
                    .source_map = this.result.source_map,
                },
            },
            .err => PrintResult{
                .err = this.err,
            },
        };
    }
};

// do not make this a packed struct
// stage1 compiler bug:
// > /optional-chain-with-function.js: Evaluation failed: TypeError: (intermediate value) is not a function
// this test failure was caused by the packed struct implementation
const ExprFlag = enum {
    forbid_call,
    forbid_in,
    has_non_optional_chain_parent,
    expr_result_is_unused,
    pub const Set = std.enums.EnumSet(ExprFlag);

    pub fn None() ExprFlag.Set {
        return Set{};
    }

    pub fn ForbidCall() ExprFlag.Set {
        return Set.init(.{ .forbid_call = true });
    }

    pub fn ForbidAnd() ExprFlag.Set {
        return Set.init(.{ .forbid_and = true });
    }

    pub fn HasNonOptionalChainParent() ExprFlag.Set {
        return Set.init(.{ .has_non_optional_chain_parent = true });
    }

    pub fn ExprResultIsUnused() ExprFlag.Set {
        return Set.init(.{ .expr_result_is_unused = true });
    }
};

const ImportVariant = enum {
    path_only,
    import_star,
    import_default,
    import_star_and_import_default,
    import_items,
    import_items_and_default,
    import_items_and_star,
    import_items_and_default_and_star,

    pub inline fn hasItems(import_variant: @This()) @This() {
        return switch (import_variant) {
            .import_default => .import_items_and_default,
            .import_star => .import_items_and_star,
            .import_star_and_import_default => .import_items_and_default_and_star,
            else => .import_items,
        };
    }

    // We always check star first so don't need to be exhaustive here
    pub inline fn hasStar(import_variant: @This()) @This() {
        return switch (import_variant) {
            .path_only => .import_star,
            else => import_variant,
        };
    }

    // We check default after star
    pub inline fn hasDefault(import_variant: @This()) @This() {
        return switch (import_variant) {
            .path_only => .import_default,
            .import_star => .import_star_and_import_default,
            else => import_variant,
        };
    }

    pub fn determine(record: *const ImportRecord, s_import: *const S.Import) ImportVariant {
        var variant = ImportVariant.path_only;

        if (record.contains_import_star) {
            variant = variant.hasStar();
        }

        if (!record.was_originally_bare_import) {
            if (!record.contains_default_alias) {
                if (s_import.default_name) |default_name| {
                    if (default_name.ref != null) {
                        variant = variant.hasDefault();
                    }
                }
            } else {
                variant = variant.hasDefault();
            }
        }

        if (s_import.items.len > 0) {
            variant = variant.hasItems();
        }

        return variant;
    }
};

fn NewPrinter(
    comptime ascii_only: bool,
    comptime Writer: type,
    comptime rewrite_esm_to_cjs: bool,
    comptime is_bun_platform: bool,
    comptime is_json: bool,
    comptime generate_source_map: bool,
) type {
    return struct {
        import_records: []const ImportRecord,

        needs_semicolon: bool = false,
        stmt_start: i32 = -1,
        options: Options,
        export_default_start: i32 = -1,
        arrow_expr_start: i32 = -1,
        for_of_init_start: i32 = -1,
        prev_op: Op.Code = Op.Code.bin_add,
        prev_op_end: i32 = -1,
        prev_num_end: i32 = -1,
        prev_reg_exp_end: i32 = -1,
        call_target: ?Expr.Data = null,
        writer: Writer,

        has_printed_bundled_import_statement: bool = false,

        renamer: rename.Renamer,
        prev_stmt_tag: Stmt.Tag = .s_empty,
        source_map_builder: SourceMap.Chunk.Builder = undefined,

        symbol_counter: u32 = 0,

        temporary_bindings: std.ArrayListUnmanaged(B.Property) = .{},

        binary_expression_stack: std.ArrayList(BinaryExpressionVisitor) = undefined,

        was_lazy_export: bool = false,

        const Printer = @This();

        /// When Printer is used as a io.Writer, this represents it's error type, aka nothing.
        pub const Error = error{};

        /// The handling of binary expressions is convoluted because we're using
        /// iteration on the heap instead of recursion on the call stack to avoid
        /// stack overflow for deeply-nested ASTs. See the comments for the similar
        /// code in the JavaScript parser for details.
        pub const BinaryExpressionVisitor = struct {
            // Inputs
            e: *E.Binary,
            level: Level = .lowest,
            flags: ExprFlag.Set = ExprFlag.None(),

            // Input for visiting the left child
            left_level: Level = .lowest,
            left_flags: ExprFlag.Set = ExprFlag.None(),

            // "Local variables" passed from "checkAndPrepare" to "visitRightAndFinish"
            entry: *const Op = undefined,
            wrap: bool = false,
            right_level: Level = .lowest,

            pub fn checkAndPrepare(v: *BinaryExpressionVisitor, p: *Printer) bool {
                var e = v.e;

                const entry: *const Op = Op.Table.getPtrConst(e.op);
                const e_level = entry.level;
                v.entry = entry;
                v.wrap = v.level.gte(e_level) or (e.op == Op.Code.bin_in and v.flags.contains(.forbid_in));

                // Destructuring assignments must be parenthesized
                const n = p.writer.written;
                if (n == p.stmt_start or n == p.arrow_expr_start) {
                    switch (e.left.data) {
                        .e_object => {
                            v.wrap = true;
                        },
                        else => {},
                    }
                }

                if (v.wrap) {
                    p.print("(");
                    v.flags.insert(.forbid_in);
                }

                v.left_level = e_level.sub(1);
                v.right_level = e_level.sub(1);
                const left_level = &v.left_level;
                const right_level = &v.right_level;

                if (e.op.isRightAssociative()) {
                    left_level.* = e_level;
                }

                if (e.op.isLeftAssociative()) {
                    right_level.* = e_level;
                }

                switch (e.op) {
                    // "??" can't directly contain "||" or "&&" without being wrapped in parentheses
                    .bin_nullish_coalescing => {
                        switch (e.left.data) {
                            .e_binary => {
                                const left = e.left.data.e_binary;
                                switch (left.op) {
                                    .bin_logical_and, .bin_logical_or => {
                                        left_level.* = .prefix;
                                    },
                                    else => {},
                                }
                            },
                            else => {},
                        }

                        switch (e.right.data) {
                            .e_binary => {
                                const right = e.right.data.e_binary;
                                switch (right.op) {
                                    .bin_logical_and, .bin_logical_or => {
                                        right_level.* = .prefix;
                                    },
                                    else => {},
                                }
                            },
                            else => {},
                        }
                    },
                    // "**" can't contain certain unary expressions
                    .bin_pow => {
                        switch (e.left.data) {
                            .e_unary => {
                                const left = e.left.data.e_unary;
                                if (left.op.unaryAssignTarget() == .none) {
                                    left_level.* = .call;
                                }
                            },
                            .e_await, .e_undefined, .e_number => {
                                left_level.* = .call;
                            },
                            .e_boolean => {
                                // When minifying, booleans are printed as "!0 and "!1"
                                if (p.options.minify_syntax) {
                                    left_level.* = .call;
                                }
                            },
                            else => {},
                        }
                    },
                    else => {},
                }

                // Special-case "#foo in bar"
                if (e.left.data == .e_private_identifier and e.op == .bin_in) {
                    const private = e.left.data.e_private_identifier;
                    const name = p.renamer.nameForSymbol(private.ref);
                    p.addSourceMappingForName(e.left.loc, name, private.ref);
                    p.printIdentifier(name);
                    v.visitRightAndFinish(p);
                    return false;
                }

                v.left_flags = ExprFlag.Set{};

                if (v.flags.contains(.forbid_in)) {
                    v.left_flags.insert(.forbid_in);
                }

                if (e.op == .bin_comma)
                    v.left_flags.insert(.expr_result_is_unused);

                return true;
            }
            pub fn visitRightAndFinish(v: *BinaryExpressionVisitor, p: *Printer) void {
                const e = v.e;
                const entry = v.entry;
                var flags = ExprFlag.Set{};

                if (e.op != .bin_comma) {
                    p.printSpace();
                }

                if (entry.is_keyword) {
                    p.printSpaceBeforeIdentifier();
                    p.print(entry.text);
                } else {
                    p.printSpaceBeforeOperator(e.op);
                    p.print(entry.text);
                    p.prev_op = e.op;
                    p.prev_op_end = p.writer.written;
                }

                p.printSpace();

                // The result of the right operand of the comma operator is unused if the caller doesn't use it
                if (e.op == .bin_comma and v.flags.contains(.expr_result_is_unused)) {
                    flags.insert(.expr_result_is_unused);
                }

                if (v.flags.contains(.forbid_in)) {
                    flags.insert(.forbid_in);
                }

                p.printExpr(e.right, v.right_level, flags);

                if (v.wrap) {
                    p.print(")");
                }
            }
        };

        pub fn writeAll(p: *Printer, bytes: anytype) anyerror!void {
            p.print(bytes);
        }

        pub fn writeByteNTimes(self: *Printer, byte: u8, n: usize) !void {
            var bytes: [256]u8 = undefined;
            @memset(bytes[0..], byte);

            var remaining: usize = n;
            while (remaining > 0) {
                const to_write = @min(remaining, bytes.len);
                try self.writeAll(bytes[0..to_write]);
                remaining -= to_write;
            }
        }

        pub fn writeBytesNTimes(self: *Printer, bytes: []const u8, n: usize) anyerror!void {
            var i: usize = 0;
            while (i < n) : (i += 1) {
                try self.writeAll(bytes);
            }
        }

        fn fmt(p: *Printer, comptime str: string, args: anytype) !void {
            const len = @call(
                .always_inline,
                std.fmt.count,
                .{ str, args },
            );
            var ptr = try p.writer.reserve(len);

            const written = @call(
                .always_inline,
                std.fmt.bufPrint,
                .{ ptr[0..len], str, args },
            ) catch unreachable;

            p.writer.advance(written.len);
        }

        pub fn printBuffer(p: *Printer, str: []const u8) void {
            p.writer.print([]const u8, str);
        }

        pub fn print(p: *Printer, str: anytype) void {
            const StringType = @TypeOf(str);
            switch (comptime StringType) {
                comptime_int, u16, u8 => {
                    p.writer.print(StringType, str);
                },
                [6]u8 => {
                    const span = str[0..6];
                    p.writer.print(@TypeOf(span), span);
                },
                else => {
                    p.writer.print(StringType, str);
                },
            }
        }

        pub inline fn unindent(p: *Printer) void {
            p.options.indent.count -|= 1;
        }

        pub inline fn indent(p: *Printer) void {
            p.options.indent.count += 1;
        }

        pub fn printIndent(p: *Printer) void {
            if (p.options.indent.count == 0 or p.options.minify_whitespace) {
                return;
            }

            const indentation_buf = switch (p.options.indent.character) {
                .space => indentation_space_buf,
                .tab => indentation_tab_buf,
            };

            var i: usize = p.options.indent.count * p.options.indent.scalar;

            while (i > 0) {
                const amt = @min(i, indentation_buf.len);
                p.print(indentation_buf[0..amt]);
                i -= amt;
            }
        }

        pub fn mangledPropName(p: *Printer, _ref: Ref) string {
            const ref = p.symbols().follow(_ref);
            // TODO: we don't support that
            if (p.options.mangled_props != null) {
                if (p.options.mangled_props.?.get(ref)) |name| return name;
            }
            return p.renamer.nameForSymbol(ref);
        }

        pub inline fn printSpace(p: *Printer) void {
            if (!p.options.minify_whitespace)
                p.print(" ");
        }
        pub inline fn printNewline(p: *Printer) void {
            if (!p.options.minify_whitespace)
                p.print("\n");
        }
        pub inline fn printSemicolonAfterStatement(p: *Printer) void {
            if (!p.options.minify_whitespace) {
                p.print(";\n");
            } else {
                p.needs_semicolon = true;
            }
        }
        pub fn printSemicolonIfNeeded(p: *Printer) void {
            if (p.needs_semicolon) {
                p.print(";");
                p.needs_semicolon = false;
            }
        }

        fn @"print = "(p: *Printer) void {
            if (p.options.minify_whitespace) {
                p.print("=");
            } else {
                p.print(" = ");
            }
        }

        fn printBunJestImportStatement(p: *Printer, import: S.Import) void {
            comptime bun.assert(is_bun_platform);

            switch (p.options.module_type) {
                .cjs => {
                    printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(__filename)"), "globalThis.Bun.jest(__filename)");
                },
                else => {
                    printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(import.meta.path)"), "globalThis.Bun.jest(import.meta.path)");
                },
            }
        }

        fn printGlobalBunImportStatement(p: *Printer, import: S.Import) void {
            if (comptime !is_bun_platform) unreachable;
            printInternalBunImport(p, import, @TypeOf("globalThis.Bun"), "globalThis.Bun");
        }

        fn printInternalBunImport(p: *Printer, import: S.Import, comptime Statement: type, statement: Statement) void {
            if (comptime !is_bun_platform) unreachable;

            if (import.star_name_loc != null) {
                p.print("var ");
                p.printSymbol(import.namespace_ref);
                p.printSpace();
                p.print("=");
                p.printSpaceBeforeIdentifier();
                if (comptime Statement == void) {
                    p.printRequireOrImportExpr(
                        import.import_record_index,
                        false,
                        &.{},
                        Expr.empty,
                        Level.lowest,
                        ExprFlag.None(),
                    );
                } else {
                    p.print(statement);
                }

                p.printSemicolonAfterStatement();
                p.printIndent();
            }

            if (import.default_name) |default| {
                p.print("var ");
                p.printSymbol(default.ref.?);
                if (comptime Statement == void) {
                    p.@"print = "();
                    p.printRequireOrImportExpr(
                        import.import_record_index,
                        false,
                        &.{},
                        Expr.empty,
                        Level.lowest,
                        ExprFlag.None(),
                    );
                } else {
                    p.@"print = "();
                    p.print(statement);
                }
                p.printSemicolonAfterStatement();
            }

            if (import.items.len > 0) {
                p.printWhitespacer(ws("var {"));

                if (!import.is_single_line) {
                    p.printNewline();
                    p.indent();
                    p.printIndent();
                }

                for (import.items, 0..) |item, i| {
                    if (i > 0) {
                        p.print(",");
                        p.printSpace();

                        if (!import.is_single_line) {
                            p.printNewline();
                            p.printIndent();
                        }
                    }

                    p.printClauseItemAs(item, .@"var");
                }

                if (!import.is_single_line) {
                    p.printNewline();
                    p.unindent();
                } else {
                    p.printSpace();
                }

                p.printWhitespacer(ws("} = "));

                if (import.star_name_loc == null and import.default_name == null) {
                    if (comptime Statement == void) {
                        p.printRequireOrImportExpr(import.import_record_index, false, &.{}, Expr.empty, Level.lowest, ExprFlag.None());
                    } else {
                        p.print(statement);
                    }
                } else if (import.default_name) |name| {
                    p.printSymbol(name.ref.?);
                } else {
                    p.printSymbol(import.namespace_ref);
                }

                p.printSemicolonAfterStatement();
            }
        }

        pub inline fn printSpaceBeforeIdentifier(
            p: *Printer,
        ) void {
            if (p.writer.written > 0 and (js_lexer.isIdentifierContinue(@as(i32, p.writer.prevChar())) or p.writer.written == p.prev_reg_exp_end)) {
                p.print(" ");
            }
        }

        pub inline fn maybePrintSpace(
            p: *Printer,
        ) void {
            switch (p.writer.prevChar()) {
                0, ' ', '\n' => {},
                else => {
                    p.print(" ");
                },
            }
        }
        pub fn printDotThenPrefix(p: *Printer) Level {
            p.print(".then(() => ");
            return .comma;
        }

        pub inline fn printUndefined(p: *Printer, loc: logger.Loc, level: Level) void {
            if (p.options.minify_syntax) {
                if (level.gte(Level.prefix)) {
                    p.addSourceMapping(loc);
                    p.print("(void 0)");
                } else {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(loc);
                    p.print("void 0");
                }
            } else {
                p.printSpaceBeforeIdentifier();
                p.addSourceMapping(loc);
                p.print("undefined");
            }
        }

        pub fn printBody(p: *Printer, stmt: Stmt) void {
            switch (stmt.data) {
                .s_block => |block| {
                    p.printSpace();
                    p.printBlock(stmt.loc, block.stmts, block.close_brace_loc);
                    p.printNewline();
                },
                else => {
                    p.printNewline();
                    p.indent();
                    p.printStmt(stmt) catch unreachable;
                    p.unindent();
                },
            }
        }

        pub fn printBlockBody(p: *Printer, stmts: []const Stmt) void {
            for (stmts) |stmt| {
                p.printSemicolonIfNeeded();
                p.printStmt(stmt) catch unreachable;
            }
        }

        pub fn printBlock(p: *Printer, loc: logger.Loc, stmts: []const Stmt, close_brace_loc: ?logger.Loc) void {
            p.addSourceMapping(loc);
            p.print("{");
            if (stmts.len > 0) {
                @branchHint(.likely);
                p.printNewline();

                p.indent();
                p.printBlockBody(stmts);
                p.unindent();

                p.printIndent();
            }
            if (close_brace_loc != null and close_brace_loc.?.start > loc.start) {
                p.addSourceMapping(close_brace_loc.?);
            }
            p.print("}");

            p.needs_semicolon = false;
        }

        pub fn printTwoBlocksInOne(p: *Printer, loc: logger.Loc, stmts: []const Stmt, prepend: []const Stmt) void {
            p.addSourceMapping(loc);
            p.print("{");
            p.printNewline();

            p.indent();
            p.printBlockBody(prepend);
            p.printBlockBody(stmts);
            p.unindent();
            p.needs_semicolon = false;

            p.printIndent();
            p.print("}");
        }

        pub fn printDecls(p: *Printer, comptime keyword: string, decls_: []G.Decl, flags: ExprFlag.Set) void {
            p.print(keyword);
            p.printSpace();
            var decls = decls_;

            if (decls.len == 0) {
                // "var ;" is invalid syntax
                // assert we never reach it
                unreachable;
            }

            if (comptime FeatureFlags.same_target_becomes_destructuring) {
                // Minify
                //
                //    var a = obj.foo, b = obj.bar, c = obj.baz;
                //
                // to
                //
                //    var {a, b, c} = obj;
                //
                // Caveats:
                //   - Same consecutive target
                //   - No optional chaining
                //   - No computed property access
                //   - Identifier bindings only
                if (decls.len > 1) brk: {
                    const first_decl = &decls[0];
                    const second_decl = &decls[1];

                    if (first_decl.binding.data != .b_identifier) break :brk;
                    if (second_decl.value == null or
                        second_decl.value.?.data != .e_dot or
                        second_decl.binding.data != .b_identifier)
                    {
                        break :brk;
                    }

                    const target_value = first_decl.value orelse break :brk;
                    const target_e_dot: *E.Dot = if (target_value.data == .e_dot)
                        target_value.data.e_dot
                    else
                        break :brk;
                    const target_ref = if (target_e_dot.target.data == .e_identifier and target_e_dot.optional_chain == null)
                        target_e_dot.target.data.e_identifier.ref
                    else
                        break :brk;

                    const second_e_dot = second_decl.value.?.data.e_dot;
                    if (second_e_dot.target.data != .e_identifier or second_e_dot.optional_chain != null) {
                        break :brk;
                    }

                    const second_ref = second_e_dot.target.data.e_identifier.ref;
                    if (!second_ref.eql(target_ref)) {
                        break :brk;
                    }

                    {
                        // Reset the temporary bindings array early on
                        var temp_bindings = p.temporary_bindings;
                        p.temporary_bindings = .{};
                        defer {
                            if (p.temporary_bindings.capacity > 0) {
                                temp_bindings.deinit(bun.default_allocator);
                            } else {
                                temp_bindings.clearRetainingCapacity();
                                p.temporary_bindings = temp_bindings;
                            }
                        }
                        temp_bindings.ensureUnusedCapacity(bun.default_allocator, 2) catch unreachable;
                        temp_bindings.appendAssumeCapacity(.{
                            .key = Expr.init(E.String, E.String.init(target_e_dot.name), target_e_dot.name_loc),
                            .value = decls[0].binding,
                        });
                        temp_bindings.appendAssumeCapacity(.{
                            .key = Expr.init(E.String, E.String.init(second_e_dot.name), second_e_dot.name_loc),
                            .value = decls[1].binding,
                        });

                        decls = decls[2..];
                        while (decls.len > 0) {
                            const decl = &decls[0];

                            if (decl.value == null or decl.value.?.data != .e_dot or decl.binding.data != .b_identifier) {
                                break;
                            }

                            const e_dot = decl.value.?.data.e_dot;
                            if (e_dot.target.data != .e_identifier or e_dot.optional_chain != null) {
                                break;
                            }

                            const ref = e_dot.target.data.e_identifier.ref;
                            if (!ref.eql(target_ref)) {
                                break;
                            }

                            temp_bindings.append(bun.default_allocator, .{
                                .key = Expr.init(E.String, E.String.init(e_dot.name), e_dot.name_loc),
                                .value = decl.binding,
                            }) catch unreachable;
                            decls = decls[1..];
                        }
                        var b_object = B.Object{
                            .properties = temp_bindings.items,
                            .is_single_line = true,
                        };
                        const binding = Binding.init(&b_object, target_e_dot.target.loc);
                        p.printBinding(binding);
                    }

                    p.printWhitespacer(ws(" = "));
                    p.printExpr(second_e_dot.target, .comma, flags);

                    if (decls.len == 0) {
                        return;
                    }

                    p.print(",");
                    p.printSpace();
                }
            }

            {
                p.printBinding(decls[0].binding);

                if (decls[0].value) |value| {
                    p.printWhitespacer(ws(" = "));
                    p.printExpr(value, .comma, flags);
                }
            }

            for (decls[1..]) |*decl| {
                p.print(",");
                p.printSpace();

                p.printBinding(decl.binding);

                if (decl.value) |value| {
                    p.printWhitespacer(ws(" = "));
                    p.printExpr(value, .comma, flags);
                }
            }
        }

        pub inline fn addSourceMapping(printer: *Printer, location: logger.Loc) void {
            if (comptime !generate_source_map) {
                return;
            }
            printer.source_map_builder.addSourceMapping(location, printer.writer.slice());
        }

        pub inline fn addSourceMappingForName(printer: *Printer, location: logger.Loc, _: string, _: Ref) void {
            if (comptime !generate_source_map) {
                return;
            }
            // TODO: esbuild does this to make the source map more accurate with E.NameOfSymbol
            // if (printer.symbols().get(printer.symbols().follow(ref))) |original_symbol| {
            //     if (!bun.strings.eql( original_symbol.original_name, name)) {
            //         printer.source_map_builder.addSourceMapping(location, originalName);
            //         return;
            //     }
            // }
            printer.addSourceMapping(location);
        }

        pub fn printSymbol(p: *Printer, ref: Ref) void {
            bun.assert(!ref.isNull()); // Invalid Symbol
            const name = p.renamer.nameForSymbol(ref);

            p.printIdentifier(name);
        }
        pub fn printClauseAlias(p: *Printer, alias: string) void {
            bun.assert(alias.len > 0);

            if (!strings.containsNonBmpCodePointOrIsInvalidIdentifier(alias)) {
                p.printSpaceBeforeIdentifier();
                p.printIdentifier(alias);
            } else {
                p.printStringLiteralUTF8(alias, false);
            }
        }

        pub fn printFnArgs(
            p: *Printer,
            open_paren_loc: ?logger.Loc,
            args: []G.Arg,
            has_rest_arg: bool,
            // is_arrow can be used for minifying later
            _: bool,
        ) void {
            const wrap = true;

            if (wrap) {
                if (open_paren_loc) |loc| {
                    p.addSourceMapping(loc);
                }
                p.print("(");
            }

            for (args, 0..) |arg, i| {
                if (i != 0) {
                    p.print(",");
                    p.printSpace();
                }

                if (has_rest_arg and i + 1 == args.len) {
                    p.print("...");
                }

                p.printBinding(arg.binding);

                if (arg.default) |default| {
                    p.printWhitespacer(ws(" = "));
                    p.printExpr(default, .comma, ExprFlag.None());
                }
            }

            if (wrap) {
                p.print(")");
            }
        }

        pub fn printFunc(p: *Printer, func: G.Fn) void {
            p.printFnArgs(func.open_parens_loc, func.args, func.flags.contains(.has_rest_arg), false);
            p.printSpace();
            p.printBlock(func.body.loc, func.body.stmts, null);
        }

        pub fn printClass(p: *Printer, class: G.Class) void {
            if (class.extends) |extends| {
                p.print(" extends");
                p.printSpace();
                p.printExpr(extends, Level.new.sub(1), ExprFlag.None());
            }

            p.printSpace();

            p.addSourceMapping(class.body_loc);
            p.print("{");
            p.printNewline();
            p.indent();

            for (class.properties) |item| {
                p.printSemicolonIfNeeded();
                p.printIndent();

                if (item.kind == .class_static_block) {
                    p.print("static");
                    p.printSpace();
                    p.printBlock(item.class_static_block.?.loc, item.class_static_block.?.stmts.slice(), null);
                    p.printNewline();
                    continue;
                }

                p.printProperty(item);

                if (item.value == null) {
                    p.printSemicolonAfterStatement();
                } else {
                    p.printNewline();
                }
            }

            p.needs_semicolon = false;
            p.unindent();
            p.printIndent();
            if (class.close_brace_loc.start > class.body_loc.start)
                p.addSourceMapping(class.close_brace_loc);
            p.print("}");
        }

        pub fn bestQuoteCharForEString(str: *const E.String, allow_backtick: bool) u8 {
            if (comptime is_json)
                return '"';

            if (str.isUTF8()) {
                return bestQuoteCharForString(u8, str.data, allow_backtick);
            } else {
                return bestQuoteCharForString(u16, str.slice16(), allow_backtick);
            }
        }

        pub fn printWhitespacer(this: *Printer, spacer: Whitespacer) void {
            if (this.options.minify_whitespace) {
                this.print(spacer.minify);
            } else {
                this.print(spacer.normal);
            }
        }

        pub fn printNonNegativeFloat(p: *Printer, float: f64) void {
            // Is this actually an integer?
            @setRuntimeSafety(false);
            const floored: f64 = @floor(float);
            const remainder: f64 = (float - floored);
            const is_integer = remainder == 0;
            if (float < std.math.maxInt(u52) and is_integer) {
                @setFloatMode(.optimized);
                // In JavaScript, numbers are represented as 64 bit floats
                // However, they could also be signed or unsigned int 32 (when doing bit shifts)
                // In this case, it's always going to unsigned since that conversion has already happened.
                const val = @as(u64, @intFromFloat(float));
                switch (val) {
                    0 => {
                        p.print("0");
                    },
                    1...9 => {
                        var bytes = [1]u8{'0' + @as(u8, @intCast(val))};
                        p.print(&bytes);
                    },
                    10 => {
                        p.print("10");
                    },
                    11...99 => {
                        const buf: *[2]u8 = (p.writer.reserve(2) catch unreachable)[0..2];
                        formatUnsignedIntegerBetween(2, buf, val);
                        p.writer.advance(2);
                    },
                    100 => {
                        p.print("100");
                    },
                    101...999 => {
                        const buf: *[3]u8 = (p.writer.reserve(3) catch unreachable)[0..3];
                        formatUnsignedIntegerBetween(3, buf, val);
                        p.writer.advance(3);
                    },

                    1000 => {
                        p.print("1000");
                    },
                    1001...9999 => {
                        const buf: *[4]u8 = (p.writer.reserve(4) catch unreachable)[0..4];
                        formatUnsignedIntegerBetween(4, buf, val);
                        p.writer.advance(4);
                    },
                    10000 => {
                        p.print("1e4");
                    },
                    100000 => {
                        p.print("1e5");
                    },
                    1000000 => {
                        p.print("1e6");
                    },
                    10000000 => {
                        p.print("1e7");
                    },
                    100000000 => {
                        p.print("1e8");
                    },
                    1000000000 => {
                        p.print("1e9");
                    },

                    10001...99999 => {
                        const buf: *[5]u8 = (p.writer.reserve(5) catch unreachable)[0..5];
                        formatUnsignedIntegerBetween(5, buf, val);
                        p.writer.advance(5);
                    },
                    100001...999999 => {
                        const buf: *[6]u8 = (p.writer.reserve(6) catch unreachable)[0..6];
                        formatUnsignedIntegerBetween(6, buf, val);
                        p.writer.advance(6);
                    },
                    1_000_001...9_999_999 => {
                        const buf: *[7]u8 = (p.writer.reserve(7) catch unreachable)[0..7];
                        formatUnsignedIntegerBetween(7, buf, val);
                        p.writer.advance(7);
                    },
                    10_000_001...99_999_999 => {
                        const buf: *[8]u8 = (p.writer.reserve(8) catch unreachable)[0..8];
                        formatUnsignedIntegerBetween(8, buf, val);
                        p.writer.advance(8);
                    },
                    100_000_001...999_999_999 => {
                        const buf: *[9]u8 = (p.writer.reserve(9) catch unreachable)[0..9];
                        formatUnsignedIntegerBetween(9, buf, val);
                        p.writer.advance(9);
                    },
                    1_000_000_001...9_999_999_999 => {
                        const buf: *[10]u8 = (p.writer.reserve(10) catch unreachable)[0..10];
                        formatUnsignedIntegerBetween(10, buf, val);
                        p.writer.advance(10);
                    },
                    else => std.fmt.formatInt(val, 10, .lower, .{}, p) catch unreachable,
                }

                return;
            }

            p.fmt("{d}", .{float}) catch {};
        }

        pub fn printStringCharactersUTF8(e: *Printer, text: []const u8, quote: u8) void {
            const writer = e.writer.stdWriter();
            (switch (quote) {
                '\'' => writePreQuotedString(text, @TypeOf(writer), writer, '\'', ascii_only, false, .utf8),
                '"' => writePreQuotedString(text, @TypeOf(writer), writer, '"', ascii_only, false, .utf8),
                '`' => writePreQuotedString(text, @TypeOf(writer), writer, '`', ascii_only, false, .utf8),
                else => unreachable,
            }) catch |err| switch (err) {};
        }
        pub fn printStringCharactersUTF16(e: *Printer, text: []const u16, quote: u8) void {
            const slice = std.mem.sliceAsBytes(text);

            const writer = e.writer.stdWriter();
            (switch (quote) {
                '\'' => writePreQuotedString(slice, @TypeOf(writer), writer, '\'', ascii_only, false, .utf16),
                '"' => writePreQuotedString(slice, @TypeOf(writer), writer, '"', ascii_only, false, .utf16),
                '`' => writePreQuotedString(slice, @TypeOf(writer), writer, '`', ascii_only, false, .utf16),
                else => unreachable,
            }) catch |err| switch (err) {};
        }

        pub fn isUnboundEvalIdentifier(p: *Printer, value: Expr) bool {
            switch (value.data) {
                .e_identifier => |ident| {
                    if (ident.ref.isSourceContentsSlice()) return false;

                    const symbol = p.symbols().get(p.symbols().follow(ident.ref)) orelse return false;
                    return symbol.kind == .unbound and strings.eqlComptime(symbol.original_name, "eval");
                },
                else => {
                    return false;
                },
            }
        }

        inline fn symbols(p: *Printer) js_ast.Symbol.Map {
            return p.renamer.symbols();
        }

        pub fn printRequireError(p: *Printer, text: string) void {
            p.print("(()=>{throw new Error(\"Cannot require module \"+");
            p.printStringLiteralUTF8(text, false);
            p.print(");})()");
        }

        pub inline fn importRecord(
            p: *const Printer,
            import_record_index: usize,
        ) *const ImportRecord {
            return &p.import_records[import_record_index];
        }

        pub fn printRequireOrImportExpr(
            p: *Printer,
            import_record_index: u32,
            was_unwrapped_require: bool,
            leading_interior_comments: []G.Comment,
            import_options: Expr,
            level_: Level,
            flags: ExprFlag.Set,
        ) void {
            _ = leading_interior_comments; // TODO:

            var level = level_;
            const wrap = level.gte(.new) or flags.contains(.forbid_call);
            if (wrap) p.print("(");
            defer if (wrap) p.print(")");

            assert(p.import_records.len > import_record_index);
            const record = p.importRecord(import_record_index);
            const module_type = p.options.module_type;

            if (comptime is_bun_platform) {
                // "bun" is not a real module. It's just globalThis.Bun.
                //
                //  transform from:
                //      const foo = await import("bun")
                //      const bar = require("bun")
                //
                //  transform to:
                //      const foo = await Promise.resolve(globalThis.Bun)
                //      const bar = globalThis.Bun
                //
                switch (record.tag) {
                    .bun => {
                        if (record.kind == .dynamic) {
                            p.print("Promise.resolve(globalThis.Bun)");
                            return;
                        } else if (record.kind == .require or record.kind == .stmt) {
                            p.print("globalThis.Bun");
                            return;
                        }
                    },
                    .bun_test => {
                        if (record.kind == .dynamic) {
                            if (module_type == .cjs) {
                                p.print("Promise.resolve(globalThis.Bun.jest(__filename))");
                            } else {
                                p.print("Promise.resolve(globalThis.Bun.jest(import.meta.path))");
                            }
                        } else if (record.kind == .require) {
                            if (module_type == .cjs) {
                                p.print("globalThis.Bun.jest(__filename)");
                            } else {
                                p.print("globalThis.Bun.jest(import.meta.path)");
                            }
                        }
                        return;
                    },
                    else => {},
                }
            }

            if (record.source_index.isValid()) {
                var meta = p.options.requireOrImportMetaForSource(record.source_index.get(), was_unwrapped_require);

                // Don't need the namespace object if the result is unused anyway
                if (flags.contains(.expr_result_is_unused)) {
                    meta.exports_ref = Ref.None;
                }

                // Internal "import()" of async ESM
                if (record.kind == .dynamic and meta.is_wrapper_async) {
                    p.printSpaceBeforeIdentifier();
                    p.printSymbol(meta.wrapper_ref);
                    p.print("()");
                    if (meta.exports_ref.isValid()) {
                        _ = p.printDotThenPrefix();
                        p.printSpaceBeforeIdentifier();
                        p.printSymbol(meta.exports_ref);
                        p.printDotThenSuffix();
                    }
                    return;
                }

                // Internal "require()" or "import()"
                if (record.kind == .dynamic) {
                    p.printSpaceBeforeIdentifier();
                    p.print("Promise.resolve()");

                    level = p.printDotThenPrefix();
                }
                defer if (record.kind == .dynamic) p.printDotThenSuffix();

                // Make sure the comma operator is properly wrapped
                const wrap_comma_operator = meta.exports_ref.isValid() and
                    meta.wrapper_ref.isValid() and
                    level.gte(.comma);
                if (wrap_comma_operator) p.print("(");
                defer if (wrap_comma_operator) p.print(")");

                // Wrap this with a call to "__toESM()" if this is a CommonJS file
                const wrap_with_to_esm = record.wrap_with_to_esm;
                if (wrap_with_to_esm) {
                    p.printSpaceBeforeIdentifier();
                    p.printSymbol(p.options.to_esm_ref);
                    p.print("(");
                }

                if (p.options.input_files_for_dev_server) |input_files| {
                    bun.assert(module_type == .internal_bake_dev);
                    p.printSpaceBeforeIdentifier();
                    p.printSymbol(p.options.hmr_ref);
                    p.print(".require(");
                    const path = input_files[record.source_index.get()].path;
                    p.printStringLiteralUTF8(path.pretty, false);
                    p.print(")");
                } else if (!meta.was_unwrapped_require) {
                    // Call the wrapper
                    if (meta.wrapper_ref.isValid()) {
                        p.printSpaceBeforeIdentifier();
                        p.printSymbol(meta.wrapper_ref);
                        p.print("()");

                        if (meta.exports_ref.isValid()) {
                            p.print(",");
                            p.printSpace();
                        }
                    }

                    // Return the namespace object if this is an ESM file
                    if (meta.exports_ref.isValid()) {
                        // Wrap this with a call to "__toCommonJS()" if this is an ESM file
                        const wrap_with_to_cjs = record.wrap_with_to_commonjs;
                        if (wrap_with_to_cjs) {
                            p.printSymbol(p.options.to_commonjs_ref);
                            p.print("(");
                        }
                        p.printSymbol(meta.exports_ref);
                        if (wrap_with_to_cjs) {
                            p.print(")");
                        }
                    }
                } else {
                    if (!meta.exports_ref.isNull())
                        p.printSymbol(meta.exports_ref);
                }

                if (wrap_with_to_esm) {
                    if (module_type.isESM()) {
                        p.print(",");
                        p.printSpace();
                        p.print("1");
                    }
                    p.print(")");
                }

                return;
            }

            // External "require()"
            if (record.kind != .dynamic) {
                p.printSpaceBeforeIdentifier();

                if (p.options.inline_require_and_import_errors) {
                    if (record.path.is_disabled and record.handles_import_errors) {
                        p.printRequireError(record.path.text);
                        return;
                    }

                    if (record.path.is_disabled) {
                        p.printDisabledImport();
                        return;
                    }
                }

                const wrap_with_to_esm = record.wrap_with_to_esm;

                if (module_type == .internal_bake_dev) {
                    p.printSpaceBeforeIdentifier();
                    p.printSymbol(p.options.hmr_ref);
                    if (record.tag == .builtin)
                        p.print(".builtin(")
                    else
                        p.print(".require(");
                    const path = record.path;
                    p.printStringLiteralUTF8(path.pretty, false);
                    p.print(")");
                    return;
                } else if (wrap_with_to_esm) {
                    p.printSpaceBeforeIdentifier();
                    p.printSymbol(p.options.to_esm_ref);
                    p.print("(");
                }

                if (p.options.require_ref) |ref| {
                    p.printSymbol(ref);
                } else {
                    p.print("require");
                }

                p.print("(");
                p.printImportRecordPath(record);
                p.print(")");

                if (wrap_with_to_esm) {
                    p.print(")");
                }
                return;
            }

            // External import()
            // if (leading_interior_comments.len > 0) {
            //     p.printNewline();
            //     p.indent();
            //     for (leading_interior_comments) |comment| {
            //         p.printIndentedComment(comment.text);
            //     }
            //     p.printIndent();
            // }
            p.addSourceMapping(record.range.loc);

            p.printSpaceBeforeIdentifier();

            // Allow it to fail at runtime, if it should
            if (module_type != .internal_bake_dev) {
                p.print("import(");
                p.printImportRecordPath(record);
            } else {
                p.printSymbol(p.options.hmr_ref);
                p.print(".dynamicImport(");
                const path = record.path;
                p.printStringLiteralUTF8(path.pretty, false);
            }

            if (!import_options.isMissing()) {
                p.printWhitespacer(ws(", "));
                p.printExpr(import_options, .comma, .{});
            }

            p.print(")");

            // if (leading_interior_comments.len > 0) {
            //     p.printNewline();
            //     p.unindent();
            //     p.printIndent();
            // }

            return;
        }

        pub inline fn printPure(p: *Printer) void {
            if (Environment.allow_assert) assert(p.options.print_dce_annotations);
            p.printWhitespacer(ws("/* @__PURE__ */ "));
        }

        pub fn printStringLiteralEString(p: *Printer, str: *E.String, allow_backtick: bool) void {
            const quote = bestQuoteCharForEString(str, allow_backtick);
            p.print(quote);
            p.printStringCharactersEString(str, quote);
            p.print(quote);
        }
        pub fn printStringLiteralUTF8(p: *Printer, str: string, allow_backtick: bool) void {
            if (Environment.allow_assert) std.debug.assert(std.unicode.wtf8ValidateSlice(str));

            const quote = if (comptime !is_json)
                bestQuoteCharForString(u8, str, allow_backtick)
            else
                '"';

            p.print(quote);
            p.printStringCharactersUTF8(str, quote);
            p.print(quote);
        }

        fn printClauseItem(p: *Printer, item: js_ast.ClauseItem) void {
            return printClauseItemAs(p, item, .import);
        }

        fn printExportClauseItem(p: *Printer, item: js_ast.ClauseItem) void {
            return printClauseItemAs(p, item, .@"export");
        }

        fn printClauseItemAs(p: *Printer, item: js_ast.ClauseItem, comptime as: @Type(.enum_literal)) void {
            const name = p.renamer.nameForSymbol(item.name.ref.?);

            if (comptime as == .import) {
                if (strings.eql(name, item.alias)) {
                    p.printIdentifier(name);
                } else {
                    p.printClauseAlias(item.alias);
                    p.print(" as ");
                    p.addSourceMapping(item.alias_loc);
                    p.printIdentifier(name);
                }
            } else if (comptime as == .@"var") {
                p.printClauseAlias(item.alias);

                if (!strings.eql(name, item.alias)) {
                    p.print(":");
                    p.printSpace();

                    p.printIdentifier(name);
                }
            } else if (comptime as == .@"export") {
                p.printIdentifier(name);

                if (!strings.eql(name, item.alias)) {
                    p.print(" as ");
                    p.addSourceMapping(item.alias_loc);
                    p.printClauseAlias(item.alias);
                }
            } else {
                @compileError("Unknown as");
            }
        }

        pub inline fn canPrintIdentifierUTF16(_: *Printer, name: []const u16) bool {
            if (comptime ascii_only or ascii_only_always_on_unless_minifying) {
                return js_lexer.isLatin1Identifier([]const u16, name);
            } else {
                return js_lexer.isIdentifierUTF16(name);
            }
        }

        fn printRawTemplateLiteral(p: *Printer, bytes: []const u8) void {
            if (comptime is_json or !ascii_only) {
                p.print(bytes);
                return;
            }

            // Translate any non-ASCII to unicode escape sequences
            // Note that this does not correctly handle malformed template literal strings
            // template literal strings can contain invalid unicode code points
            // and pretty much anything else
            //
            // we use WTF-8 here, but that's still not good enough.
            //
            var ascii_start: usize = 0;
            var is_ascii = false;
            var iter = CodepointIterator.init(bytes);
            var cursor = CodepointIterator.Cursor{};

            while (iter.next(&cursor)) {
                switch (cursor.c) {
                    // unlike other versions, we only want to mutate > 0x7F
                    0...last_ascii => {
                        if (!is_ascii) {
                            ascii_start = cursor.i;
                            is_ascii = true;
                        }
                    },
                    else => {
                        if (is_ascii) {
                            p.print(bytes[ascii_start..cursor.i]);
                            is_ascii = false;
                        }

                        switch (cursor.c) {
                            0...0xFFFF => {
                                p.print([_]u8{
                                    '\\',
                                    'u',
                                    hex_chars[cursor.c >> 12],
                                    hex_chars[(cursor.c >> 8) & 15],
                                    hex_chars[(cursor.c >> 4) & 15],
                                    hex_chars[cursor.c & 15],
                                });
                            },
                            else => {
                                p.print("\\u{");
                                std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable;
                                p.print("}");
                            },
                        }
                    },
                }
            }

            if (is_ascii) {
                p.print(bytes[ascii_start..]);
            }
        }

        pub fn printExpr(p: *Printer, expr: Expr, level: Level, in_flags: ExprFlag.Set) void {
            var flags = in_flags;

            switch (expr.data) {
                .e_missing => {},
                .e_undefined => {
                    p.addSourceMapping(expr.loc);
                    p.printUndefined(expr.loc, level);
                },
                .e_super => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("super");
                },
                .e_null => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("null");
                },
                .e_this => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("this");
                },
                .e_spread => |e| {
                    p.addSourceMapping(expr.loc);
                    p.print("...");
                    p.printExpr(e.value, .comma, ExprFlag.None());
                },
                .e_new_target => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("new.target");
                },
                .e_import_meta => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    if (p.options.module_type == .internal_bake_dev) {
                        bun.assert(p.options.hmr_ref.isValid());
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".importMeta");
                    } else if (!p.options.import_meta_ref.isValid()) {
                        // Most of the time, leave it in there
                        p.print("import.meta");
                    } else {
                        // Note: The bundler will not hit this code path. The bundler will replace
                        // the ImportMeta AST node with a regular Identifier AST node.
                        //
                        // This is currently only used in Bun's runtime for CommonJS modules
                        // referencing import.meta
                        //
                        // TODO: This assertion trips when using `import.meta` with `--format=cjs`
                        bun.debugAssert(p.options.module_type == .cjs);

                        p.printSymbol(p.options.import_meta_ref);
                    }
                },
                .e_import_meta_main => |data| {
                    if (p.options.module_type == .esm and p.options.target != .node) {
                        // Node.js doesn't support import.meta.main
                        // Most of the time, leave it in there
                        if (data.inverted) {
                            p.addSourceMapping(expr.loc);
                            p.print("!");
                        } else {
                            p.printSpaceBeforeIdentifier();
                            p.addSourceMapping(expr.loc);
                        }
                        p.print("import.meta.main");
                    } else {
                        bun.debugAssert(p.options.module_type != .internal_bake_dev);

                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(expr.loc);

                        if (p.options.require_ref) |require|
                            p.printSymbol(require)
                        else
                            p.print("require");

                        if (data.inverted)
                            p.printWhitespacer(ws(".main != "))
                        else
                            p.printWhitespacer(ws(".main == "));

                        if (p.options.target == .node) {
                            // "__require.module"
                            if (p.options.require_ref) |require|
                                p.printSymbol(require)
                            else
                                p.print("require");

                            p.print(".module");
                        } else if (p.options.commonjs_module_ref.isValid()) {
                            p.printSymbol(p.options.commonjs_module_ref);
                        } else {
                            p.print("module");
                        }
                    }
                },
                .e_special => |special| switch (special) {
                    .module_exports => {
                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(expr.loc);

                        if (p.options.commonjs_module_exports_assigned_deoptimized) {
                            if (p.options.commonjs_module_ref.isValid()) {
                                p.printSymbol(p.options.commonjs_module_ref);
                            } else {
                                p.print("module");
                            }
                            p.print(".exports");
                        } else {
                            p.printSymbol(p.options.commonjs_named_exports_ref);
                        }
                    },
                    .hot_enabled => {
                        bun.debugAssert(p.options.module_type == .internal_bake_dev);
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".indirectHot");
                    },
                    .hot_data => {
                        bun.debugAssert(p.options.module_type == .internal_bake_dev);
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".data");
                    },
                    .hot_accept => {
                        bun.debugAssert(p.options.module_type == .internal_bake_dev);
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".accept");
                    },
                    .hot_accept_visited => {
                        bun.debugAssert(p.options.module_type == .internal_bake_dev);
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".acceptSpecifiers");
                    },
                    .hot_disabled => {
                        bun.debugAssert(p.options.module_type != .internal_bake_dev);
                        p.printExpr(.{ .data = .e_undefined, .loc = expr.loc }, level, in_flags);
                    },
                    .resolved_specifier_string => |index| {
                        bun.debugAssert(p.options.module_type == .internal_bake_dev);
                        p.printStringLiteralUTF8(p.importRecord(index.get()).path.pretty, true);
                    },
                },

                .e_commonjs_export_identifier => |id| {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);

                    for (p.options.commonjs_named_exports.keys(), p.options.commonjs_named_exports.values()) |key, value| {
                        if (value.loc_ref.ref.?.eql(id.ref)) {
                            if (p.options.commonjs_named_exports_deoptimized or value.needs_decl) {
                                if (p.options.commonjs_module_exports_assigned_deoptimized and
                                    id.base == .module_dot_exports and
                                    p.options.commonjs_module_ref.isValid())
                                {
                                    p.printSymbol(p.options.commonjs_module_ref);
                                    p.print(".exports");
                                } else {
                                    p.printSymbol(p.options.commonjs_named_exports_ref);
                                }

                                if (js_lexer.isIdentifier(key)) {
                                    p.print(".");
                                    p.print(key);
                                } else {
                                    p.print("[");
                                    p.printStringLiteralUTF8(key, false);
                                    p.print("]");
                                }
                            } else {
                                p.printSymbol(value.loc_ref.ref.?);
                            }
                            break;
                        }
                    }
                },
                .e_new => |e| {
                    const has_pure_comment = e.can_be_unwrapped_if_unused and p.options.print_dce_annotations;
                    const wrap = level.gte(.call) or (has_pure_comment and level.gte(.postfix));

                    if (wrap) {
                        p.print("(");
                    }

                    if (has_pure_comment) {
                        p.printPure();
                    }

                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("new");
                    p.printSpace();
                    p.printExpr(e.target, .new, ExprFlag.ForbidCall());
                    const args = e.args.slice();
                    if (args.len > 0 or level.gte(.postfix)) {
                        p.print("(");

                        if (args.len > 0) {
                            p.printExpr(args[0], .comma, ExprFlag.None());

                            for (args[1..]) |arg| {
                                p.print(",");
                                p.printSpace();
                                p.printExpr(arg, .comma, ExprFlag.None());
                            }
                        }

                        if (e.close_parens_loc.start > expr.loc.start) {
                            p.addSourceMapping(e.close_parens_loc);
                        }

                        p.print(")");
                    }

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_call => |e| {
                    var wrap = level.gte(.new) or flags.contains(.forbid_call);
                    var target_flags = ExprFlag.None();
                    if (e.optional_chain == null) {
                        target_flags = ExprFlag.HasNonOptionalChainParent();
                    } else if (flags.contains(.has_non_optional_chain_parent)) {
                        wrap = true;
                    }

                    const has_pure_comment = e.can_be_unwrapped_if_unused and p.options.print_dce_annotations;
                    if (has_pure_comment and level.gte(.postfix)) {
                        wrap = true;
                    }

                    if (wrap) {
                        p.print("(");
                    }

                    if (has_pure_comment) {
                        const was_stmt_start = p.stmt_start == p.writer.written;
                        p.printPure();
                        if (was_stmt_start) {
                            p.stmt_start = p.writer.written;
                        }
                    }
                    // We only want to generate an unbound eval() in CommonJS
                    p.call_target = e.target.data;

                    const is_unbound_eval = (!e.is_direct_eval and
                        p.isUnboundEvalIdentifier(e.target) and
                        e.optional_chain == null);

                    if (is_unbound_eval) {
                        p.print("(0,");
                        p.printSpace();
                        p.printExpr(e.target, .postfix, ExprFlag.None());
                        p.print(")");
                    } else {
                        p.printExpr(e.target, .postfix, target_flags);
                    }

                    if (e.optional_chain != null and (e.optional_chain orelse unreachable) == .start) {
                        p.print("?.");
                    }
                    p.print("(");
                    const args = e.args.slice();

                    if (args.len > 0) {
                        p.printExpr(args[0], .comma, ExprFlag.None());
                        for (args[1..]) |arg| {
                            p.print(",");
                            p.printSpace();
                            p.printExpr(arg, .comma, ExprFlag.None());
                        }
                    }
                    if (e.close_paren_loc.start > expr.loc.start) {
                        p.addSourceMapping(e.close_paren_loc);
                    }
                    p.print(")");
                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_require_main => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);

                    if (p.options.require_ref) |require_ref| {
                        p.printSymbol(require_ref);
                        p.print(".main");
                    } else if (p.options.module_type == .internal_bake_dev) {
                        p.print("false"); // there is no true main entry point
                    } else {
                        p.print("require.main");
                    }
                },
                .e_require_call_target => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);

                    if (p.options.require_ref) |require_ref| {
                        p.printSymbol(require_ref);
                    } else if (p.options.module_type == .internal_bake_dev) {
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".require");
                    } else {
                        p.print("require");
                    }
                },
                .e_require_resolve_call_target => {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);

                    if (p.options.require_ref) |require_ref| {
                        p.printSymbol(require_ref);
                        p.print(".resolve");
                    } else if (p.options.module_type == .internal_bake_dev) {
                        p.printSymbol(p.options.hmr_ref);
                        p.print(".requireResolve");
                    } else {
                        p.print("require.resolve");
                    }
                },
                .e_require_string => |e| {
                    if (!rewrite_esm_to_cjs) {
                        p.printRequireOrImportExpr(
                            e.import_record_index,
                            e.unwrapped_id != std.math.maxInt(u32),
                            &([_]G.Comment{}),
                            Expr.empty,
                            level,
                            flags,
                        );
                    }
                },
                .e_require_resolve_string => |e| {
                    const wrap = level.gte(.new) or flags.contains(.forbid_call);
                    if (wrap) {
                        p.print("(");
                    }

                    p.printSpaceBeforeIdentifier();

                    if (p.options.require_ref) |require_ref| {
                        p.printSymbol(require_ref);
                        p.print(".resolve");
                    } else {
                        p.print("require.resolve");
                    }

                    p.print("(");
                    p.printStringLiteralUTF8(p.importRecord(e.import_record_index).path.text, true);
                    p.print(")");

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_import => |e| {
                    // Handle non-string expressions
                    if (e.isImportRecordNull()) {
                        const wrap = level.gte(.new) or flags.contains(.forbid_call);
                        if (wrap) {
                            p.print("(");
                        }

                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(expr.loc);
                        if (p.options.module_type == .internal_bake_dev) {
                            p.printSymbol(p.options.hmr_ref);
                            p.print(".dynamicImport(");
                        } else {
                            p.print("import(");
                        }
                        // TODO:
                        // if (e.leading_interior_comments.len > 0) {
                        //     p.printNewline();
                        //     p.indent();
                        //     for (e.leading_interior_comments) |comment| {
                        //         p.printIndentedComment(comment.text);
                        //     }
                        //     p.printIndent();
                        // }
                        p.printExpr(e.expr, .comma, ExprFlag.None());

                        if (!e.options.isMissing()) {
                            p.printWhitespacer(ws(", "));
                            p.printExpr(e.options, .comma, .{});
                        }

                        // TODO:
                        // if (e.leading_interior_comments.len > 0) {
                        //     p.printNewline();
                        //     p.unindent();
                        //     p.printIndent();
                        // }
                        p.print(")");
                        if (wrap) {
                            p.print(")");
                        }
                    } else {
                        p.printRequireOrImportExpr(
                            e.import_record_index,
                            false,
                            &.{}, // e.leading_interior_comments,
                            e.options,
                            level,
                            flags,
                        );
                    }
                },
                .e_dot => |e| {
                    const isOptionalChain = e.optional_chain == .start;

                    var wrap = false;
                    if (e.optional_chain == null) {
                        flags.insert(.has_non_optional_chain_parent);

                        // Inline cross-module TypeScript enum references here
                        if (p.tryToGetImportedEnumValue(e.target, e.name)) |inlined| {
                            p.printInlinedEnum(inlined, e.name, level);
                            return;
                        }
                    } else {
                        if (flags.contains(.has_non_optional_chain_parent)) {
                            wrap = true;
                            p.print("(");
                        }

                        flags.remove(.has_non_optional_chain_parent);
                    }
                    flags.setIntersection(ExprFlag.Set.init(.{ .has_non_optional_chain_parent = true, .forbid_call = true }));

                    p.printExpr(
                        e.target,
                        .postfix,
                        flags,
                    );

                    if (js_lexer.isIdentifier(e.name)) {
                        if (isOptionalChain) {
                            p.print("?.");
                        } else {
                            if (p.prev_num_end == p.writer.written) {
                                // "1.toString" is a syntax error, so print "1 .toString" instead
                                p.print(" ");
                            }

                            p.print(".");
                        }

                        p.addSourceMapping(e.name_loc);
                        p.printIdentifier(e.name);
                    } else {
                        if (isOptionalChain) {
                            p.print("?.[");
                        } else {
                            p.print("[");
                        }

                        p.printStringLiteralUTF8(e.name, false);

                        p.print("]");
                    }

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_index => |e| {
                    var wrap = false;
                    if (e.optional_chain == null) {
                        flags.insert(.has_non_optional_chain_parent);

                        if (e.index.data.as(.e_string)) |str| {
                            str.resolveRopeIfNeeded(p.options.allocator);

                            if (str.isUTF8()) if (p.tryToGetImportedEnumValue(e.target, str.data)) |value| {
                                p.printInlinedEnum(value, str.data, level);
                                return;
                            };
                        }
                    } else {
                        if (flags.contains(.has_non_optional_chain_parent)) {
                            wrap = true;
                            p.print("(");
                        }
                        flags.remove(.has_non_optional_chain_parent);
                    }

                    p.printExpr(e.target, .postfix, flags);

                    const is_optional_chain_start = e.optional_chain == .start;
                    if (is_optional_chain_start) {
                        p.print("?.");
                    }

                    switch (e.index.data) {
                        .e_private_identifier => {
                            const priv = e.index.data.e_private_identifier;
                            if (!is_optional_chain_start) {
                                p.print(".");
                            }
                            p.addSourceMapping(e.index.loc);
                            p.printSymbol(priv.ref);
                        },
                        else => {
                            p.print("[");
                            p.addSourceMapping(e.index.loc);
                            p.printExpr(e.index, .lowest, ExprFlag.None());
                            p.print("]");
                        },
                    }

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_if => |e| {
                    const wrap = level.gte(.conditional);
                    if (wrap) {
                        p.print("(");
                        flags.remove(.forbid_in);
                    }
                    p.printExpr(e.test_, .conditional, flags);
                    p.printSpace();
                    p.print("?");
                    p.printSpace();
                    p.printExpr(e.yes, .yield, ExprFlag.None());
                    p.printSpace();
                    p.print(":");
                    p.printSpace();
                    flags.insert(.forbid_in);
                    p.printExpr(e.no, .yield, flags);
                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_arrow => |e| {
                    const wrap = level.gte(.assign);

                    if (wrap) {
                        p.print("(");
                    }

                    if (e.is_async) {
                        p.addSourceMapping(expr.loc);
                        p.printSpaceBeforeIdentifier();
                        p.print("async");
                        p.printSpace();
                    }

                    p.printFnArgs(if (e.is_async) null else expr.loc, e.args, e.has_rest_arg, true);
                    p.printWhitespacer(ws(" => "));

                    var wasPrinted = false;
                    if (e.body.stmts.len == 1 and e.prefer_expr) {
                        switch (e.body.stmts[0].data) {
                            .s_return => {
                                if (e.body.stmts[0].data.s_return.value) |val| {
                                    p.arrow_expr_start = p.writer.written;
                                    p.printExpr(val, .comma, ExprFlag.Set.init(.{ .forbid_in = true }));
                                    wasPrinted = true;
                                }
                            },
                            else => {},
                        }
                    }

                    if (!wasPrinted) {
                        p.printBlock(e.body.loc, e.body.stmts, null);
                    }

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_function => |e| {
                    const n = p.writer.written;
                    const wrap = p.stmt_start == n or p.export_default_start == n;

                    if (wrap) {
                        p.print("(");
                    }

                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    if (e.func.flags.contains(.is_async)) {
                        p.print("async ");
                    }
                    p.print("function");
                    if (e.func.flags.contains(.is_generator)) {
                        p.print("*");
                        p.printSpace();
                    }

                    if (e.func.name) |sym| {
                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(sym.loc);
                        p.printSymbol(sym.ref orelse Output.panic("internal error: expected E.Function's name symbol to have a ref\n{any}", .{e.func}));
                    }

                    p.printFunc(e.func);
                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_class => |e| {
                    const n = p.writer.written;
                    const wrap = p.stmt_start == n or p.export_default_start == n;
                    if (wrap) {
                        p.print("(");
                    }

                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("class");
                    if (e.class_name) |name| {
                        p.print(" ");
                        p.addSourceMapping(name.loc);
                        p.printSymbol(name.ref orelse Output.panic("internal error: expected E.Class's name symbol to have a ref\n{any}", .{e}));
                    }
                    p.printClass(e.*);
                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_array => |e| {
                    p.addSourceMapping(expr.loc);
                    p.print("[");
                    const items = e.items.slice();
                    if (items.len > 0) {
                        if (!e.is_single_line) {
                            p.indent();
                        }

                        for (items, 0..) |item, i| {
                            if (i != 0) {
                                p.print(",");
                                if (e.is_single_line) {
                                    p.printSpace();
                                }
                            }
                            if (!e.is_single_line) {
                                p.printNewline();
                                p.printIndent();
                            }
                            p.printExpr(item, .comma, ExprFlag.None());

                            if (i == items.len - 1 and item.data == .e_missing) {
                                // Make sure there's a comma after trailing missing items
                                p.print(",");
                            }
                        }

                        if (!e.is_single_line) {
                            p.unindent();
                            p.printNewline();
                            p.printIndent();
                        }
                    }

                    if (e.close_bracket_loc.start > expr.loc.start) {
                        p.addSourceMapping(e.close_bracket_loc);
                    }

                    p.print("]");
                },
                .e_object => |e| {
                    const n = p.writer.written;
                    const wrap = if (comptime is_json)
                        false
                    else
                        p.stmt_start == n or p.arrow_expr_start == n;

                    if (wrap) {
                        p.print("(");
                    }
                    p.addSourceMapping(expr.loc);
                    p.print("{");
                    const props = expr.data.e_object.properties.slice();
                    if (props.len > 0) {
                        if (!e.is_single_line) {
                            p.indent();
                        }

                        if (e.is_single_line and !is_json) {
                            p.printSpace();
                        } else {
                            p.printNewline();
                            p.printIndent();
                        }
                        p.printProperty(props[0]);

                        if (props.len > 1) {
                            for (props[1..]) |property| {
                                p.print(",");

                                if (e.is_single_line and !is_json) {
                                    p.printSpace();
                                } else {
                                    p.printNewline();
                                    p.printIndent();
                                }
                                p.printProperty(property);
                            }
                        }

                        if (e.is_single_line and !is_json) {
                            p.printSpace();
                        } else {
                            p.unindent();
                            p.printNewline();
                            p.printIndent();
                        }
                    }
                    if (e.close_brace_loc.start > expr.loc.start) {
                        p.addSourceMapping(e.close_brace_loc);
                    }
                    p.print("}");
                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_boolean => |e| {
                    p.addSourceMapping(expr.loc);
                    if (p.options.minify_syntax) {
                        if (level.gte(Level.prefix)) {
                            p.print(if (e.value) "(!0)" else "(!1)");
                        } else {
                            p.print(if (e.value) "!0" else "!1");
                        }
                    } else {
                        p.printSpaceBeforeIdentifier();
                        p.print(if (e.value) "true" else "false");
                    }
                },
                .e_string => |e| {
                    e.resolveRopeIfNeeded(p.options.allocator);
                    p.addSourceMapping(expr.loc);

                    // If this was originally a template literal, print it as one as long as we're not minifying
                    if (e.prefer_template and !p.options.minify_syntax) {
                        p.print("`");
                        p.printStringCharactersEString(e, '`');
                        p.print("`");
                        return;
                    }

                    p.printStringLiteralEString(e, true);
                },
                .e_template => |e| {
                    if (e.tag == null and (p.options.minify_syntax or p.was_lazy_export)) {
                        var replaced = std.ArrayList(E.TemplatePart).init(p.options.allocator);
                        for (e.parts, 0..) |_part, i| {
                            var part = _part;
                            const inlined_value: ?js_ast.Expr = switch (part.value.data) {
                                .e_name_of_symbol => |e2| Expr.init(
                                    E.String,
                                    E.String.init(p.mangledPropName(e2.ref)),
                                    part.value.loc,
                                ),
                                .e_dot => brk: {
                                    // TODO: handle inlining of dot properties
                                    break :brk null;
                                },
                                else => null,
                            };

                            if (inlined_value) |value| {
                                if (replaced.items.len == 0) {
                                    replaced.appendSlice(e.parts[0..i]) catch bun.outOfMemory();
                                }
                                part.value = value;
                                replaced.append(part) catch bun.outOfMemory();
                            } else if (replaced.items.len > 0) {
                                replaced.append(part) catch bun.outOfMemory();
                            }
                        }

                        if (replaced.items.len > 0) {
                            var copy = e.*;
                            copy.parts = replaced.items;
                            const e2 = copy.fold(p.options.allocator, expr.loc);
                            switch (e2.data) {
                                .e_string => {
                                    p.print('"');
                                    p.printStringCharactersUTF8(e2.data.e_string.data, '"');
                                    p.print('"');
                                    return;
                                },
                                .e_template => {
                                    e.* = e2.data.e_template.*;
                                },
                                else => {},
                            }
                        }

                        // Convert no-substitution template literals into strings if it's smaller
                        if (e.parts.len == 0) {
                            p.addSourceMapping(expr.loc);
                            p.printStringCharactersEString(&e.head.cooked, '`');
                            return;
                        }
                    }

                    if (e.tag) |tag| {
                        p.addSourceMapping(expr.loc);
                        // Optional chains are forbidden in template tags
                        if (expr.isOptionalChain()) {
                            p.print("(");
                            p.printExpr(tag, .lowest, ExprFlag.None());
                            p.print(")");
                        } else {
                            p.printExpr(tag, .postfix, ExprFlag.None());
                        }
                    } else {
                        p.addSourceMapping(expr.loc);
                    }

                    p.print("`");
                    switch (e.head) {
                        .raw => |raw| p.printRawTemplateLiteral(raw),
                        .cooked => |*cooked| {
                            if (cooked.isPresent()) {
                                cooked.resolveRopeIfNeeded(p.options.allocator);
                                p.printStringCharactersEString(cooked, '`');
                            }
                        },
                    }

                    for (e.parts) |*part| {
                        p.print("${");
                        p.printExpr(part.value, .lowest, ExprFlag.None());
                        p.print("}");
                        switch (part.tail) {
                            .raw => |raw| p.printRawTemplateLiteral(raw),
                            .cooked => |*cooked| {
                                if (cooked.isPresent()) {
                                    cooked.resolveRopeIfNeeded(p.options.allocator);
                                    p.printStringCharactersEString(cooked, '`');
                                }
                            },
                        }
                    }
                    p.print("`");
                },
                .e_reg_exp => |e| {
                    p.addSourceMapping(expr.loc);
                    p.printRegExpLiteral(e);
                },
                .e_big_int => |e| {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print(e.value);
                    p.print('n');
                },
                .e_number => |e| {
                    p.addSourceMapping(expr.loc);
                    p.printNumber(e.value, level);
                },
                .e_identifier => |e| {
                    const name = p.renamer.nameForSymbol(e.ref);
                    const wrap = p.writer.written == p.for_of_init_start and strings.eqlComptime(name, "let");

                    if (wrap) {
                        p.print("(");
                    }

                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.printIdentifier(name);

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_import_identifier => |e| {
                    // Potentially use a property access instead of an identifier
                    var didPrint = false;

                    const ref = if (p.options.module_type != .internal_bake_dev)
                        p.symbols().follow(e.ref)
                    else
                        e.ref;
                    const symbol = p.symbols().get(ref).?;

                    // if (bun.strings.eql(symbol.original_name, "registerClientReference")) {
                    //     @breakpoint();
                    // }

                    if (symbol.import_item_status == .missing) {
                        p.printUndefined(expr.loc, level);
                        didPrint = true;
                    } else if (symbol.namespace_alias) |namespace| {
                        if (namespace.import_record_index < p.import_records.len) {
                            const import_record = p.importRecord(namespace.import_record_index);
                            if (namespace.was_originally_property_access) {
                                var wrap = false;
                                didPrint = true;

                                if (p.call_target) |target| {
                                    wrap = e.was_originally_identifier and (target == .e_identifier and
                                        target.e_identifier.ref.eql(expr.data.e_import_identifier.ref));
                                }

                                if (wrap) {
                                    p.printWhitespacer(ws("(0, "));
                                }
                                p.printSpaceBeforeIdentifier();
                                p.addSourceMapping(expr.loc);
                                p.printNamespaceAlias(import_record.*, namespace);

                                if (wrap) {
                                    p.print(")");
                                }
                            } else if (import_record.was_originally_require and import_record.path.is_disabled) {
                                p.addSourceMapping(expr.loc);

                                if (import_record.handles_import_errors) {
                                    p.printRequireError(import_record.path.text);
                                } else {
                                    p.printDisabledImport();
                                }
                                didPrint = true;
                            }
                        }

                        if (!didPrint) {
                            didPrint = true;

                            const wrap = if (p.call_target) |target|
                                e.was_originally_identifier and (target == .e_identifier and
                                    target.e_identifier.ref.eql(expr.data.e_import_identifier.ref))
                            else
                                false;

                            if (wrap) {
                                p.printWhitespacer(ws("(0, "));
                            }

                            p.printSpaceBeforeIdentifier();
                            p.addSourceMapping(expr.loc);
                            p.printSymbol(namespace.namespace_ref);
                            const alias = namespace.alias;
                            if (js_lexer.isIdentifier(alias)) {
                                p.print(".");
                                // TODO: addSourceMappingForName
                                p.printIdentifier(alias);
                            } else {
                                p.print("[");
                                // TODO: addSourceMappingForName
                                // p.addSourceMappingForName(alias);
                                p.printStringLiteralUTF8(alias, false);
                                p.print("]");
                            }

                            if (wrap) {
                                p.print(")");
                            }
                        }
                    }
                    // else if (p.options.const_values.get(ref)) |const_value| {
                    //     p.printSpaceBeforeIdentifier();
                    //     // TODO: addSourceMappingForName
                    //     // p.addSourceMappingForName(renamer.nameForSymbol(e.ref));
                    //     p.addSourceMapping(expr.loc);
                    //     p.printExpr(const_value, level, flags);
                    //     didPrint = true;
                    // }

                    if (!didPrint) {
                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(expr.loc);
                        p.printSymbol(e.ref);
                    }
                },
                .e_await => |e| {
                    const wrap = level.gte(.prefix);

                    if (wrap) {
                        p.print("(");
                    }

                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("await");
                    p.printSpace();
                    p.printExpr(e.value, Level.sub(.prefix, 1), ExprFlag.None());

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_yield => |e| {
                    const wrap = level.gte(.assign);
                    if (wrap) {
                        p.print("(");
                    }

                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(expr.loc);
                    p.print("yield");

                    if (e.value) |val| {
                        if (e.is_star) {
                            p.print("*");
                        }
                        p.printSpace();
                        p.printExpr(val, .yield, ExprFlag.None());
                    }

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_unary => |e| {
                    // 4.00 ms  eums.EnumIndexer(src.js_ast.Op.Code).indexOf
                    const entry: *const Op = Op.Table.getPtrConst(e.op);
                    const wrap = level.gte(entry.level);

                    if (wrap) {
                        p.print("(");
                    }

                    if (!e.op.isPrefix()) {
                        p.printExpr(e.value, Op.Level.sub(.postfix, 1), ExprFlag.None());
                    }

                    if (entry.is_keyword) {
                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(expr.loc);
                        p.print(entry.text);
                        p.printSpace();
                    } else {
                        p.printSpaceBeforeOperator(e.op);
                        p.print(entry.text);
                        p.prev_op = e.op;
                        p.prev_op_end = p.writer.written;
                    }

                    if (e.op.isPrefix()) {
                        p.printExpr(e.value, Op.Level.sub(.prefix, 1), ExprFlag.None());
                    }

                    if (wrap) {
                        p.print(")");
                    }
                },
                .e_binary => |e| {
                    // The handling of binary expressions is convoluted because we're using
                    // iteration on the heap instead of recursion on the call stack to avoid
                    // stack overflow for deeply-nested ASTs. See the comments for the similar
                    // code in the JavaScript parser for details.
                    var v = BinaryExpressionVisitor{
                        .e = e,
                        .level = level,
                        .flags = flags,
                        .entry = Op.Table.getPtrConst(e.op),
                    };

                    // Use a single stack to reduce allocation overhead
                    const stack_bottom = p.binary_expression_stack.items.len;

                    while (true) {
                        if (!v.checkAndPrepare(p)) {
                            break;
                        }

                        const left = v.e.left;
                        const left_binary: ?*E.Binary = if (left.data == .e_binary) left.data.e_binary else null;

                        // Stop iterating if iteration doesn't apply to the left node
                        if (left_binary == null) {
                            p.printExpr(left, v.left_level, v.left_flags);
                            v.visitRightAndFinish(p);
                            break;
                        }

                        // Only allocate heap memory on the stack for nested binary expressions
                        p.binary_expression_stack.append(v) catch bun.outOfMemory();
                        v = BinaryExpressionVisitor{
                            .e = left_binary.?,
                            .level = v.left_level,
                            .flags = v.left_flags,
                        };
                    }

                    // Process all binary operations from the deepest-visited node back toward
                    // our original top-level binary operation
                    while (p.binary_expression_stack.items.len > stack_bottom) {
                        var last = p.binary_expression_stack.pop().?;
                        last.visitRightAndFinish(p);
                    }
                },
                .e_inlined_enum => |e| {
                    p.printExpr(e.value, level, flags);
                    if (!p.options.minify_whitespace and !p.options.minify_identifiers) {
                        p.print(" /* ");
                        p.print(e.comment);
                        p.print(" */");
                    }
                },
                .e_name_of_symbol => |e| {
                    const name = p.mangledPropName(e.ref);
                    p.addSourceMappingForName(expr.loc, name, e.ref);

                    if (!p.options.minify_whitespace and e.has_property_key_comment) {
                        p.print(" /* @__KEY__ */");
                    }

                    p.print('"');
                    p.printStringCharactersUTF8(name, '"');
                    p.print('"');
                },

                .e_jsx_element,
                .e_private_identifier,
                => {
                    if (Environment.isDebug)
                        Output.panic("Unexpected expression of type .{s}", .{@tagName(expr.data)});
                },
            }
        }

        pub fn printSpaceBeforeOperator(p: *Printer, next: Op.Code) void {
            if (p.prev_op_end == p.writer.written) {
                const prev = p.prev_op;
                // "+ + y" => "+ +y"
                // "+ ++ y" => "+ ++y"
                // "x + + y" => "x+ +y"
                // "x ++ + y" => "x+++y"
                // "x + ++ y" => "x+ ++y"
                // "-- >" => "-- >"
                // "< ! --" => "<! --"
                if (((prev == Op.Code.bin_add or prev == Op.Code.un_pos) and (next == Op.Code.bin_add or next == Op.Code.un_pos or next == Op.Code.un_pre_inc)) or
                    ((prev == Op.Code.bin_sub or prev == Op.Code.un_neg) and (next == Op.Code.bin_sub or next == Op.Code.un_neg or next == Op.Code.un_pre_dec)) or
                    (prev == Op.Code.un_post_dec and next == Op.Code.bin_gt) or
                    (prev == Op.Code.un_not and next == Op.Code.un_pre_dec and p.writer.written > 1 and p.writer.prevPrevChar() == '<'))
                {
                    p.print(" ");
                }
            }
        }

        pub inline fn printDotThenSuffix(p: *Printer) void {
            p.print(")");
        }

        // This assumes the string has already been quoted.
        pub fn printStringCharactersEString(p: *Printer, str: *const E.String, c: u8) void {
            if (!str.isUTF8()) {
                p.printStringCharactersUTF16(str.slice16(), c);
            } else {
                p.printStringCharactersUTF8(str.data, c);
            }
        }

        pub fn printNamespaceAlias(p: *Printer, _: ImportRecord, namespace: G.NamespaceAlias) void {
            p.printSymbol(namespace.namespace_ref);

            // In the case of code like this:
            // module.exports = require("foo")
            // if "foo" is bundled
            // then we access it as the namespace symbol itself
            // that means the namespace alias is empty
            if (namespace.alias.len == 0) return;

            if (js_lexer.isIdentifier(namespace.alias)) {
                p.print(".");
                p.printIdentifier(namespace.alias);
            } else {
                p.print("[");
                p.printStringLiteralUTF8(namespace.alias, false);
                p.print("]");
            }
        }

        pub fn printRegExpLiteral(p: *Printer, e: *const E.RegExp) void {
            const n = p.writer.written;

            // Avoid forming a single-line comment
            if (n > 0 and p.writer.prevChar() == '/') {
                p.print(" ");
            }

            if (comptime is_bun_platform) {
                // Translate any non-ASCII to unicode escape sequences
                var ascii_start: usize = 0;
                var is_ascii = false;
                var iter = CodepointIterator.init(e.value);
                var cursor = CodepointIterator.Cursor{};
                while (iter.next(&cursor)) {
                    switch (cursor.c) {
                        first_ascii...last_ascii => {
                            if (!is_ascii) {
                                ascii_start = cursor.i;
                                is_ascii = true;
                            }
                        },
                        else => {
                            if (is_ascii) {
                                p.print(e.value[ascii_start..cursor.i]);
                                is_ascii = false;
                            }

                            switch (cursor.c) {
                                0...0xFFFF => {
                                    p.print([_]u8{
                                        '\\',
                                        'u',
                                        hex_chars[cursor.c >> 12],
                                        hex_chars[(cursor.c >> 8) & 15],
                                        hex_chars[(cursor.c >> 4) & 15],
                                        hex_chars[cursor.c & 15],
                                    });
                                },

                                else => |c| {
                                    const k = c - 0x10000;
                                    const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
                                    const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));

                                    p.print(&[_]u8{
                                        '\\',
                                        'u',
                                        hex_chars[lo >> 12],
                                        hex_chars[(lo >> 8) & 15],
                                        hex_chars[(lo >> 4) & 15],
                                        hex_chars[lo & 15],
                                        '\\',
                                        'u',
                                        hex_chars[hi >> 12],
                                        hex_chars[(hi >> 8) & 15],
                                        hex_chars[(hi >> 4) & 15],
                                        hex_chars[hi & 15],
                                    });
                                },
                            }
                        },
                    }
                }

                if (is_ascii) {
                    p.print(e.value[ascii_start..]);
                }
            } else {
                // UTF8 sequence is fine
                p.print(e.value);
            }

            // Need a space before the next identifier to avoid it turning into flags
            p.prev_reg_exp_end = p.writer.written;
        }

        pub fn printProperty(p: *Printer, item_in: G.Property) void {
            var item = item_in;
            if (comptime !is_json) {
                if (item.kind == .spread) {
                    if (comptime is_json and Environment.allow_assert)
                        unreachable;
                    p.print("...");
                    p.printExpr(item.value.?, .comma, ExprFlag.None());
                    return;
                }

                // Handle key syntax compression for cross-module constant inlining of enums
                if (p.options.minify_syntax and item.flags.contains(.is_computed)) {
                    if (item.key.?.data.as(.e_dot)) |dot| {
                        if (p.tryToGetImportedEnumValue(dot.target, dot.name)) |value| {
                            switch (value) {
                                .string => |str| {
                                    item.key.?.data = .{ .e_string = str };

                                    // Problematic key names must stay computed for correctness
                                    if (!str.eqlComptime("__proto__") and !str.eqlComptime("constructor") and !str.eqlComptime("prototype")) {
                                        item.flags.setPresent(.is_computed, false);
                                    }
                                },
                                .number => |num| {
                                    item.key.?.data = .{ .e_number = .{ .value = num } };
                                    item.flags.setPresent(.is_computed, false);
                                },
                            }
                        }
                    }
                }

                if (item.flags.contains(.is_static)) {
                    if (comptime is_json and Environment.allow_assert)
                        unreachable;
                    p.print("static");
                    p.printSpace();
                }

                switch (item.kind) {
                    .get => {
                        if (comptime is_json and Environment.allow_assert)
                            unreachable;
                        p.printSpaceBeforeIdentifier();
                        p.print("get");
                        p.printSpace();
                    },
                    .set => {
                        if (comptime is_json and Environment.allow_assert)
                            unreachable;
                        p.printSpaceBeforeIdentifier();
                        p.print("set");
                        p.printSpace();
                    },
                    else => {},
                }

                if (item.value) |val| {
                    switch (val.data) {
                        .e_function => |func| {
                            if (item.flags.contains(.is_method)) {
                                if (func.func.flags.contains(.is_async)) {
                                    p.printSpaceBeforeIdentifier();
                                    p.print("async");
                                }

                                if (func.func.flags.contains(.is_generator)) {
                                    p.print("*");
                                }

                                if (func.func.flags.contains(.is_generator) and func.func.flags.contains(.is_async)) {
                                    p.printSpace();
                                }
                            }
                        },
                        else => {},
                    }

                    // If var is declared in a parent scope and var is then written via destructuring pattern, key is null
                    // example:
                    //  var foo = 1;
                    //  if (true) {
                    //      var { foo } = { foo: 2 };
                    //  }
                    if (item.key == null) {
                        p.printExpr(val, .comma, ExprFlag.None());
                        return;
                    }
                }
            }

            const _key = item.key.?;

            if (!is_json and item.flags.contains(.is_computed)) {
                p.print("[");
                p.printExpr(_key, .comma, ExprFlag.None());
                p.print("]");

                if (item.value) |val| {
                    switch (val.data) {
                        .e_function => |func| {
                            if (item.flags.contains(.is_method)) {
                                p.printFunc(func.func);
                                return;
                            }
                        },
                        else => {},
                    }

                    p.print(":");
                    p.printSpace();
                    p.printExpr(val, .comma, ExprFlag.None());
                }

                if (item.initializer) |initial| {
                    p.printInitializer(initial);
                }
                return;
            }

            switch (_key.data) {
                .e_private_identifier => |priv| {
                    if (comptime is_json) {
                        unreachable;
                    }

                    p.addSourceMapping(_key.loc);
                    p.printSymbol(priv.ref);
                },
                .e_string => |key| {
                    p.addSourceMapping(_key.loc);
                    if (key.isUTF8()) {
                        key.resolveRopeIfNeeded(p.options.allocator);
                        p.printSpaceBeforeIdentifier();
                        var allow_shorthand: bool = true;
                        // In react/cjs/react.development.js, there's part of a function like this:
                        // var escaperLookup = {
                        //     "=": "=0",
                        //     ":": "=2"
                        //   };
                        // While each of those property keys are ASCII, a subset of ASCII is valid as the start of an identifier
                        // "=" and ":" are not valid
                        // So we need to check
                        if (!is_json and js_lexer.isIdentifier(key.data)) {
                            p.printIdentifier(key.data);
                        } else {
                            allow_shorthand = false;
                            p.printStringLiteralEString(key, false);
                        }

                        // Use a shorthand property if the names are the same
                        if (item.value) |val| {
                            switch (val.data) {
                                .e_identifier => |e| {
                                    if (key.eql(string, p.renamer.nameForSymbol(e.ref))) {
                                        if (item.initializer) |initial| {
                                            p.printInitializer(initial);
                                        }
                                        if (allow_shorthand) {
                                            return;
                                        }
                                    }
                                },
                                .e_import_identifier => |e| inner: {
                                    const ref = p.symbols().follow(e.ref);
                                    if (p.options.input_files_for_dev_server != null)
                                        break :inner;
                                    // if (p.options.const_values.count() > 0 and p.options.const_values.contains(ref))
                                    //     break :inner;

                                    if (p.symbols().get(ref)) |symbol| {
                                        if (symbol.namespace_alias == null and strings.eql(key.data, p.renamer.nameForSymbol(e.ref))) {
                                            if (item.initializer) |initial| {
                                                p.printInitializer(initial);
                                            }
                                            if (allow_shorthand) {
                                                return;
                                            }
                                        }
                                    }
                                },
                                else => {},
                            }
                        }
                    } else if (!is_json and p.canPrintIdentifierUTF16(key.slice16())) {
                        p.printSpaceBeforeIdentifier();
                        p.printIdentifierUTF16(key.slice16()) catch unreachable;

                        // Use a shorthand property if the names are the same
                        if (item.value) |val| {
                            switch (val.data) {
                                .e_identifier => |e| {

                                    // TODO: is needing to check item.flags.contains(.was_shorthand) a bug?
                                    // esbuild doesn't have to do that...
                                    // maybe it's a symptom of some other underlying issue
                                    // or maybe, it's because i'm not lowering the same way that esbuild does.
                                    if (item.flags.contains(.was_shorthand) or strings.utf16EqlString(key.slice16(), p.renamer.nameForSymbol(e.ref))) {
                                        if (item.initializer) |initial| {
                                            p.printInitializer(initial);
                                        }
                                        return;
                                    }
                                    // if (strings) {}
                                },
                                // .e_import_identifier => |e| inner: {
                                .e_import_identifier => |e| {
                                    const ref = p.symbols().follow(e.ref);

                                    // if (p.options.const_values.count() > 0 and p.options.const_values.contains(ref))
                                    //     break :inner;

                                    if (p.symbols().get(ref)) |symbol| {
                                        if (symbol.namespace_alias == null and strings.utf16EqlString(key.slice16(), p.renamer.nameForSymbol(e.ref))) {
                                            if (item.initializer) |initial| {
                                                p.printInitializer(initial);
                                            }
                                            return;
                                        }
                                    }
                                },
                                else => {},
                            }
                        }
                    } else {
                        const c = bestQuoteCharForString(u16, key.slice16(), false);
                        p.print(c);
                        p.printStringCharactersUTF16(key.slice16(), c);
                        p.print(c);
                    }
                },
                else => {
                    if (comptime is_json) {
                        unreachable;
                    }

                    p.printExpr(_key, .lowest, ExprFlag.Set{});
                },
            }

            if (item.kind != .normal) {
                if (comptime is_json) {
                    bun.unreachablePanic("item.kind must be normal in json, received: {any}", .{item.kind});
                }

                switch (item.value.?.data) {
                    .e_function => |func| {
                        p.printFunc(func.func);
                        return;
                    },
                    else => {},
                }
            }

            if (item.value) |val| {
                switch (val.data) {
                    .e_function => |f| {
                        if (item.flags.contains(.is_method)) {
                            p.printFunc(f.func);

                            return;
                        }
                    },
                    else => {},
                }

                p.print(":");
                p.printSpace();
                p.printExpr(val, .comma, ExprFlag.Set{});
            }

            if (comptime is_json) {
                bun.assert(item.initializer == null);
            }

            if (item.initializer) |initial| {
                p.printInitializer(initial);
            }
        }

        pub fn printInitializer(p: *Printer, initial: Expr) void {
            p.printSpace();
            p.print("=");
            p.printSpace();
            p.printExpr(initial, .comma, ExprFlag.None());
        }

        pub fn printBinding(p: *Printer, binding: Binding) void {
            switch (binding.data) {
                .b_missing => {},
                .b_identifier => |b| {
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(binding.loc);
                    p.printSymbol(b.ref);
                },
                .b_array => |b| {
                    p.print("[");
                    if (b.items.len > 0) {
                        if (!b.is_single_line) {
                            p.indent();
                        }

                        for (b.items, 0..) |*item, i| {
                            if (i != 0) {
                                p.print(",");
                                if (b.is_single_line) {
                                    p.printSpace();
                                }
                            }

                            if (!b.is_single_line) {
                                p.printNewline();
                                p.printIndent();
                            }

                            const is_last = i + 1 == b.items.len;
                            if (b.has_spread and is_last) {
                                p.print("...");
                            }

                            p.printBinding(item.binding);

                            p.maybePrintDefaultBindingValue(item);

                            // Make sure there's a comma after trailing missing items
                            if (is_last and item.binding.data == .b_missing) {
                                p.print(",");
                            }
                        }

                        if (!b.is_single_line) {
                            p.unindent();
                            p.printNewline();
                            p.printIndent();
                        }
                    }

                    p.print("]");
                },
                .b_object => |b| {
                    p.print("{");
                    if (b.properties.len > 0) {
                        if (!b.is_single_line) {
                            p.indent();
                        }

                        for (b.properties, 0..) |*property, i| {
                            if (i != 0) {
                                p.print(",");
                            }

                            if (b.is_single_line) {
                                p.printSpace();
                            } else {
                                p.printNewline();
                                p.printIndent();
                            }

                            if (property.flags.contains(.is_spread)) {
                                p.print("...");
                            } else {
                                if (property.flags.contains(.is_computed)) {
                                    p.print("[");
                                    p.printExpr(property.key, .comma, ExprFlag.None());
                                    p.print("]:");
                                    p.printSpace();

                                    p.printBinding(property.value);
                                    p.maybePrintDefaultBindingValue(property);
                                    continue;
                                }

                                switch (property.key.data) {
                                    .e_string => |str| {
                                        str.resolveRopeIfNeeded(p.options.allocator);
                                        p.addSourceMapping(property.key.loc);

                                        if (str.isUTF8()) {
                                            p.printSpaceBeforeIdentifier();
                                            // Example case:
                                            //      const Menu = React.memo(function Menu({
                                            //          aria-label: ariaLabel,
                                            //              ^
                                            // That needs to be:
                                            //          "aria-label": ariaLabel,
                                            if (js_lexer.isIdentifier(str.data)) {
                                                p.printIdentifier(str.data);

                                                // Use a shorthand property if the names are the same
                                                switch (property.value.data) {
                                                    .b_identifier => |id| {
                                                        if (str.eql(string, p.renamer.nameForSymbol(id.ref))) {
                                                            p.maybePrintDefaultBindingValue(property);
                                                            continue;
                                                        }
                                                    },
                                                    else => {},
                                                }
                                            } else {
                                                p.printStringLiteralUTF8(str.data, false);
                                            }
                                        } else if (p.canPrintIdentifierUTF16(str.slice16())) {
                                            p.printSpaceBeforeIdentifier();
                                            p.printIdentifierUTF16(str.slice16()) catch unreachable;

                                            // Use a shorthand property if the names are the same
                                            switch (property.value.data) {
                                                .b_identifier => |id| {
                                                    if (strings.utf16EqlString(str.slice16(), p.renamer.nameForSymbol(id.ref))) {
                                                        p.maybePrintDefaultBindingValue(property);
                                                        continue;
                                                    }
                                                },
                                                else => {},
                                            }
                                        } else {
                                            p.printExpr(property.key, .lowest, ExprFlag.None());
                                        }
                                    },
                                    else => {
                                        p.printExpr(property.key, .lowest, ExprFlag.None());
                                    },
                                }

                                p.print(":");
                                p.printSpace();
                            }

                            p.printBinding(property.value);
                            p.maybePrintDefaultBindingValue(property);
                        }

                        if (!b.is_single_line) {
                            p.unindent();
                            p.printNewline();
                            p.printIndent();
                        } else {
                            p.printSpace();
                        }
                    }
                    p.print("}");
                },
            }
        }

        pub fn maybePrintDefaultBindingValue(p: *Printer, property: anytype) void {
            if (property.default_value) |default| {
                p.printSpace();
                p.print("=");
                p.printSpace();
                p.printExpr(default, .comma, ExprFlag.None());
            }
        }

        pub fn printStmt(p: *Printer, stmt: Stmt) !void {
            const prev_stmt_tag = p.prev_stmt_tag;

            defer {
                p.prev_stmt_tag = std.meta.activeTag(stmt.data);
            }

            switch (stmt.data) {
                .s_comment => |s| {
                    p.printIndentedComment(s.text);
                },
                .s_function => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    const name = s.func.name orelse Output.panic("Internal error: expected func to have a name ref\n{any}", .{s});
                    const nameRef = name.ref orelse Output.panic("Internal error: expected func to have a name\n{any}", .{s});

                    if (s.func.flags.contains(.is_export)) {
                        if (!rewrite_esm_to_cjs) {
                            p.print("export ");
                        }
                    }
                    if (s.func.flags.contains(.is_async)) {
                        p.print("async ");
                    }
                    p.print("function");
                    if (s.func.flags.contains(.is_generator)) {
                        p.print("*");
                        p.printSpace();
                    } else {
                        p.printSpaceBeforeIdentifier();
                    }

                    p.addSourceMapping(name.loc);
                    p.printSymbol(nameRef);
                    p.printFunc(s.func);

                    // if (rewrite_esm_to_cjs and s.func.flags.contains(.is_export)) {
                    //     p.printSemicolonAfterStatement();
                    //     p.print("var ");
                    //     p.printSymbol(nameRef);
                    //     p.@"print = "();
                    //     p.printSymbol(nameRef);
                    //     p.printSemicolonAfterStatement();
                    // } else {
                    p.printNewline();
                    // }

                    if (rewrite_esm_to_cjs and s.func.flags.contains(.is_export)) {
                        p.printIndent();
                        p.printBundledExport(p.renamer.nameForSymbol(nameRef), p.renamer.nameForSymbol(nameRef));
                        p.printSemicolonAfterStatement();
                    }
                },
                .s_class => |s| {
                    // Give an extra newline for readaiblity
                    if (prev_stmt_tag != .s_empty) {
                        p.printNewline();
                    }

                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    const nameRef = s.class.class_name.?.ref.?;
                    if (s.is_export) {
                        if (!rewrite_esm_to_cjs) {
                            p.print("export ");
                        }
                    }

                    p.print("class ");
                    p.addSourceMapping(s.class.class_name.?.loc);
                    p.printSymbol(nameRef);
                    p.printClass(s.class);

                    if (rewrite_esm_to_cjs and s.is_export) {
                        p.printSemicolonAfterStatement();
                    } else {
                        p.printNewline();
                    }

                    if (rewrite_esm_to_cjs) {
                        if (s.is_export) {
                            p.printIndent();
                            p.printBundledExport(p.renamer.nameForSymbol(nameRef), p.renamer.nameForSymbol(nameRef));
                            p.printSemicolonAfterStatement();
                        }
                    }
                },
                .s_empty => {
                    if (p.prev_stmt_tag == .s_empty and p.options.indent.count == 0) return;

                    p.printIndent();
                    p.addSourceMapping(stmt.loc);
                    p.print(";");
                    p.printNewline();
                },
                .s_export_default => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("export default ");

                    switch (s.value) {
                        .expr => |expr| {

                            // Functions and classes must be wrapped to avoid confusion with their statement forms
                            p.export_default_start = p.writer.written;
                            p.printExpr(expr, .comma, ExprFlag.None());
                            p.printSemicolonAfterStatement();
                            return;
                        },

                        .stmt => |s2| {
                            switch (s2.data) {
                                .s_function => |func| {
                                    p.printSpaceBeforeIdentifier();

                                    if (func.func.flags.contains(.is_async)) {
                                        p.print("async ");
                                    }
                                    p.print("function");

                                    if (func.func.flags.contains(.is_generator)) {
                                        p.print("*");
                                        p.printSpace();
                                    } else {
                                        p.maybePrintSpace();
                                    }

                                    if (func.func.name) |name| {
                                        p.printSymbol(name.ref.?);
                                    }

                                    p.printFunc(func.func);

                                    p.printNewline();
                                },
                                .s_class => |class| {
                                    p.printSpaceBeforeIdentifier();

                                    if (class.class.class_name) |name| {
                                        p.print("class ");
                                        p.printSymbol(name.ref orelse Output.panic("Internal error: Expected class to have a name ref\n{any}", .{class}));
                                    } else {
                                        p.print("class");
                                    }

                                    p.printClass(class.class);

                                    p.printNewline();
                                },
                                else => {
                                    Output.panic("Internal error: unexpected export default stmt data {any}", .{s});
                                },
                            }
                        },
                    }
                },
                .s_export_star => |s| {

                    // Give an extra newline for readaiblity
                    if (!prev_stmt_tag.isExportLike()) {
                        p.printNewline();
                    }
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);

                    if (s.alias != null)
                        p.printWhitespacer(comptime ws("export *").append(" as "))
                    else
                        p.printWhitespacer(comptime ws("export * from "));

                    if (s.alias) |alias| {
                        p.printClauseAlias(alias.original_name);
                        p.print(" ");
                        p.printWhitespacer(ws("from "));
                    }

                    p.printImportRecordPath(p.importRecord(s.import_record_index));
                    p.printSemicolonAfterStatement();
                },
                .s_export_clause => |s| {
                    if (rewrite_esm_to_cjs) {
                        p.printIndent();
                        p.printSpaceBeforeIdentifier();
                        p.addSourceMapping(stmt.loc);

                        switch (s.items.len) {
                            0 => {},
                            // It unfortunately cannot be so simple as exports.foo = foo;
                            // If we have a lazy re-export and it's read-only...
                            // we have to overwrite it via Object.defineProperty

                            // Object.assign(__export, {prop1, prop2, prop3});
                            else => {
                                p.print("Object.assign");

                                p.print("(");
                                p.printModuleExportSymbol();
                                p.print(",");
                                p.printSpace();
                                p.print("{");
                                p.printSpace();
                                const last = s.items.len - 1;
                                for (s.items, 0..) |item, i| {
                                    const symbol = p.symbols().getWithLink(item.name.ref.?).?;
                                    const name = symbol.original_name;
                                    var did_print = false;

                                    if (symbol.namespace_alias) |namespace| {
                                        const import_record = p.importRecord(namespace.import_record_index);
                                        if (namespace.was_originally_property_access) {
                                            p.printIdentifier(name);
                                            p.print(": () => ");
                                            p.printNamespaceAlias(import_record.*, namespace);
                                            did_print = true;
                                        }
                                    }

                                    if (!did_print) {
                                        p.printClauseAlias(item.alias);
                                        if (!strings.eql(name, item.alias)) {
                                            p.print(":");
                                            p.printSpaceBeforeIdentifier();
                                            p.printIdentifier(name);
                                        }
                                    }

                                    if (i < last) {
                                        p.print(",");
                                    }
                                }
                                p.print("})");
                                p.printSemicolonAfterStatement();
                            },
                        }
                        return;
                    }

                    // Give an extra newline for export default for readability
                    if (!prev_stmt_tag.isExportLike()) {
                        p.printNewline();
                    }

                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("export");
                    p.printSpace();

                    if (s.items.len == 0) {
                        p.print("{}");
                        p.printSemicolonAfterStatement();
                        return;
                    }

                    // This transforms code like this:
                    // import {Foo, Bar} from 'bundled-module';
                    // export {Foo, Bar};
                    // into
                    // export var Foo = $$bundledModule.Foo; (where $$bundledModule is created at import time)
                    // This is necessary unfortunately because export statements do not allow dot expressions
                    // The correct approach here is to invert the logic
                    // instead, make the entire module behave like a CommonJS module
                    // and export that one instead
                    // This particular code segment does the transform inline by adding an extra pass over export clauses
                    // and then swapRemove'ing them as we go
                    var array = std.ArrayListUnmanaged(js_ast.ClauseItem){ .items = s.items, .capacity = s.items.len };
                    {
                        var i: usize = 0;
                        while (i < array.items.len) {
                            const item: js_ast.ClauseItem = array.items[i];

                            if (item.original_name.len > 0) {
                                if (p.symbols().get(item.name.ref.?)) |symbol| {
                                    if (symbol.namespace_alias) |namespace| {
                                        const import_record = p.importRecord(namespace.import_record_index);
                                        if (namespace.was_originally_property_access) {
                                            p.print("var ");
                                            p.printSymbol(item.name.ref.?);
                                            p.@"print = "();
                                            p.printNamespaceAlias(import_record.*, namespace);
                                            p.printSemicolonAfterStatement();
                                            _ = array.swapRemove(i);

                                            if (i < array.items.len) {
                                                p.printIndent();
                                                p.printSpaceBeforeIdentifier();
                                                p.print("export");
                                                p.printSpace();
                                            }

                                            continue;
                                        }
                                    }
                                }
                            }

                            i += 1;
                        }

                        if (array.items.len == 0) {
                            return;
                        }

                        s.items = array.items;
                    }

                    p.print("{");

                    if (!s.is_single_line) {
                        p.indent();
                    } else {
                        p.printSpace();
                    }

                    for (s.items, 0..) |item, i| {
                        if (i != 0) {
                            p.print(",");
                            if (s.is_single_line) {
                                p.printSpace();
                            }
                        }

                        if (!s.is_single_line) {
                            p.printNewline();
                            p.printIndent();
                        }

                        p.printExportClauseItem(item);
                    }

                    if (!s.is_single_line) {
                        p.unindent();
                        p.printNewline();
                        p.printIndent();
                    } else {
                        p.printSpace();
                    }

                    p.print("}");
                    p.printSemicolonAfterStatement();
                },
                .s_export_from => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);

                    const import_record = p.importRecord(s.import_record_index);

                    p.printWhitespacer(ws("export {"));

                    if (!s.is_single_line) {
                        p.indent();
                    } else {
                        p.printSpace();
                    }

                    for (s.items, 0..) |item, i| {
                        if (i != 0) {
                            p.print(",");
                            if (s.is_single_line) {
                                p.printSpace();
                            }
                        }

                        if (!s.is_single_line) {
                            p.printNewline();
                            p.printIndent();
                        }
                        p.printExportClauseItem(item);
                    }

                    if (!s.is_single_line) {
                        p.unindent();
                        p.printNewline();
                        p.printIndent();
                    } else {
                        p.printSpace();
                    }

                    p.printWhitespacer(ws("} from "));
                    p.printImportRecordPath(import_record);
                    p.printSemicolonAfterStatement();
                },
                .s_local => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    switch (s.kind) {
                        .k_const => {
                            p.printDeclStmt(s.is_export, "const", s.decls.slice());
                        },
                        .k_let => {
                            p.printDeclStmt(s.is_export, "let", s.decls.slice());
                        },
                        .k_var => {
                            p.printDeclStmt(s.is_export, "var", s.decls.slice());
                        },
                        .k_using => {
                            p.printDeclStmt(s.is_export, "using", s.decls.slice());
                        },
                        .k_await_using => {
                            p.printDeclStmt(s.is_export, "await using", s.decls.slice());
                        },
                    }
                },
                .s_if => |s| {
                    p.printIndent();
                    p.printIf(s, stmt.loc);
                },
                .s_do_while => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("do");
                    switch (s.body.data) {
                        .s_block => {
                            p.printSpace();
                            p.printBlock(s.body.loc, s.body.data.s_block.stmts, s.body.data.s_block.close_brace_loc);
                            p.printSpace();
                        },
                        else => {
                            p.printNewline();
                            p.indent();
                            p.printStmt(s.body) catch unreachable;
                            p.printSemicolonIfNeeded();
                            p.unindent();
                            p.printIndent();
                        },
                    }

                    p.print("while");
                    p.printSpace();
                    p.print("(");
                    p.printExpr(s.test_, .lowest, ExprFlag.None());
                    p.print(")");
                    p.printSemicolonAfterStatement();
                },
                .s_for_in => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("for");
                    p.printSpace();
                    p.print("(");
                    p.printForLoopInit(s.init);
                    p.printSpace();
                    p.printSpaceBeforeIdentifier();
                    p.print("in");
                    p.printSpace();
                    p.printExpr(s.value, .lowest, ExprFlag.None());
                    p.print(")");
                    p.printBody(s.body);
                },
                .s_for_of => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("for");
                    if (s.is_await) {
                        p.print(" await");
                    }
                    p.printSpace();
                    p.print("(");
                    p.for_of_init_start = p.writer.written;
                    p.printForLoopInit(s.init);
                    p.printSpace();
                    p.printSpaceBeforeIdentifier();
                    p.print("of");
                    p.printSpace();
                    p.printExpr(s.value, .comma, ExprFlag.None());
                    p.print(")");
                    p.printBody(s.body);
                },
                .s_while => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("while");
                    p.printSpace();
                    p.print("(");
                    p.printExpr(s.test_, .lowest, ExprFlag.None());
                    p.print(")");
                    p.printBody(s.body);
                },
                .s_with => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("with");
                    p.printSpace();
                    p.print("(");
                    p.printExpr(s.value, .lowest, ExprFlag.None());
                    p.print(")");
                    p.printBody(s.body);
                },
                .s_label => |s| {
                    if (!p.options.minify_whitespace and p.options.indent.count > 0) {
                        p.printIndent();
                    }
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.printSymbol(s.name.ref orelse Output.panic("Internal error: expected label to have a name {any}", .{s}));
                    p.print(":");
                    p.printBody(s.stmt);
                },
                .s_try => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("try");
                    p.printSpace();
                    p.printBlock(s.body_loc, s.body, null);

                    if (s.catch_) |catch_| {
                        p.printSpace();
                        p.addSourceMapping(catch_.loc);
                        p.print("catch");
                        if (catch_.binding) |binding| {
                            p.printSpace();
                            p.print("(");
                            p.printBinding(binding);
                            p.print(")");
                        }
                        p.printSpace();
                        p.printBlock(catch_.body_loc, catch_.body, null);
                    }

                    if (s.finally) |finally| {
                        p.printSpace();
                        p.print("finally");
                        p.printSpace();
                        p.printBlock(finally.loc, finally.stmts, null);
                    }

                    p.printNewline();
                },
                .s_for => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("for");
                    p.printSpace();
                    p.print("(");

                    if (s.init) |init_| {
                        p.printForLoopInit(init_);
                    }

                    p.print(";");

                    if (s.test_) |test_| {
                        p.printExpr(test_, .lowest, ExprFlag.None());
                    }

                    p.print(";");
                    p.printSpace();

                    if (s.update) |update| {
                        p.printExpr(update, .lowest, ExprFlag.None());
                    }

                    p.print(")");
                    p.printBody(s.body);
                },
                .s_switch => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("switch");
                    p.printSpace();
                    p.print("(");

                    p.printExpr(s.test_, .lowest, ExprFlag.None());

                    p.print(")");
                    p.printSpace();
                    p.print("{");
                    p.printNewline();
                    p.indent();

                    for (s.cases) |c| {
                        p.printSemicolonIfNeeded();
                        p.printIndent();

                        if (c.value) |val| {
                            p.print("case");
                            p.printSpace();
                            p.printExpr(val, .logical_and, ExprFlag.None());
                        } else {
                            p.print("default");
                        }

                        p.print(":");

                        if (c.body.len == 1) {
                            switch (c.body[0].data) {
                                .s_block => {
                                    p.printSpace();
                                    p.printBlock(c.body[0].loc, c.body[0].data.s_block.stmts, c.body[0].data.s_block.close_brace_loc);
                                    p.printNewline();
                                    continue;
                                },
                                else => {},
                            }
                        }

                        p.printNewline();
                        p.indent();
                        for (c.body) |st| {
                            p.printSemicolonIfNeeded();
                            p.printStmt(st) catch unreachable;
                        }
                        p.unindent();
                    }

                    p.unindent();
                    p.printIndent();
                    p.print("}");
                    p.printNewline();
                    p.needs_semicolon = false;
                },
                .s_import => |s| {
                    bun.assert(s.import_record_index < p.import_records.len);
                    bun.debugAssert(p.options.module_type != .internal_bake_dev);

                    const record: *const ImportRecord = p.importRecord(s.import_record_index);
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);

                    if (comptime is_bun_platform) {
                        switch (record.tag) {
                            .bun_test => {
                                p.printBunJestImportStatement(s.*);
                                return;
                            },
                            .bun => {
                                p.printGlobalBunImportStatement(s.*);
                                return;
                            },
                            else => {},
                        }
                    }

                    if (record.path.is_disabled) {
                        if (record.contains_import_star) {
                            p.print("var ");
                            p.printSymbol(s.namespace_ref);
                            p.@"print = "();
                            p.printDisabledImport();
                            p.printSemicolonAfterStatement();
                        }

                        if (s.items.len > 0 or s.default_name != null) {
                            p.printIndent();
                            p.printSpaceBeforeIdentifier();
                            p.printWhitespacer(ws("var {"));

                            if (s.default_name) |default_name| {
                                p.printSpace();
                                p.print("default:");
                                p.printSpace();
                                p.printSymbol(default_name.ref.?);

                                if (s.items.len > 0) {
                                    p.printSpace();
                                    p.print(",");
                                    p.printSpace();
                                    for (s.items, 0..) |item, i| {
                                        p.printClauseItemAs(item, .@"var");

                                        if (i < s.items.len - 1) {
                                            p.print(",");
                                            p.printSpace();
                                        }
                                    }
                                }
                            } else {
                                for (s.items, 0..) |item, i| {
                                    p.printClauseItemAs(item, .@"var");

                                    if (i < s.items.len - 1) {
                                        p.print(",");
                                        p.printSpace();
                                    }
                                }
                            }

                            p.print("}");
                            p.@"print = "();

                            if (record.contains_import_star) {
                                p.printSymbol(s.namespace_ref);
                                p.printSemicolonAfterStatement();
                            } else {
                                p.printDisabledImport();
                                p.printSemicolonAfterStatement();
                            }
                        }

                        return;
                    }

                    if (record.handles_import_errors and record.path.is_disabled and record.kind.isCommonJS()) {
                        return;
                    }

                    p.print("import");

                    var item_count: usize = 0;

                    if (s.default_name) |name| {
                        p.print(" ");
                        p.printSymbol(name.ref.?);
                        item_count += 1;
                    }

                    if (s.items.len > 0) {
                        if (item_count > 0) {
                            p.print(",");
                        }
                        p.printSpace();

                        p.print("{");
                        if (!s.is_single_line) {
                            p.indent();
                        } else {
                            p.printSpace();
                        }

                        for (s.items, 0..) |item, i| {
                            if (i != 0) {
                                p.print(",");
                                if (s.is_single_line) {
                                    p.printSpace();
                                }
                            }

                            if (!s.is_single_line) {
                                p.printNewline();
                                p.printIndent();
                            }

                            p.printClauseItem(item);
                        }

                        if (!s.is_single_line) {
                            p.unindent();
                            p.printNewline();
                            p.printIndent();
                        } else {
                            p.printSpace();
                        }
                        p.print("}");
                        item_count += 1;
                    }

                    if (record.contains_import_star) {
                        if (item_count > 0) {
                            p.print(",");
                        }
                        p.printSpace();

                        p.printWhitespacer(ws("* as"));
                        p.print(" ");
                        p.printSymbol(s.namespace_ref);
                        item_count += 1;
                    }

                    if (item_count > 0) {
                        if (!p.options.minify_whitespace or
                            record.contains_import_star or
                            s.items.len == 0)
                            p.print(" ");

                        p.printWhitespacer(ws("from "));
                    }

                    p.printImportRecordPath(record);

                    // backwards compatibility: previously, we always stripped type
                    if (comptime is_bun_platform) if (record.loader) |loader| switch (loader) {
                        .jsx => p.printWhitespacer(ws(" with { type: \"jsx\" }")),
                        .js => p.printWhitespacer(ws(" with { type: \"js\" }")),
                        .ts => p.printWhitespacer(ws(" with { type: \"ts\" }")),
                        .tsx => p.printWhitespacer(ws(" with { type: \"tsx\" }")),
                        .css => p.printWhitespacer(ws(" with { type: \"css\" }")),
                        .file => p.printWhitespacer(ws(" with { type: \"file\" }")),
                        .json => p.printWhitespacer(ws(" with { type: \"json\" }")),
                        .jsonc => p.printWhitespacer(ws(" with { type: \"jsonc\" }")),
                        .toml => p.printWhitespacer(ws(" with { type: \"toml\" }")),
                        .wasm => p.printWhitespacer(ws(" with { type: \"wasm\" }")),
                        .napi => p.printWhitespacer(ws(" with { type: \"napi\" }")),
                        .base64 => p.printWhitespacer(ws(" with { type: \"base64\" }")),
                        .dataurl => p.printWhitespacer(ws(" with { type: \"dataurl\" }")),
                        .text => p.printWhitespacer(ws(" with { type: \"text\" }")),
                        .bunsh => p.printWhitespacer(ws(" with { type: \"sh\" }")),
                        // sqlite_embedded only relevant when bundling
                        .sqlite, .sqlite_embedded => p.printWhitespacer(ws(" with { type: \"sqlite\" }")),
                        .html => p.printWhitespacer(ws(" with { type: \"html\" }")),
                    };
                    p.printSemicolonAfterStatement();
                },
                .s_block => |s| {
                    p.printIndent();
                    p.printBlock(stmt.loc, s.stmts, s.close_brace_loc);
                    p.printNewline();
                },
                .s_debugger => {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("debugger");
                    p.printSemicolonAfterStatement();
                },
                .s_directive => |s| {
                    if (comptime is_json)
                        unreachable;

                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.printStringLiteralUTF8(s.value, false);
                    p.printSemicolonAfterStatement();
                },
                .s_break => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("break");
                    if (s.label) |label| {
                        p.print(" ");
                        p.printSymbol(label.ref.?);
                    }

                    p.printSemicolonAfterStatement();
                },
                .s_continue => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("continue");

                    if (s.label) |label| {
                        p.print(" ");
                        p.printSymbol(label.ref.?);
                    }
                    p.printSemicolonAfterStatement();
                },
                .s_return => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("return");

                    if (s.value) |value| {
                        p.printSpace();
                        p.printExpr(value, .lowest, ExprFlag.None());
                    }
                    p.printSemicolonAfterStatement();
                },
                .s_throw => |s| {
                    p.printIndent();
                    p.printSpaceBeforeIdentifier();
                    p.addSourceMapping(stmt.loc);
                    p.print("throw");
                    p.printSpace();
                    p.printExpr(s.value, .lowest, ExprFlag.None());
                    p.printSemicolonAfterStatement();
                },
                .s_expr => |s| {
                    if (!p.options.minify_whitespace and p.options.indent.count > 0) {
                        p.printIndent();
                    }

                    p.stmt_start = p.writer.written;
                    p.printExpr(s.value, .lowest, ExprFlag.ExprResultIsUnused());
                    p.printSemicolonAfterStatement();
                },
                else => |tag| {
                    Output.panic("Unexpected tag in printStmt: .{s}", .{@tagName(tag)});
                },
            }
        }

        pub inline fn printModuleExportSymbol(p: *Printer) void {
            p.print("module.exports");
        }

        pub fn printImportRecordPath(p: *Printer, import_record: *const ImportRecord) void {
            if (comptime is_json)
                unreachable;

            const quote = bestQuoteCharForString(u8, import_record.path.text, false);
            if (import_record.print_namespace_in_path and !import_record.path.isFile()) {
                p.print(quote);
                p.printStringCharactersUTF8(import_record.path.namespace, quote);
                p.print(":");
                p.printStringCharactersUTF8(import_record.path.text, quote);
                p.print(quote);
            } else {
                p.print(quote);
                p.printStringCharactersUTF8(import_record.path.text, quote);
                p.print(quote);
            }
        }

        pub fn printBundledImport(p: *Printer, record: ImportRecord, s: *S.Import) void {
            if (record.is_internal) {
                return;
            }

            const import_record = p.importRecord(s.import_record_index);
            const is_disabled = import_record.path.is_disabled;
            const module_id = import_record.module_id;

            // If the bundled import was disabled and only imported for side effects
            // we can skip it

            if (record.path.is_disabled) {
                if (p.symbols().get(s.namespace_ref) == null)
                    return;
            }

            switch (ImportVariant.determine(&record, s)) {
                .path_only => {
                    if (!is_disabled) {
                        p.printCallModuleID(module_id);
                        p.printSemicolonAfterStatement();
                    }
                },
                .import_items_and_default, .import_default => {
                    if (!is_disabled) {
                        p.print("var $");
                        p.printModuleId(module_id);
                        p.@"print = "();
                        p.printLoadFromBundle(s.import_record_index);

                        if (s.default_name) |default_name| {
                            p.print(", ");
                            p.printSymbol(default_name.ref.?);
                            p.print(" = (($");
                            p.printModuleId(module_id);

                            p.print(" && \"default\" in $");
                            p.printModuleId(module_id);
                            p.print(") ? $");
                            p.printModuleId(module_id);
                            p.print(".default : $");
                            p.printModuleId(module_id);
                            p.print(")");
                        }
                    } else {
                        if (s.default_name) |default_name| {
                            p.print("var ");
                            p.printSymbol(default_name.ref.?);
                            p.@"print = "();
                            p.printDisabledImport();
                        }
                    }

                    p.printSemicolonAfterStatement();
                },
                .import_star_and_import_default => {
                    p.print("var ");
                    p.printSymbol(s.namespace_ref);
                    p.@"print = "();
                    p.printLoadFromBundle(s.import_record_index);

                    if (s.default_name) |default_name| {
                        p.print(",");
                        p.printSpace();
                        p.printSymbol(default_name.ref.?);
                        p.@"print = "();

                        if (!is_bun_platform) {
                            p.print("(");
                            p.printSymbol(s.namespace_ref);
                            p.printWhitespacer(ws(" && \"default\" in "));
                            p.printSymbol(s.namespace_ref);
                            p.printWhitespacer(ws(" ? "));
                            p.printSymbol(s.namespace_ref);
                            p.printWhitespacer(ws(".default : "));
                            p.printSymbol(s.namespace_ref);
                            p.print(")");
                        } else {
                            p.printSymbol(s.namespace_ref);
                        }
                    }
                    p.printSemicolonAfterStatement();
                },
                .import_star => {
                    p.print("var ");
                    p.printSymbol(s.namespace_ref);
                    p.@"print = "();
                    p.printLoadFromBundle(s.import_record_index);
                    p.printSemicolonAfterStatement();
                },

                else => {
                    p.print("var $");
                    p.printModuleIdAssumeEnabled(module_id);
                    p.@"print = "();
                    p.printLoadFromBundle(s.import_record_index);
                    p.printSemicolonAfterStatement();
                },
            }
        }
        pub fn printLoadFromBundle(p: *Printer, import_record_index: u32) void {
            p.printLoadFromBundleWithoutCall(import_record_index);
            p.print("()");
        }

        inline fn printDisabledImport(p: *Printer) void {
            p.printWhitespacer(ws("(() => ({}))"));
        }

        pub fn printLoadFromBundleWithoutCall(p: *Printer, import_record_index: u32) void {
            const record = p.importRecord(import_record_index);
            if (record.path.is_disabled) {
                p.printDisabledImport();
                return;
            }

            @call(bun.callmod_inline, printModuleId, .{ p, p.importRecord(import_record_index).module_id });
        }

        pub fn printCallModuleID(p: *Printer, module_id: u32) void {
            printModuleId(p, module_id);
            p.print("()");
        }

        inline fn printModuleId(p: *Printer, module_id: u32) void {
            bun.assert(module_id != 0); // either module_id is forgotten or it should be disabled
            p.printModuleIdAssumeEnabled(module_id);
        }

        inline fn printModuleIdAssumeEnabled(p: *Printer, module_id: u32) void {
            p.print("$");
            std.fmt.formatInt(module_id, 16, .lower, .{}, p) catch unreachable;
        }

        pub fn printBundledRexport(p: *Printer, name: string, import_record_index: u32) void {
            p.print("Object.defineProperty(");
            p.printModuleExportSymbol();
            p.print(",");
            p.printStringLiteralUTF8(name, true);

            p.printWhitespacer(ws(",{get: () => ("));
            p.printLoadFromBundle(import_record_index);
            p.printWhitespacer(ws("), enumerable: true, configurable: true})"));
        }

        // We must use Object.defineProperty() to handle re-exports from ESM -> CJS
        // Here is an example where a runtime error occurs when assigning directly to module.exports
        // > 24077 |   module.exports.init = init;
        // >       ^
        // >  TypeError: Attempted to assign to readonly property.
        pub fn printBundledExport(p: *Printer, name: string, identifier: string) void {
            // In the event that
            p.print("Object.defineProperty(");
            p.printModuleExportSymbol();
            p.print(",");
            p.printStringLiteralUTF8(name, true);
            p.print(",{get: () => ");
            p.printIdentifier(identifier);
            p.print(", enumerable: true, configurable: true})");
        }

        pub fn printForLoopInit(p: *Printer, initSt: Stmt) void {
            switch (initSt.data) {
                .s_expr => |s| {
                    p.printExpr(
                        s.value,
                        .lowest,
                        ExprFlag.Set.init(.{ .forbid_in = true, .expr_result_is_unused = true }),
                    );
                },
                .s_local => |s| {
                    switch (s.kind) {
                        .k_var => {
                            p.printDecls("var", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }));
                        },
                        .k_let => {
                            p.printDecls("let", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }));
                        },
                        .k_const => {
                            p.printDecls("const", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }));
                        },
                        .k_using => {
                            p.printDecls("using", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }));
                        },
                        .k_await_using => {
                            p.printDecls("await using", s.decls.slice(), ExprFlag.Set.init(.{ .forbid_in = true }));
                        },
                    }
                },
                // for(;)
                .s_empty => {},
                else => {
                    Output.panic("Internal error: Unexpected stmt in for loop {any}", .{initSt});
                },
            }
        }
        pub fn printIf(p: *Printer, s: *const S.If, loc: logger.Loc) void {
            p.printSpaceBeforeIdentifier();
            p.addSourceMapping(loc);
            p.print("if");
            p.printSpace();
            p.print("(");
            p.printExpr(s.test_, .lowest, ExprFlag.None());
            p.print(")");

            switch (s.yes.data) {
                .s_block => |block| {
                    p.printSpace();
                    p.printBlock(s.yes.loc, block.stmts, block.close_brace_loc);

                    if (s.no != null) {
                        p.printSpace();
                    } else {
                        p.printNewline();
                    }
                },
                else => {
                    if (wrapToAvoidAmbiguousElse(&s.yes.data)) {
                        p.printSpace();
                        p.print("{");
                        p.printNewline();

                        p.indent();
                        p.printStmt(s.yes) catch unreachable;
                        p.unindent();
                        p.needs_semicolon = false;

                        p.printIndent();
                        p.print("}");

                        if (s.no != null) {
                            p.printSpace();
                        } else {
                            p.printNewline();
                        }
                    } else {
                        p.printNewline();
                        p.indent();
                        p.printStmt(s.yes) catch unreachable;
                        p.unindent();

                        if (s.no != null) {
                            p.printIndent();
                        }
                    }
                },
            }

            if (s.no) |no_block| {
                p.printSemicolonIfNeeded();
                p.printSpaceBeforeIdentifier();
                p.addSourceMapping(no_block.loc);
                p.print("else");

                switch (no_block.data) {
                    .s_block => {
                        p.printSpace();
                        p.printBlock(no_block.loc, no_block.data.s_block.stmts, null);
                        p.printNewline();
                    },
                    .s_if => {
                        p.printIf(no_block.data.s_if, no_block.loc);
                    },
                    else => {
                        p.printNewline();
                        p.indent();
                        p.printStmt(no_block) catch unreachable;
                        p.unindent();
                    },
                }
            }
        }

        pub fn wrapToAvoidAmbiguousElse(s_: *const Stmt.Data) bool {
            var s = s_;
            while (true) {
                switch (s.*) {
                    .s_if => |index| {
                        if (index.no) |*no| {
                            s = &no.data;
                        } else {
                            return true;
                        }
                    },
                    .s_for => |current| {
                        s = &current.body.data;
                    },
                    .s_for_in => |current| {
                        s = &current.body.data;
                    },
                    .s_for_of => |current| {
                        s = &current.body.data;
                    },
                    .s_while => |current| {
                        s = &current.body.data;
                    },
                    .s_with => |current| {
                        s = &current.body.data;
                    },
                    else => {
                        return false;
                    },
                }
            }
        }

        pub fn tryToGetImportedEnumValue(p: *Printer, target: Expr, name: []const u8) ?js_ast.InlinedEnumValue.Decoded {
            if (target.data.as(.e_import_identifier)) |id| {
                const ref = p.symbols().follow(id.ref);
                if (p.symbols().get(ref)) |symbol| {
                    if (symbol.kind == .ts_enum) {
                        if (p.options.ts_enums.get(ref)) |enum_value| {
                            if (enum_value.get(name)) |value|
                                return value.decode();
                        }
                    }
                }
            }
            return null;
        }

        pub fn printInlinedEnum(
            p: *Printer,
            inlined: js_ast.InlinedEnumValue.Decoded,
            comment: []const u8,
            level: Level,
        ) void {
            switch (inlined) {
                .number => |num| p.printNumber(num, level),

                // TODO: extract printString
                .string => |str| p.printExpr(.{
                    .data = .{ .e_string = str },
                    .loc = logger.Loc.Empty,
                }, level, .{}),
            }

            if (!p.options.minify_whitespace and !p.options.minify_identifiers) {
                // TODO: rewrite this to handle </script>
                if (!bun.strings.containsComptime(comment, "*/")) {
                    p.print(" /* ");
                    p.print(comment);
                    p.print(" */");
                }
            }
        }

        pub fn printDeclStmt(p: *Printer, is_export: bool, comptime keyword: string, decls: []G.Decl) void {
            if (!rewrite_esm_to_cjs and is_export) {
                p.print("export ");
            }
            p.printDecls(keyword, decls, ExprFlag.None());
            p.printSemicolonAfterStatement();
            if (rewrite_esm_to_cjs and is_export and decls.len > 0) {
                for (decls) |decl| {
                    p.printIndent();
                    p.printSymbol(p.options.runtime_imports.__export.?.ref);
                    p.print("(");
                    p.printSpaceBeforeIdentifier();
                    p.printModuleExportSymbol();
                    p.print(",");
                    p.printSpace();

                    switch (decl.binding.data) {
                        .b_identifier => |ident| {
                            p.print("{");
                            p.printSpace();
                            p.printSymbol(ident.ref);
                            if (p.options.minify_whitespace)
                                p.print(":()=>(")
                            else
                                p.print(": () => (");
                            p.printSymbol(ident.ref);
                            p.print(") }");
                        },
                        .b_object => |obj| {
                            p.print("{");
                            p.printSpace();
                            for (obj.properties) |prop| {
                                switch (prop.value.data) {
                                    .b_identifier => |ident| {
                                        p.printSymbol(ident.ref);
                                        if (p.options.minify_whitespace)
                                            p.print(":()=>(")
                                        else
                                            p.print(": () => (");
                                        p.printSymbol(ident.ref);
                                        p.print("),");
                                        p.printNewline();
                                    },
                                    else => {},
                                }
                            }
                            p.print("}");
                        },
                        else => {
                            p.printBinding(decl.binding);
                        },
                    }
                    p.print(")");
                    p.printSemicolonAfterStatement();
                }
            }
        }

        pub fn printIdentifier(p: *Printer, identifier: string) void {
            if (comptime ascii_only) {
                p.printIdentifierAsciiOnly(identifier);
            } else {
                p.print(identifier);
            }
        }

        fn printIdentifierAsciiOnly(p: *Printer, identifier: string) void {
            var ascii_start: usize = 0;
            var is_ascii = false;
            var iter = CodepointIterator.init(identifier);
            var cursor = CodepointIterator.Cursor{};
            while (iter.next(&cursor)) {
                switch (cursor.c) {
                    first_ascii...last_ascii => {
                        if (!is_ascii) {
                            ascii_start = cursor.i;
                            is_ascii = true;
                        }
                    },
                    else => {
                        if (is_ascii) {
                            p.print(identifier[ascii_start..cursor.i]);
                            is_ascii = false;
                        }

                        p.print("\\u{");
                        std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable;
                        p.print("}");
                    },
                }
            }

            if (is_ascii) {
                p.print(identifier[ascii_start..]);
            }
        }

        pub fn printIdentifierUTF16(p: *Printer, name: []const u16) !void {
            const n = name.len;
            var i: usize = 0;

            const CodeUnitType = u32;
            while (i < n) {
                var c: CodeUnitType = name[i];
                i += 1;

                if (c & ~@as(CodeUnitType, 0x03ff) == 0xd800 and i < n) {
                    c = 0x10000 + (((c & 0x03ff) << 10) | (name[i] & 0x03ff));
                    i += 1;
                }

                if ((comptime ascii_only) and c > last_ascii) {
                    switch (c) {
                        0...0xFFFF => {
                            p.print(
                                [_]u8{
                                    '\\',
                                    'u',
                                    hex_chars[c >> 12],
                                    hex_chars[(c >> 8) & 15],
                                    hex_chars[(c >> 4) & 15],
                                    hex_chars[c & 15],
                                },
                            );
                        },
                        else => {
                            p.print("\\u");
                            var buf_ptr = p.writer.reserve(4) catch unreachable;
                            p.writer.advance(strings.encodeWTF8RuneT(buf_ptr[0..4], CodeUnitType, c));
                        },
                    }
                    continue;
                }

                {
                    var buf_ptr = p.writer.reserve(4) catch unreachable;
                    p.writer.advance(strings.encodeWTF8RuneT(buf_ptr[0..4], CodeUnitType, c));
                }
            }
        }

        pub fn printNumber(p: *Printer, value: f64, level: Level) void {
            const absValue = @abs(value);
            if (std.math.isNan(value)) {
                p.printSpaceBeforeIdentifier();
                p.print("NaN");
            } else if (std.math.isPositiveInf(value) or std.math.isNegativeInf(value)) {
                const wrap = ((!p.options.has_run_symbol_renamer or p.options.minify_syntax) and level.gte(.multiply)) or
                    (std.math.isNegativeInf(value) and level.gte(.prefix));

                if (wrap) {
                    p.print("(");
                }

                if (std.math.isNegativeInf(value)) {
                    p.printSpaceBeforeOperator(.un_neg);
                    p.print("-");
                } else {
                    p.printSpaceBeforeIdentifier();
                }

                // If we are not running the symbol renamer, we must not print "Infinity".
                // Some code may assign `Infinity` to another idenitifier.
                //
                // We do not want:
                //
                //   const Infinity = 1 / 0
                //
                // to be transformed into:
                //
                //   const Infinity = Infinity
                //
                if (is_json or (!p.options.minify_syntax and p.options.has_run_symbol_renamer)) {
                    p.print("Infinity");
                } else if (p.options.minify_whitespace) {
                    p.print("1/0");
                } else {
                    p.print("1 / 0");
                }

                if (wrap) {
                    p.print(")");
                }
            } else if (!std.math.signbit(value)) {
                p.printSpaceBeforeIdentifier();

                p.printNonNegativeFloat(absValue);

                // Remember the end of the latest number
                p.prev_num_end = p.writer.written;
            } else if (level.gte(.prefix)) {
                // Expressions such as "(-1).toString" need to wrap negative numbers.
                // Instead of testing for "value < 0" we test for "signbit(value)" and
                // "!isNaN(value)" because we need this to be true for "-0" and "-0 < 0"
                // is false.
                p.print("(-");
                p.printNonNegativeFloat(absValue);
                p.print(")");
            } else {
                p.printSpaceBeforeOperator(Op.Code.un_neg);
                p.print("-");
                p.printNonNegativeFloat(absValue);

                // Remember the end of the latest number
                p.prev_num_end = p.writer.written;
            }
        }

        pub fn printIndentedComment(p: *Printer, _text: string) void {
            var text = _text;
            if (strings.startsWith(text, "/*")) {
                // Re-indent multi-line comments
                while (strings.indexOfChar(text, '\n')) |newline_index| {
                    p.printIndent();
                    p.print(text[0 .. newline_index + 1]);
                    text = text[newline_index + 1 ..];
                }
                p.printIndent();
                p.print(text);
                p.printNewline();
            } else {
                // Print a mandatory newline after single-line comments
                p.printIndent();
                p.print(text);
                p.print("\n");
            }
        }

        pub fn init(
            writer: Writer,
            import_records: []const ImportRecord,
            opts: Options,
            renamer: bun.renamer.Renamer,
            source_map_builder: SourceMap.Chunk.Builder,
        ) Printer {
            var printer = Printer{
                .import_records = import_records,
                .options = opts,
                .writer = writer,
                .renamer = renamer,
                .source_map_builder = source_map_builder,
            };
            if (comptime generate_source_map) {
                // This seems silly to cache but the .items() function apparently costs 1ms according to Instruments.
                printer.source_map_builder.line_offset_table_byte_offset_list =
                    printer
                        .source_map_builder
                        .line_offset_tables
                        .items(.byte_offset_to_start_of_line);
            }

            return printer;
        }

        fn printDevServerModule(
            p: *Printer,
            source: *const logger.Source,
            ast: *const Ast,
            part: *const js_ast.Part,
        ) void {
            p.indent();
            p.printIndent();

            p.printStringLiteralUTF8(source.path.pretty, false);

            const func = part.stmts[0].data.s_expr.value.data.e_function.func;

            // Special-case lazy-export AST
            if (ast.has_lazy_export) {
                @branchHint(.unlikely);
                p.printFnArgs(func.open_parens_loc, func.args, func.flags.contains(.has_rest_arg), false);
                p.printSpace();
                p.print("{\n");
                if (func.body.stmts[0].data.s_lazy_export.* != .e_undefined) {
                    p.indent();
                    p.printIndent();
                    p.printSymbol(p.options.hmr_ref);
                    p.print(".cjs.exports = ");
                    p.printExpr(.{
                        .data = func.body.stmts[0].data.s_lazy_export.*,
                        .loc = func.body.stmts[0].loc,
                    }, .comma, .{});
                    p.print("; // bun .s_lazy_export\n");
                    p.unindent();
                }
                p.printIndent();
                p.print("},\n");
                return;
            }

            // ESM is represented by an array tuple [ dependencies, exports, starImports, load, async ];
            else if (ast.exports_kind == .esm) {
                p.print(": [ [");
                // Print the dependencies.
                if (part.stmts.len > 1) {
                    p.indent();
                    p.print("\n");
                    for (part.stmts[1..]) |stmt| {
                        p.printIndent();
                        const import = stmt.data.s_import;
                        const record = p.importRecord(import.import_record_index);
                        p.printStringLiteralUTF8(record.path.pretty, false);

                        const item_count = @as(u32, @intFromBool(import.default_name != null)) +
                            @as(u32, @intCast(import.items.len));
                        p.fmt(", {d},", .{item_count}) catch {};
                        if (item_count == 0) {
                            // Add a comment explaining why the number could be zero
                            p.print(if (import.star_name_loc != null) " // namespace import" else " // bare import");
                        } else {
                            if (import.default_name != null) {
                                p.print(" \"default\",");
                            }
                            for (import.items) |item| {
                                p.print(" ");
                                p.printStringLiteralUTF8(item.alias, false);
                                p.print(",");
                            }
                        }
                        p.print("\n");
                    }
                    p.unindent();
                    p.printIndent();
                }
                p.print("], [");

                // Print the exports
                if (ast.named_exports.count() > 0) {
                    p.indent();
                    var len: usize = std.math.maxInt(usize);
                    for (ast.named_exports.keys()) |key| {
                        if (len > 120) {
                            p.printNewline();
                            p.printIndent();
                            len = 0;
                        } else {
                            p.print(" ");
                        }
                        len += key.len;
                        p.printStringLiteralUTF8(key, false);
                        p.print(",");
                    }
                    p.unindent();
                    p.printNewline();
                    p.printIndent();
                }
                p.print("], [");

                // Print export stars
                p.indent();
                var had_any_stars = false;
                for (ast.export_star_import_records) |star| {
                    const record = p.importRecord(star);
                    if (record.path.is_disabled) continue;
                    had_any_stars = true;
                    p.printNewline();
                    p.printIndent();
                    p.printStringLiteralUTF8(record.path.pretty, false);
                    p.print(",");
                }
                p.unindent();
                if (had_any_stars) {
                    p.printNewline();
                    p.printIndent();
                }
                p.print("], ");

                // Print the code
                if (!ast.top_level_await_keyword.isEmpty()) p.print("async");
                p.printFnArgs(func.open_parens_loc, func.args, func.flags.contains(.has_rest_arg), false);
                p.print(" => {\n");
                p.indent();
                p.printBlockBody(func.body.stmts);
                p.unindent();
                p.printIndent();
                p.print("}, ");

                // Print isAsync
                p.print(if (!ast.top_level_await_keyword.isEmpty()) "true" else "false");
                p.print("],\n");
            } else {
                bun.assert(ast.exports_kind == .cjs);
                p.printFunc(func);
                p.print(",\n");
            }

            p.unindent();
        }
    };
}

pub const WriteResult = struct {
    off: u32,
    len: usize,
    end_off: u32,
};

pub fn NewWriter(
    comptime ContextType: type,
    comptime writeByte: fn (ctx: *ContextType, char: u8) anyerror!usize,
    comptime writeAllFn: fn (ctx: *ContextType, buf: anytype) anyerror!usize,
    comptime getLastByte: fn (ctx: *const ContextType) u8,
    comptime getLastLastByte: fn (ctx: *const ContextType) u8,
    comptime reserveNext: fn (ctx: *ContextType, count: u64) anyerror![*]u8,
    comptime advanceBy: fn (ctx: *ContextType, count: u64) void,
) type {
    return struct {
        const Self = @This();
        ctx: ContextType,
        written: i32 = -1,
        // Used by the printer
        prev_char: u8 = 0,
        prev_prev_char: u8 = 0,
        err: ?anyerror = null,
        orig_err: ?anyerror = null,

        pub fn init(ctx: ContextType) Self {
            return .{
                .ctx = ctx,
            };
        }

        pub fn stdWriter(self: *Self) std.io.Writer(*Self, error{}, stdWriterWrite) {
            return .{ .context = self };
        }
        pub fn stdWriterWrite(self: *Self, bytes: []const u8) error{}!usize {
            self.print([]const u8, bytes);
            return bytes.len;
        }

        pub fn isCopyFileRangeSupported() bool {
            return comptime std.meta.hasFn(ContextType, "copyFileRange");
        }

        pub fn copyFileRange(ctx: ContextType, in_file: StoredFileDescriptorType, start: usize, end: usize) !void {
            ctx.sendfile(
                in_file,
                start,
                end,
            );
        }

        pub fn getMutableBuffer(this: *Self) *MutableString {
            return this.ctx.getMutableBuffer();
        }

        pub fn slice(this: *Self) string {
            return this.ctx.slice();
        }

        pub fn getError(writer: *const Self) anyerror!void {
            if (writer.orig_err) |orig_err| {
                return orig_err;
            }

            if (writer.err) |err| {
                return err;
            }
        }

        pub inline fn prevChar(writer: *const Self) u8 {
            return @call(bun.callmod_inline, getLastByte, .{&writer.ctx});
        }

        pub inline fn prevPrevChar(writer: *const Self) u8 {
            return @call(bun.callmod_inline, getLastLastByte, .{&writer.ctx});
        }

        pub fn reserve(writer: *Self, count: u64) anyerror![*]u8 {
            return try reserveNext(&writer.ctx, count);
        }

        pub fn advance(writer: *Self, count: u64) void {
            advanceBy(&writer.ctx, count);
            writer.written += @as(i32, @intCast(count));
        }

        pub const Error = error{FormatError};

        pub fn writeAll(writer: *Self, bytes: anytype) Error!usize {
            const written = @max(writer.written, 0);
            writer.print(@TypeOf(bytes), bytes);
            return @as(usize, @intCast(writer.written)) - @as(usize, @intCast(written));
        }

        pub inline fn print(writer: *Self, comptime ValueType: type, str: ValueType) void {
            switch (ValueType) {
                comptime_int, u16, u8 => {
                    const written = writeByte(&writer.ctx, @as(u8, @intCast(str))) catch |err| brk: {
                        writer.orig_err = err;
                        break :brk 0;
                    };

                    writer.written += @as(i32, @intCast(written));
                    writer.err = if (written == 0) error.WriteFailed else writer.err;
                },
                else => {
                    const written = writeAllFn(&writer.ctx, str) catch |err| brk: {
                        writer.orig_err = err;
                        break :brk 0;
                    };

                    writer.written += @as(i32, @intCast(written));
                    if (written < str.len) {
                        writer.err = if (written == 0) error.WriteFailed else error.PartialWrite;
                    }
                },
            }
        }

        pub fn flush(writer: *Self) !void {
            if (std.meta.hasFn(ContextType, "flush")) {
                try writer.ctx.flush();
            }
        }
        pub fn done(writer: *Self) !void {
            if (std.meta.hasFn(ContextType, "done")) {
                try writer.ctx.done();
            }
        }
    };
}

pub const DirectWriter = struct {
    handle: FileDescriptorType,

    pub fn write(writer: *DirectWriter, buf: []const u8) !usize {
        return try std.posix.write(writer.handle, buf);
    }

    pub fn writeAll(writer: *DirectWriter, buf: []const u8) !void {
        _ = try std.posix.write(writer.handle, buf);
    }

    pub const Error = std.posix.WriteError;
};

pub const BufferWriter = struct {
    buffer: MutableString = undefined,
    written: []u8 = &[_]u8{},
    sentinel: [:0]const u8 = "",
    append_null_byte: bool = false,
    append_newline: bool = false,
    approximate_newline_count: usize = 0,
    last_bytes: [2]u8 = [_]u8{ 0, 0 },

    pub fn getMutableBuffer(this: *BufferWriter) *MutableString {
        return &this.buffer;
    }

    pub fn getWritten(this: *BufferWriter) []u8 {
        return this.buffer.list.items;
    }

    pub fn init(allocator: std.mem.Allocator) BufferWriter {
        return BufferWriter{
            .buffer = MutableString.initEmpty(allocator),
        };
    }

    pub fn print(ctx: *BufferWriter, comptime fmt: string, args: anytype) anyerror!void {
        try ctx.buffer.list.writer(ctx.buffer.allocator).print(fmt, args);
    }

    pub fn writeByteNTimes(ctx: *BufferWriter, byte: u8, n: usize) anyerror!void {
        try ctx.buffer.appendCharNTimes(byte, n);
    }

    pub fn writeByte(ctx: *BufferWriter, byte: u8) anyerror!usize {
        try ctx.buffer.appendChar(byte);
        ctx.approximate_newline_count += @intFromBool(byte == '\n');
        ctx.last_bytes = .{ ctx.last_bytes[1], byte };
        return 1;
    }
    pub fn writeAll(ctx: *BufferWriter, bytes: anytype) anyerror!usize {
        try ctx.buffer.append(bytes);
        ctx.approximate_newline_count += @intFromBool(bytes.len > 0 and bytes[bytes.len - 1] == '\n');

        if (bytes.len >= 2) {
            ctx.last_bytes = bytes[bytes.len - 2 ..][0..2].*;
        } else if (bytes.len >= 1) {
            ctx.last_bytes = .{ ctx.last_bytes[1], bytes[bytes.len - 1] };
        }

        return bytes.len;
    }

    pub fn slice(self: *@This()) string {
        return self.buffer.list.items;
    }

    pub fn getLastByte(ctx: *const BufferWriter) u8 {
        return ctx.last_bytes[1];
    }

    pub fn getLastLastByte(ctx: *const BufferWriter) u8 {
        return ctx.last_bytes[0];
    }

    pub fn reserveNext(ctx: *BufferWriter, count: u64) anyerror![*]u8 {
        try ctx.buffer.growIfNeeded(count);
        return @as([*]u8, @ptrCast(&ctx.buffer.list.items.ptr[ctx.buffer.list.items.len]));
    }

    pub fn advanceBy(ctx: *BufferWriter, count: u64) void {
        if (comptime Environment.isDebug) bun.assert(ctx.buffer.list.items.len + count <= ctx.buffer.list.capacity);

        ctx.buffer.list.items = ctx.buffer.list.items.ptr[0 .. ctx.buffer.list.items.len + count];

        if (count >= 2) {
            ctx.last_bytes = ctx.buffer.list.items[ctx.buffer.list.items.len - 2 ..][0..2].*;
        } else if (count >= 1) {
            ctx.last_bytes = .{ ctx.last_bytes[1], ctx.buffer.list.items[ctx.buffer.list.items.len - 1] };
        }
    }

    pub fn reset(ctx: *BufferWriter) void {
        ctx.buffer.reset();
        ctx.approximate_newline_count = 0;
        ctx.written = &.{};
    }

    pub fn writtenWithoutTrailingZero(ctx: *const BufferWriter) []u8 {
        var written = ctx.written;
        while (written.len > 0 and written[written.len - 1] == 0) {
            written = written[0 .. written.len - 1];
        }

        return written;
    }

    pub fn done(
        ctx: *BufferWriter,
    ) anyerror!void {
        if (ctx.append_newline) {
            ctx.append_newline = false;
            try ctx.buffer.appendChar('\n');
        }

        if (ctx.append_null_byte) {
            ctx.sentinel = ctx.buffer.sliceWithSentinel();
            ctx.written = ctx.buffer.slice();
        } else {
            ctx.written = ctx.buffer.slice();
        }
    }

    pub fn flush(
        _: *BufferWriter,
    ) anyerror!void {}
};
pub const BufferPrinter = NewWriter(
    BufferWriter,
    BufferWriter.writeByte,
    BufferWriter.writeAll,
    BufferWriter.getLastByte,
    BufferWriter.getLastLastByte,
    BufferWriter.reserveNext,
    BufferWriter.advanceBy,
);

pub const Format = enum {
    esm,
    cjs,

    // bun.js must escape non-latin1 identifiers in the output This is because
    // we load JavaScript as a UTF-8 buffer instead of a UTF-16 buffer
    // JavaScriptCore does not support UTF-8 identifiers when the source code
    // string is loaded as const char* We don't want to double the size of code
    // in memory...
    esm_ascii,
    cjs_ascii,
};

const GenerateSourceMap = enum {
    disable,
    lazy,
    eager,
};
pub fn getSourceMapBuilder(
    comptime generate_source_map: GenerateSourceMap,
    comptime is_bun_platform: bool,
    opts: Options,
    source: *const logger.Source,
    tree: *const Ast,
) SourceMap.Chunk.Builder {
    if (comptime generate_source_map == .disable)
        return undefined;

    return .{
        .source_map = .init(
            opts.source_map_allocator orelse opts.allocator,
            is_bun_platform and generate_source_map == .lazy,
        ),
        .cover_lines_without_mappings = true,
        .approximate_input_line_count = tree.approximate_newline_count,
        .prepend_count = is_bun_platform and generate_source_map == .lazy,
        .line_offset_tables = opts.line_offset_tables orelse brk: {
            if (generate_source_map == .lazy) break :brk SourceMap.LineOffsetTable.generate(
                opts.source_map_allocator orelse opts.allocator,
                source.contents,
                @as(
                    i32,
                    @intCast(tree.approximate_newline_count),
                ),
            );
            break :brk .empty;
        },
    };
}

pub fn printAst(
    comptime Writer: type,
    _writer: Writer,
    tree: Ast,
    symbols: js_ast.Symbol.Map,
    source: *const logger.Source,
    comptime ascii_only: bool,
    opts: Options,
    comptime generate_source_map: bool,
) !usize {
    var renamer: rename.Renamer = undefined;
    var no_op_renamer: rename.NoOpRenamer = undefined;
    var module_scope = tree.module_scope;
    if (opts.minify_identifiers) {
        const allocator = opts.allocator;
        var reserved_names = try rename.computeInitialReservedNames(allocator, opts.module_type);
        for (module_scope.children.slice()) |child| {
            child.parent = &module_scope;
        }

        rename.computeReservedNamesForScope(&module_scope, &symbols, &reserved_names, allocator);
        var minify_renamer = try rename.MinifyRenamer.init(allocator, symbols, tree.nested_scope_slot_counts, reserved_names);

        var top_level_symbols = rename.StableSymbolCount.Array.init(allocator);
        defer top_level_symbols.deinit();

        const uses_exports_ref = tree.uses_exports_ref;
        const uses_module_ref = tree.uses_module_ref;
        const exports_ref = tree.exports_ref;
        const module_ref = tree.module_ref;
        const parts = tree.parts;

        const dont_break_the_code = .{
            tree.module_ref,
            tree.exports_ref,
            tree.require_ref,
        };

        inline for (dont_break_the_code) |ref| {
            if (symbols.get(ref)) |symbol| {
                symbol.must_not_be_renamed = true;
            }
        }

        for (tree.named_exports.values()) |named_export| {
            if (symbols.get(named_export.ref)) |symbol| {
                symbol.must_not_be_renamed = true;
            }
        }

        if (uses_exports_ref) {
            try minify_renamer.accumulateSymbolUseCount(&top_level_symbols, exports_ref, 1, &.{source.index.value});
        }

        if (uses_module_ref) {
            try minify_renamer.accumulateSymbolUseCount(&top_level_symbols, module_ref, 1, &.{source.index.value});
        }

        for (parts.slice()) |part| {
            try minify_renamer.accumulateSymbolUseCounts(&top_level_symbols, part.symbol_uses, &.{source.index.value});

            for (part.declared_symbols.refs()) |declared_ref| {
                try minify_renamer.accumulateSymbolUseCount(&top_level_symbols, declared_ref, 1, &.{source.index.value});
            }
        }

        std.sort.pdq(rename.StableSymbolCount, top_level_symbols.items, {}, rename.StableSymbolCount.lessThan);

        try minify_renamer.allocateTopLevelSymbolSlots(top_level_symbols);
        var minifier = tree.char_freq.?.compile(allocator);
        try minify_renamer.assignNamesByFrequency(&minifier);

        renamer = minify_renamer.toRenamer();
    } else {
        no_op_renamer = rename.NoOpRenamer.init(symbols, source);
        renamer = no_op_renamer.toRenamer();
    }

    defer {
        if (opts.minify_identifiers) {
            renamer.deinit(opts.allocator);
        }
    }

    const PrinterType = NewPrinter(
        ascii_only,
        Writer,
        false,
        // if it's ascii_only, it is also bun
        ascii_only,
        false,
        generate_source_map,
    );
    const writer = _writer;

    var printer = PrinterType.init(
        writer,
        tree.import_records.slice(),
        opts,
        renamer,
        getSourceMapBuilder(if (generate_source_map) .lazy else .disable, ascii_only, opts, source, &tree),
    );
    defer {
        if (comptime generate_source_map) {
            printer.source_map_builder.line_offset_tables.deinit(opts.allocator);
        }
    }
    printer.was_lazy_export = tree.has_lazy_export;
    var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
    printer.binary_expression_stack = std.ArrayList(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
    defer printer.binary_expression_stack.clearAndFree();

    if (!opts.bundling and
        tree.uses_require_ref and
        tree.exports_kind == .esm and
        opts.target == .bun)
    {
        // Hoist the `var {require}=import.meta;` declaration. Previously,
        // `import.meta.require` was inlined into transpiled files, which
        // meant calling `func.toString()` on a function with `require`
        // would observe `import.meta.require` inside of the source code.
        // Normally, Bun doesn't guarantee `Function.prototype.toString`
        // will match the untranspiled source code, but in this case the new
        // code is not valid outside of an ES module (eg, in `new Function`)
        // https://github.com/oven-sh/bun/issues/15738#issuecomment-2574283514
        //
        // This is never a symbol collision because `uses_require_ref` means
        // `require` must be an unbound variable.
        printer.print("var {require}=import.meta;");
    }

    for (tree.parts.slice()) |part| {
        for (part.stmts) |stmt| {
            try printer.printStmt(stmt);
            if (printer.writer.getError()) {} else |err| {
                return err;
            }
            printer.printSemicolonIfNeeded();
        }
    }

    if (comptime FeatureFlags.runtime_transpiler_cache and generate_source_map) {
        if (opts.source_map_handler) |handler| {
            const source_maps_chunk = printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten());
            if (opts.runtime_transpiler_cache) |cache| {
                cache.put(printer.writer.ctx.getWritten(), source_maps_chunk.buffer.list.items);
            }

            try handler.onSourceMapChunk(source_maps_chunk, source.*);
        } else {
            if (opts.runtime_transpiler_cache) |cache| {
                cache.put(printer.writer.ctx.getWritten(), "");
            }
        }
    } else if (comptime generate_source_map) {
        if (opts.source_map_handler) |handler| {
            try handler.onSourceMapChunk(printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten()), source.*);
        }
    }

    try printer.writer.done();

    return @as(usize, @intCast(@max(printer.writer.written, 0)));
}

pub fn printJSON(
    comptime Writer: type,
    _writer: Writer,
    expr: Expr,
    source: *const logger.Source,
    opts: Options,
) !usize {
    const PrinterType = NewPrinter(false, Writer, false, false, true, false);
    const writer = _writer;
    var s_expr = S.SExpr{ .value = expr };
    const stmt = Stmt{ .loc = logger.Loc.Empty, .data = .{
        .s_expr = &s_expr,
    } };
    var stmts = [_]js_ast.Stmt{stmt};
    var parts = [_]js_ast.Part{.{ .stmts = &stmts }};
    const ast = Ast.initTest(&parts);
    const list = js_ast.Symbol.List.init(ast.symbols.slice());
    const nested_list = js_ast.Symbol.NestedList.init(&[_]js_ast.Symbol.List{list});
    var renamer = rename.NoOpRenamer.init(js_ast.Symbol.Map.initList(nested_list), source);

    var printer = PrinterType.init(
        writer,
        ast.import_records.slice(),
        opts,
        renamer.toRenamer(),
        undefined,
    );
    var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
    printer.binary_expression_stack = std.ArrayList(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
    defer printer.binary_expression_stack.clearAndFree();

    printer.printExpr(expr, Level.lowest, ExprFlag.Set{});
    if (printer.writer.getError()) {} else |err| {
        return err;
    }
    try printer.writer.done();

    return @as(usize, @intCast(@max(printer.writer.written, 0)));
}

pub fn print(
    allocator: std.mem.Allocator,
    target: options.Target,
    ast: Ast,
    source: *const logger.Source,
    opts: Options,
    import_records: []const ImportRecord,
    parts: []const js_ast.Part,
    renamer: bun.renamer.Renamer,
    comptime generate_source_maps: bool,
) PrintResult {
    const trace = bun.perf.trace("JSPrinter.print");
    defer trace.end();

    const buffer_writer = BufferWriter.init(allocator);
    var buffer_printer = BufferPrinter.init(buffer_writer);

    return printWithWriter(
        *BufferPrinter,
        &buffer_printer,
        target,
        ast,
        source,
        opts,
        import_records,
        parts,
        renamer,
        comptime generate_source_maps,
    );
}

pub fn printWithWriter(
    comptime Writer: type,
    writer: Writer,
    target: options.Target,
    ast: Ast,
    source: *const logger.Source,
    opts: Options,
    import_records: []const ImportRecord,
    parts: []const js_ast.Part,
    renamer: bun.renamer.Renamer,
    comptime generate_source_maps: bool,
) PrintResult {
    return switch (target.isBun()) {
        inline else => |is_bun| printWithWriterAndPlatform(
            Writer,
            writer,
            is_bun,
            ast,
            source,
            opts,
            import_records,
            parts,
            renamer,
            generate_source_maps,
        ),
    };
}

/// The real one
pub fn printWithWriterAndPlatform(
    comptime Writer: type,
    writer: Writer,
    comptime is_bun_platform: bool,
    ast: Ast,
    source: *const logger.Source,
    opts: Options,
    import_records: []const ImportRecord,
    parts: []const js_ast.Part,
    renamer: bun.renamer.Renamer,
    comptime generate_source_maps: bool,
) PrintResult {
    const prev_action = bun.crash_handler.current_action;
    defer bun.crash_handler.current_action = prev_action;
    bun.crash_handler.current_action = .{ .print = source.path.text };

    const PrinterType = NewPrinter(
        // if it's bun, it is also ascii_only
        is_bun_platform,
        Writer,
        false,
        is_bun_platform,
        false,
        generate_source_maps,
    );
    var printer = PrinterType.init(
        writer,
        import_records,
        opts,
        renamer,
        getSourceMapBuilder(if (generate_source_maps) .eager else .disable, is_bun_platform, opts, source, &ast),
    );
    printer.was_lazy_export = ast.has_lazy_export;
    var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
    printer.binary_expression_stack = std.ArrayList(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
    defer printer.binary_expression_stack.clearAndFree();

    defer printer.temporary_bindings.deinit(bun.default_allocator);
    defer writer.* = printer.writer.*;

    if (opts.module_type == .internal_bake_dev and !source.index.isRuntime()) {
        printer.printDevServerModule(source, &ast, &parts[0]);
    } else {
        // The IIFE wrapper is done in `postProcessJSChunk`, so we just manually
        // trigger an indent.
        if (opts.module_type == .iife) {
            printer.indent();
        }

        for (parts) |part| {
            for (part.stmts) |stmt| {
                printer.printStmt(stmt) catch |err| {
                    return .{ .err = err };
                };
                if (printer.writer.getError()) {} else |err| {
                    return .{ .err = err };
                }
                printer.printSemicolonIfNeeded();
            }
        }
    }

    printer.writer.done() catch |err| {
        // In bundle_v2, this is backed by an arena, but incremental uses
        // `dev.allocator` for this buffer, so it must be freed.
        printer.source_map_builder.source_map.ctx.data.deinit();

        return .{ .err = err };
    };

    const written = printer.writer.ctx.getWritten();
    const source_map: ?SourceMap.Chunk = if (generate_source_maps) brk: {
        if (written.len == 0 or printer.source_map_builder.source_map.shouldIgnore()) {
            printer.source_map_builder.source_map.ctx.data.deinit();
            break :brk null;
        }
        const chunk = printer.source_map_builder.generateChunk(written);
        assert(!chunk.should_ignore);
        break :brk chunk;
    } else null;

    return .{
        .result = .{
            .code = written,
            .source_map = source_map,
        },
    };
}

pub fn printCommonJS(
    comptime Writer: type,
    _writer: Writer,
    tree: Ast,
    symbols: js_ast.Symbol.Map,
    source: *const logger.Source,
    comptime ascii_only: bool,
    opts: Options,
    comptime generate_source_map: bool,
) !usize {
    const prev_action = bun.crash_handler.current_action;
    defer bun.crash_handler.current_action = prev_action;
    bun.crash_handler.current_action = .{ .print = source.path.text };

    const PrinterType = NewPrinter(ascii_only, Writer, true, false, false, generate_source_map);
    const writer = _writer;
    var renamer = rename.NoOpRenamer.init(symbols, source);
    var printer = PrinterType.init(
        writer,
        tree.import_records.slice(),
        opts,
        renamer.toRenamer(),
        getSourceMapBuilder(if (generate_source_map) .lazy else .disable, false, opts, source, &tree),
    );
    var bin_stack_heap = std.heap.stackFallback(1024, bun.default_allocator);
    printer.binary_expression_stack = std.ArrayList(PrinterType.BinaryExpressionVisitor).init(bin_stack_heap.get());
    defer printer.binary_expression_stack.clearAndFree();

    for (tree.parts.slice()) |part| {
        for (part.stmts) |stmt| {
            try printer.printStmt(stmt);
            if (printer.writer.getError()) {} else |err| {
                return err;
            }
            printer.printSemicolonIfNeeded();
        }
    }

    // Add a couple extra newlines at the end
    printer.writer.print(@TypeOf("\n\n"), "\n\n");

    if (comptime generate_source_map) {
        if (opts.source_map_handler) |handler| {
            try handler.onSourceMapChunk(printer.source_map_builder.generateChunk(printer.writer.ctx.getWritten()), source.*);
        }
    }

    try printer.writer.done();

    return @as(usize, @intCast(@max(printer.writer.written, 0)));
}
