package moduletransforms

import (
	"slices"

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/binder"
	"github.com/microsoft/typescript-go/internal/collections"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/printer"
	"github.com/microsoft/typescript-go/internal/transformers"
	"github.com/microsoft/typescript-go/internal/tspath"
)

type CommonJSModuleTransformer struct {
	transformers.Transformer
	topLevelVisitor           *ast.NodeVisitor // visits statements at top level of a module
	topLevelNestedVisitor     *ast.NodeVisitor // visits nested statements at top level of a module
	discardedValueVisitor     *ast.NodeVisitor // visits expressions whose values would be discarded at runtime
	assignmentPatternVisitor  *ast.NodeVisitor // visits assignment patterns in a destructuring assignment
	compilerOptions           *core.CompilerOptions
	resolver                  binder.ReferenceResolver
	getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind
	moduleKind                core.ModuleKind
	languageVersion           core.ScriptTarget
	currentSourceFile         *ast.SourceFile
	currentModuleInfo         *externalModuleInfo
	parentNode                *ast.Node // used for ancestor tracking via pushNode/popNode to detect expression identifiers
	currentNode               *ast.Node // used for ancestor tracking via pushNode/popNode to detect expression identifiers
}

func NewCommonJSModuleTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
	compilerOptions := opts.CompilerOptions
	emitContext := opts.Context
	tx := &CommonJSModuleTransformer{compilerOptions: compilerOptions, resolver: opts.Resolver, getEmitModuleFormatOfFile: opts.GetEmitModuleFormatOfFile}
	tx.topLevelVisitor = emitContext.NewNodeVisitor(tx.visitTopLevel)
	tx.topLevelNestedVisitor = emitContext.NewNodeVisitor(tx.visitTopLevelNested)
	tx.discardedValueVisitor = emitContext.NewNodeVisitor(tx.visitDiscardedValue)
	tx.assignmentPatternVisitor = emitContext.NewNodeVisitor(tx.visitAssignmentPattern)
	tx.languageVersion = compilerOptions.GetEmitScriptTarget()
	tx.moduleKind = compilerOptions.GetEmitModuleKind()
	return tx.NewTransformer(tx.visit, emitContext)
}

// Pushes a new child node onto the ancestor tracking stack, returning the grandparent node to be restored later via `popNode`.
func (tx *CommonJSModuleTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) {
	grandparentNode = tx.parentNode
	tx.parentNode = tx.currentNode
	tx.currentNode = node
	return grandparentNode
}

// Pops the last child node off the ancestor tracking stack, restoring the grandparent node.
func (tx *CommonJSModuleTransformer) popNode(grandparentNode *ast.Node) {
	tx.currentNode = tx.parentNode
	tx.parentNode = grandparentNode
}

// Visits a node at the top level of the source file.
func (tx *CommonJSModuleTransformer) visitTopLevel(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	switch node.Kind {
	case ast.KindImportDeclaration:
		node = tx.visitTopLevelImportDeclaration(node.AsImportDeclaration())
	case ast.KindImportEqualsDeclaration:
		node = tx.visitTopLevelImportEqualsDeclaration(node.AsImportEqualsDeclaration())
	case ast.KindExportDeclaration:
		node = tx.visitTopLevelExportDeclaration(node.AsExportDeclaration())
	case ast.KindExportAssignment:
		node = tx.visitTopLevelExportAssignment(node.AsExportAssignment())
	case ast.KindFunctionDeclaration:
		node = tx.visitTopLevelFunctionDeclaration(node.AsFunctionDeclaration())
	case ast.KindClassDeclaration:
		node = tx.visitTopLevelClassDeclaration(node.AsClassDeclaration())
	case ast.KindVariableStatement:
		node = tx.visitTopLevelVariableStatement(node.AsVariableStatement())
	default:
		node = tx.visitTopLevelNestedNoStack(node)
	}
	return node
}

// Visits nested elements at the top-level of a module.
func (tx *CommonJSModuleTransformer) visitTopLevelNested(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	return tx.visitTopLevelNestedNoStack(node)
}

// Visits nested elements at the top-level of a module without ancestor tracking.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedNoStack(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindVariableStatement:
		node = tx.visitTopLevelVariableStatement(node.AsVariableStatement())
	case ast.KindForStatement:
		node = tx.visitTopLevelNestedForStatement(node.AsForStatement())
	case ast.KindForInStatement, ast.KindForOfStatement:
		node = tx.visitTopLevelNestedForInOrOfStatement(node.AsForInOrOfStatement())
	case ast.KindDoStatement:
		node = tx.visitTopLevelNestedDoStatement(node.AsDoStatement())
	case ast.KindWhileStatement:
		node = tx.visitTopLevelNestedWhileStatement(node.AsWhileStatement())
	case ast.KindLabeledStatement:
		node = tx.visitTopLevelNestedLabeledStatement(node.AsLabeledStatement())
	case ast.KindWithStatement:
		node = tx.visitTopLevelNestedWithStatement(node.AsWithStatement())
	case ast.KindIfStatement:
		node = tx.visitTopLevelNestedIfStatement(node.AsIfStatement())
	case ast.KindSwitchStatement:
		node = tx.visitTopLevelNestedSwitchStatement(node.AsSwitchStatement())
	case ast.KindCaseBlock:
		node = tx.visitTopLevelNestedCaseBlock(node.AsCaseBlock())
	case ast.KindCaseClause, ast.KindDefaultClause:
		node = tx.visitTopLevelNestedCaseOrDefaultClause(node.AsCaseOrDefaultClause())
	case ast.KindTryStatement:
		node = tx.visitTopLevelNestedTryStatement(node.AsTryStatement())
	case ast.KindCatchClause:
		node = tx.visitTopLevelNestedCatchClause(node.AsCatchClause())
	case ast.KindBlock:
		node = tx.visitTopLevelNestedBlock(node.AsBlock())
	default:
		node = tx.visitNoStack(node, false /*resultIsDiscarded*/)
	}
	return node
}

// Visits source elements that are not top-level or top-level nested statements.
func (tx *CommonJSModuleTransformer) visit(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	return tx.visitNoStack(node, false /*resultIsDiscarded*/)
}

// Visits source elements that are not top-level or top-level nested statements without ancestor tracking.
func (tx *CommonJSModuleTransformer) visitNoStack(node *ast.Node, resultIsDiscarded bool) *ast.Node {
	// This visitor does not need to descend into the tree if there are no dynamic imports or identifiers in the subtree
	if !ast.IsSourceFile(node) && node.SubtreeFacts()&(ast.SubtreeContainsDynamicImport|ast.SubtreeContainsIdentifier) == 0 {
		return node
	}

	switch node.Kind {
	case ast.KindSourceFile:
		node = tx.visitSourceFile(node.AsSourceFile())
	case ast.KindForStatement:
		node = tx.visitForStatement(node.AsForStatement())
	case ast.KindForInStatement, ast.KindForOfStatement:
		node = tx.visitForInOrOfStatement(node.AsForInOrOfStatement())
	case ast.KindExpressionStatement:
		node = tx.visitExpressionStatement(node.AsExpressionStatement())
	case ast.KindVoidExpression:
		node = tx.visitVoidExpression(node.AsVoidExpression())
	case ast.KindParenthesizedExpression:
		node = tx.visitParenthesizedExpression(node.AsParenthesizedExpression(), resultIsDiscarded)
	case ast.KindPartiallyEmittedExpression:
		node = tx.visitPartiallyEmittedExpression(node.AsPartiallyEmittedExpression(), resultIsDiscarded)
	case ast.KindCallExpression:
		node = tx.visitCallExpression(node.AsCallExpression())
	case ast.KindTaggedTemplateExpression:
		node = tx.visitTaggedTemplateExpression(node.AsTaggedTemplateExpression())
	case ast.KindBinaryExpression:
		node = tx.visitBinaryExpression(node.AsBinaryExpression(), resultIsDiscarded)
	case ast.KindPrefixUnaryExpression:
		node = tx.visitPrefixUnaryExpression(node.AsPrefixUnaryExpression(), resultIsDiscarded)
	case ast.KindPostfixUnaryExpression:
		node = tx.visitPostfixUnaryExpression(node.AsPostfixUnaryExpression(), resultIsDiscarded)
	case ast.KindShorthandPropertyAssignment:
		node = tx.visitShorthandPropertyAssignment(node.AsShorthandPropertyAssignment())
	case ast.KindIdentifier:
		node = tx.visitIdentifier(node)
	default:
		node = tx.Visitor().VisitEachChild(node)
	}

	return node
}

// Visits source elements whose value is discarded if they are expressions.
func (tx *CommonJSModuleTransformer) visitDiscardedValue(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	return tx.visitNoStack(node, true /*resultIsDiscarded*/)
}

func (tx *CommonJSModuleTransformer) visitAssignmentPattern(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	return tx.visitAssignmentPatternNoStack(node)
}

func (tx *CommonJSModuleTransformer) visitAssignmentPatternNoStack(node *ast.Node) *ast.Node {
	switch node.Kind {
	// AssignmentPattern
	case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression:
		node = tx.assignmentPatternVisitor.VisitEachChild(node)

	// AssignmentProperty
	case ast.KindPropertyAssignment:
		node = tx.visitAssignmentProperty(node.AsPropertyAssignment())
	case ast.KindShorthandPropertyAssignment:
		node = tx.visitShorthandAssignmentProperty(node.AsShorthandPropertyAssignment())

	// AssignmentRestProperty
	case ast.KindSpreadAssignment:
		node = tx.visitAssignmentRestProperty(node.AsSpreadAssignment())

	// AssignmentRestElement
	case ast.KindSpreadElement:
		node = tx.visitAssignmentRestElement(node.AsSpreadElement())

	// AssignmentElement
	default:
		if ast.IsExpression(node) {
			node = tx.visitAssignmentElement(node)
			break
		}

		node = tx.visitNoStack(node, false /*resultIsDiscarded*/)
	}
	return node
}

func (tx *CommonJSModuleTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node {
	if node.IsDeclarationFile ||
		!(ast.IsEffectiveExternalModule(node, tx.compilerOptions) ||
			node.SubtreeFacts()&ast.SubtreeContainsDynamicImport != 0) {
		return node.AsNode()
	}

	tx.currentSourceFile = node
	tx.currentModuleInfo = collectExternalModuleInfo(node, tx.compilerOptions, tx.EmitContext(), tx.resolver)
	updated := tx.transformCommonJSModule(node)
	tx.currentSourceFile = nil
	tx.currentModuleInfo = nil
	return updated
}

func (tx *CommonJSModuleTransformer) shouldEmitUnderscoreUnderscoreESModule() bool {
	if tspath.FileExtensionIsOneOf(tx.currentSourceFile.FileName(), tspath.SupportedJSExtensionsFlat) &&
		tx.currentSourceFile.CommonJSModuleIndicator != nil &&
		(tx.currentSourceFile.ExternalModuleIndicator == nil || tx.currentSourceFile.ExternalModuleIndicator.Kind == ast.KindSourceFile) {
		return false
	}
	if tx.currentModuleInfo.exportEquals == nil && ast.IsExternalModule(tx.currentSourceFile) {
		return true
	}
	return false
}

func (tx *CommonJSModuleTransformer) createUnderscoreUnderscoreESModule() *ast.Statement {
	statement := tx.Factory().NewExpressionStatement(
		tx.Factory().NewCallExpression(
			tx.Factory().NewPropertyAccessExpression(
				tx.Factory().NewIdentifier("Object"),
				nil, /*questionDotToken*/
				tx.Factory().NewIdentifier("defineProperty"),
				ast.NodeFlagsNone,
			),
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Factory().NewNodeList([]*ast.Node{
				tx.Factory().NewIdentifier("exports"),
				tx.Factory().NewStringLiteral("__esModule", ast.TokenFlagsNone),
				tx.Factory().NewObjectLiteralExpression(
					tx.Factory().NewNodeList([]*ast.Node{
						tx.Factory().NewPropertyAssignment(
							nil, /*modifiers*/
							tx.Factory().NewIdentifier("value"),
							nil, /*postfixToken*/
							nil, /*typeNode*/
							tx.Factory().NewTrueExpression(),
						),
					}),
					false, /*multiLine*/
				),
			}),
			ast.NodeFlagsNone,
		),
	)
	tx.EmitContext().SetEmitFlags(statement, printer.EFCustomPrologue)
	return statement
}

func (tx *CommonJSModuleTransformer) transformCommonJSModule(node *ast.SourceFile) *ast.Node {
	tx.EmitContext().StartVariableEnvironment()

	// emit standard prologue directives (e.g. "use strict")
	prologue, rest := tx.Factory().SplitStandardPrologue(node.Statements.Nodes)
	statements := slices.Clone(prologue)

	// emit custom prologues from other transformations
	custom, rest := tx.Factory().SplitCustomPrologue(rest)
	statements = append(statements, core.FirstResult(tx.topLevelVisitor.VisitSlice(custom))...)

	// emits `Object.defineProperty(exports, "__esModule", { value: true });` at the top of the file
	if tx.shouldEmitUnderscoreUnderscoreESModule() {
		statements = append(statements, tx.createUnderscoreUnderscoreESModule())
	}

	// initialize all exports to `undefined`, e.g.:
	//  exports.a = exports.b = void 0;
	if len(tx.currentModuleInfo.exportedNames) > 0 {
		const chunkSize = 50
		l := len(tx.currentModuleInfo.exportedNames)
		for i := 0; i < l; i += chunkSize {
			right := tx.Factory().NewVoidZeroExpression()
			for _, nextId := range tx.currentModuleInfo.exportedNames[i:min(i+chunkSize, l)] {
				var left *ast.Expression
				if nextId.Kind == ast.KindStringLiteral {
					left = tx.Factory().NewElementAccessExpression(
						tx.Factory().NewIdentifier("exports"),
						nil, /*questionDotToken*/
						tx.Factory().NewStringLiteralFromNode(nextId),
						ast.NodeFlagsNone,
					)
				} else {
					name := nextId.Clone(tx.Factory())
					tx.EmitContext().SetEmitFlags(name, printer.EFNoSourceMap|printer.EFNoComments)
					left = tx.Factory().NewPropertyAccessExpression(
						tx.Factory().NewIdentifier("exports"),
						nil, /*questionDotToken*/
						name,
						ast.NodeFlagsNone,
					)
				}
				right = tx.Factory().NewAssignmentExpression(left, right)
			}
			statement := tx.Factory().NewExpressionStatement(right)
			tx.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue)
			statements = append(statements, statement)
		}
	}

	// initialize exports for function declarations, e.g.:
	//  exports.f = f;
	//  function f() {}
	// These are marked as custom prologue so they are ordered before the external helpers
	// import declaration (e.g., `const tslib_1 = require("tslib")`), matching TypeScript's emit order.
	exportedFunctionsStart := len(statements)
	for f := range tx.currentModuleInfo.exportedFunctions.Values() {
		statements = tx.appendExportsOfClassOrFunctionDeclaration(statements, f.AsNode())
	}
	for _, s := range statements[exportedFunctionsStart:] {
		tx.EmitContext().AddEmitFlags(s, printer.EFCustomPrologue)
	}

	// visit the remaining statements in the source file
	rest, _ = tx.topLevelVisitor.VisitSlice(rest)
	statements = append(statements, rest...)

	// emit `module.exports = ...` if needd
	statements = tx.appendExportEqualsIfNeeded(statements)

	// merge temp variables into the statement list
	statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements)

	statementList := tx.Factory().NewNodeList(statements)
	statementList.Loc = node.Statements.Loc
	result := tx.Factory().UpdateSourceFile(node, statementList, node.EndOfFileToken).AsSourceFile()
	tx.EmitContext().AddEmitHelper(result.AsNode(), tx.EmitContext().ReadEmitHelpers()...)

	externalHelpersImportDeclaration := createExternalHelpersImportDeclarationIfNeeded(tx.EmitContext(), result, tx.compilerOptions, tx.getEmitModuleFormatOfFile(node), false /*hasExportStarsToExportValues*/, false /*hasImportStar*/, false /*hasImportDefault*/)
	if externalHelpersImportDeclaration != nil {
		prologue, rest := tx.Factory().SplitStandardPrologue(result.Statements.Nodes)
		custom, rest := tx.Factory().SplitCustomPrologue(rest)
		statements := slices.Clone(prologue)
		statements = append(statements, custom...)
		statements = append(statements, tx.topLevelVisitor.VisitNode(externalHelpersImportDeclaration))
		statements = append(statements, rest...)
		statementList := tx.Factory().NewNodeList(statements)
		statementList.Loc = result.Statements.Loc
		result = tx.Factory().UpdateSourceFile(result, statementList, node.EndOfFileToken).AsSourceFile()
	}

	return result.AsNode()
}

// Adds the down-level representation of `export=` to the statement list if one exists in the source file.
//
// - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
func (tx *CommonJSModuleTransformer) appendExportEqualsIfNeeded(statements []*ast.Statement) []*ast.Statement {
	if tx.currentModuleInfo.exportEquals != nil {
		expressionResult := tx.visitExportEquals(tx.currentModuleInfo.exportEquals)
		if expressionResult != nil {
			statement := tx.Factory().NewExpressionStatement(
				tx.Factory().NewAssignmentExpression(
					tx.Factory().NewPropertyAccessExpression(
						tx.Factory().NewIdentifier("module"),
						nil, /*questionDotToken*/
						tx.Factory().NewIdentifier("exports"),
						ast.NodeFlagsNone,
					),
					expressionResult,
				),
			)

			tx.EmitContext().AssignCommentAndSourceMapRanges(statement, tx.currentModuleInfo.exportEquals.AsNode())
			tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments)
			statements = append(statements, statement)
		}
	}
	return statements
}

func (tx *CommonJSModuleTransformer) visitExportEquals(node *ast.ExportAssignment) *ast.Expression {
	grandparentNode := tx.pushNode(node.AsNode())
	defer tx.popNode(grandparentNode)
	return tx.Visitor().VisitNode(node.Expression)
}

// Appends the exports of an ImportDeclaration to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `decl` parameter is the declaration whose exports are to be recorded.
func (tx *CommonJSModuleTransformer) appendExportsOfImportDeclaration(statements []*ast.Statement, decl *ast.ImportDeclaration) []*ast.Statement {
	if tx.currentModuleInfo.exportEquals != nil {
		return statements
	}

	importClause := decl.ImportClause
	if importClause == nil {
		return statements
	}

	seen := &collections.Set[string]{}
	if importClause.Name() != nil {
		statements = tx.appendExportsOfDeclaration(statements, importClause, seen, false /*liveBinding*/)
	}

	namedBindings := importClause.AsImportClause().NamedBindings
	if namedBindings != nil {
		switch namedBindings.Kind {
		case ast.KindNamespaceImport:
			statements = tx.appendExportsOfDeclaration(statements, namedBindings, seen, false /*liveBinding*/)

		case ast.KindNamedImports:
			for _, importBinding := range namedBindings.Elements() {
				statements = tx.appendExportsOfDeclaration(statements, importBinding, seen, true /*liveBinding*/)
			}
		}
	}

	return statements
}

// Appends the exports of a VariableStatement to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `node` parameter is the VariableStatement whose exports are to be recorded.
func (tx *CommonJSModuleTransformer) appendExportsOfVariableStatement(statements []*ast.Statement, node *ast.VariableStatement) []*ast.Statement {
	return tx.appendExportsOfVariableDeclarationList(statements, node.DeclarationList.AsVariableDeclarationList() /*isForInOrOfInitializer*/, false)
}

// Appends the exports of a VariableDeclarationList to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `node` parameter is the VariableDeclarationList whose exports are to be recorded.
func (tx *CommonJSModuleTransformer) appendExportsOfVariableDeclarationList(statements []*ast.Statement, node *ast.VariableDeclarationList, isForInOrOfInitializer bool) []*ast.Statement {
	if tx.currentModuleInfo.exportEquals != nil {
		return statements
	}

	for _, decl := range node.Declarations.Nodes {
		statements = tx.appendExportsOfBindingElement(statements, decl, isForInOrOfInitializer)
	}

	return statements
}

// Appends the exports of a VariableDeclaration or BindingElement to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `decl` parameter is the declaration whose exports are to be recorded.
func (tx *CommonJSModuleTransformer) appendExportsOfBindingElement(statements []*ast.Statement, decl *ast.Node /*VariableDeclaration | BindingElement*/, isForInOrOfInitializer bool) []*ast.Statement {
	if tx.currentModuleInfo.exportEquals != nil || decl.Name() == nil {
		return statements
	}

	if ast.IsBindingPattern(decl.Name()) {
		for _, element := range decl.Name().Elements() {
			if !ast.IsOmittedExpression(element) {
				statements = tx.appendExportsOfBindingElement(statements, element, isForInOrOfInitializer)
			}
		}
	} else if !transformers.IsGeneratedIdentifier(tx.EmitContext(), decl.Name()) &&
		(!ast.IsVariableDeclaration(decl) || decl.Initializer() != nil || isForInOrOfInitializer) {
		statements = tx.appendExportsOfDeclaration(statements, decl, nil /*seen*/, false /*liveBinding*/)
	}

	return statements
}

// Appends the exports of a ClassDeclaration or FunctionDeclaration to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `decl` parameter is the declaration whose exports are to be recorded.
func (tx *CommonJSModuleTransformer) appendExportsOfClassOrFunctionDeclaration(statements []*ast.Statement, decl *ast.Declaration) []*ast.Statement {
	if tx.currentModuleInfo.exportEquals != nil {
		return statements
	}

	seen := &collections.Set[string]{}
	if ast.HasSyntacticModifier(decl, ast.ModifierFlagsExport) {
		var exportName *ast.IdentifierNode
		if ast.HasSyntacticModifier(decl, ast.ModifierFlagsDefault) {
			exportName = tx.Factory().NewIdentifier("default")
		} else {
			exportName = tx.Factory().GetDeclarationName(decl)
		}

		exportValue := tx.Factory().GetLocalName(decl)
		statements = tx.appendExportStatement(statements, seen, exportName, exportValue, &decl.Loc, false /*allowComments*/, false /*liveBinding*/)
	}

	if decl.Name() != nil {
		return tx.appendExportsOfDeclaration(statements, decl, seen, false /*liveBinding*/)
	}

	return statements
}

// Appends the exports of a declaration to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `decl` parameter is the declaration to export.
func (tx *CommonJSModuleTransformer) appendExportsOfDeclaration(statements []*ast.Statement, decl *ast.Declaration, seen *collections.Set[string], liveBinding bool) []*ast.Statement {
	if tx.currentModuleInfo.exportEquals != nil {
		return statements
	}

	if seen == nil {
		seen = &collections.Set[string]{}
	}

	if name := decl.Name(); tx.currentModuleInfo.exportSpecifiers.Len() > 0 && name != nil && ast.IsIdentifier(name) {
		name = tx.Factory().GetDeclarationName(decl)
		exportSpecifiers := tx.currentModuleInfo.exportSpecifiers.Get(name.Text())
		if len(exportSpecifiers) > 0 {
			exportValue := tx.visitExpressionIdentifier(name)
			for _, exportSpecifier := range exportSpecifiers {
				statements = tx.appendExportStatement(statements, seen, exportSpecifier.Name(), exportValue, &exportSpecifier.Name().Loc /*location*/, false /*allowComments*/, liveBinding)
			}
		}
	}

	return statements
}

// Appends the down-level representation of an export to a statement list, returning the statement list.
//
//   - The `statements` parameter is a statement list to which the down-level export statements are to be appended.
//   - The `exportName` parameter is the name of the export.
//   - The `expression` parameter is the expression to export.
//   - The `location` parameter is the location to use for source maps and comments for the export.
//   - The `allowComments` parameter indicates whether to allow comments on the export.
func (tx *CommonJSModuleTransformer) appendExportStatement(statements []*ast.Statement, seen *collections.Set[string], exportName *ast.ModuleExportName, expression *ast.Expression, location *core.TextRange, allowComments bool, liveBinding bool) []*ast.Statement {
	if exportName.Kind != ast.KindStringLiteral {
		if seen.Has(exportName.Text()) {
			return statements
		}
		seen.Add(exportName.Text())
	}
	statements = append(statements, tx.createExportStatement(exportName, expression, location, allowComments, liveBinding))
	return statements
}

// Creates a call to the current file's export function to export a value.
//
//   - The `name` parameter is the bound name of the export.
//   - The `value` parameter is the exported value.
//   - The `location` parameter is the location to use for source maps and comments for the export.
//   - The `allowComments` parameter indicates whether to emit comments for the statement.
func (tx *CommonJSModuleTransformer) createExportStatement(name *ast.ModuleExportName, value *ast.Expression, location *core.TextRange, allowComments bool, liveBinding bool) *ast.Statement {
	statement := tx.Factory().NewExpressionStatement(tx.createExportExpression(name, value, nil /*location*/, liveBinding))
	if location != nil {
		tx.EmitContext().SetCommentRange(statement, *location)
	}
	tx.EmitContext().AddEmitFlags(statement, printer.EFStartOnNewLine)
	if !allowComments {
		tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments)
	}
	return statement
}

// Creates a call to the current file's export function to export a value.
//
//   - The `name` parameter is the bound name of the export.
//   - The `value` parameter is the exported value.
//   - The `location` parameter is the location to use for source maps and comments for the export.
func (tx *CommonJSModuleTransformer) createExportExpression(name *ast.ModuleExportName, value *ast.Expression, location *core.TextRange, liveBinding bool) *ast.Expression {
	var expression *ast.Expression
	if liveBinding {
		// For a live binding we emit a getter on `exports` that returns the value:
		//  Object.defineProperty(exports, "<name>", { enumerable: true, get: function () { return <value>; } });
		expression = tx.Factory().NewCallExpression(
			tx.Factory().NewPropertyAccessExpression(
				tx.Factory().NewIdentifier("Object"),
				nil, /*questionDotToken*/
				tx.Factory().NewIdentifier("defineProperty"),
				ast.NodeFlagsNone,
			),
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Factory().NewNodeList([]*ast.Node{
				tx.Factory().NewIdentifier("exports"),
				tx.Factory().NewStringLiteralFromNode(name),
				tx.Factory().NewObjectLiteralExpression(
					tx.Factory().NewNodeList([]*ast.Node{
						tx.Factory().NewPropertyAssignment(
							nil, /*modifiers*/
							tx.Factory().NewIdentifier("enumerable"),
							nil, /*postfixToken*/
							nil, /*typeNode*/
							tx.Factory().NewTrueExpression(),
						),
						tx.Factory().NewPropertyAssignment(
							nil, /*modifiers*/
							tx.Factory().NewIdentifier("get"),
							nil, /*postfixToken*/
							nil, /*typeNode*/
							tx.Factory().NewFunctionExpression(
								nil, /*modifiers*/
								nil, /*asteriskToken*/
								nil, /*name*/
								nil, /*typeParameters*/
								tx.Factory().NewNodeList([]*ast.Node{}),
								nil, /*type*/
								nil, /*fullSignature*/
								tx.Factory().NewBlock(
									tx.Factory().NewNodeList([]*ast.Node{
										tx.Factory().NewReturnStatement(value),
									}),
									false, /*multiLine*/
								),
							),
						),
					}),
					false, /*multiLine*/
				),
			}),
			ast.NodeFlagsNone,
		)
	} else {
		// Otherwise, we emit a simple property assignment.
		var left *ast.Expression
		if name.Kind == ast.KindStringLiteral {
			// emits:
			//  exports["<name>"] = <value>;
			left = tx.Factory().NewElementAccessExpression(
				tx.Factory().NewIdentifier("exports"),
				nil, /*questionDotToken*/
				tx.Factory().NewStringLiteralFromNode(name),
				ast.NodeFlagsNone,
			)
		} else {
			// emits:
			//  exports.<name> = <value>;
			left = tx.Factory().NewPropertyAccessExpression(
				tx.Factory().NewIdentifier("exports"),
				nil, /*questionDotToken*/
				name.Clone(tx.Factory()),
				ast.NodeFlagsNone,
			)
		}
		expression = tx.Factory().NewAssignmentExpression(left, value)
	}
	if location != nil {
		tx.EmitContext().SetCommentRange(expression, *location)
	}
	return expression
}

// Creates a `require()` call to import an external module.
func (tx *CommonJSModuleTransformer) createRequireCall(node *ast.Node /*ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration*/) *ast.Node {
	var args []*ast.Expression
	moduleName := getExternalModuleNameLiteral(tx.Factory(), node, tx.currentSourceFile, nil /*host*/, nil /*resolver*/, tx.compilerOptions)
	if moduleName != nil {
		args = append(args, rewriteModuleSpecifier(tx.EmitContext(), moduleName, tx.compilerOptions))
	}
	return tx.Factory().NewCallExpression(
		tx.Factory().NewIdentifier("require"),
		nil, /*questionDotToken*/
		nil, /*typeArguments*/
		tx.Factory().NewNodeList(args),
		ast.NodeFlagsNone,
	)
}

func (tx *CommonJSModuleTransformer) getHelperExpressionForExport(node *ast.ExportDeclaration, innerExpr *ast.Expression) *ast.Expression {
	if getExportNeedsImportStarHelper(node) {
		return tx.Visitor().VisitNode(tx.Factory().NewImportStarHelper(innerExpr))
	}
	return innerExpr
}

func (tx *CommonJSModuleTransformer) getHelperExpressionForImport(node *ast.ImportDeclaration, innerExpr *ast.Expression) *ast.Expression {
	if getImportNeedsImportStarHelper(node) {
		return tx.Visitor().VisitNode(tx.Factory().NewImportStarHelper(innerExpr))
	}
	if getImportNeedsImportDefaultHelper(node) {
		return tx.Visitor().VisitNode(tx.Factory().NewImportDefaultHelper(innerExpr))
	}
	return innerExpr
}

func (tx *CommonJSModuleTransformer) visitTopLevelImportDeclaration(node *ast.ImportDeclaration) *ast.Node {
	if node.ImportClause == nil {
		// import "mod";
		statement := tx.Factory().NewExpressionStatement(tx.createRequireCall(node.AsNode()))
		tx.EmitContext().SetOriginal(statement, node.AsNode())
		tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
		return statement
	}

	var statements []*ast.Statement
	var variables []*ast.VariableDeclarationNode
	namespaceDeclaration := ast.GetNamespaceDeclarationNode(node.AsNode())
	if namespaceDeclaration != nil && !ast.IsDefaultImport(node.AsNode()) {
		// import * as n from "mod";
		variables = append(
			variables,
			tx.Factory().NewVariableDeclaration(
				namespaceDeclaration.Name().Clone(tx.Factory()),
				nil, /*exclamationToken*/
				nil, /*type*/
				tx.getHelperExpressionForImport(node, tx.createRequireCall(node.AsNode())),
			),
		)
	} else {
		// import d from "mod";
		// import { x, y } from "mod";
		// import d, { x, y } from "mod";
		// import d, * as n from "mod";
		variables = append(
			variables,
			tx.Factory().NewVariableDeclaration(
				tx.Factory().NewGeneratedNameForNode(node.AsNode()),
				nil, /*exclamationToken*/
				nil, /*type*/
				tx.getHelperExpressionForImport(node, tx.createRequireCall(node.AsNode())),
			),
		)

		if namespaceDeclaration != nil && ast.IsDefaultImport(node.AsNode()) {
			variables = append(
				variables,
				tx.Factory().NewVariableDeclaration(
					namespaceDeclaration.Name().Clone(tx.Factory()),
					nil, /*exclamationToken*/
					nil, /*type*/
					tx.Factory().NewGeneratedNameForNode(node.AsNode()),
				),
			)
		}
	}

	varStatement := tx.Factory().NewVariableStatement(
		nil, /*modifiers*/
		tx.Factory().NewVariableDeclarationList(
			tx.Factory().NewNodeList(variables),
			ast.NodeFlagsConst,
		),
	)

	tx.EmitContext().SetOriginal(varStatement, node.AsNode())
	tx.EmitContext().AssignCommentAndSourceMapRanges(varStatement, node.AsNode())
	statements = append(statements, varStatement)
	statements = tx.appendExportsOfImportDeclaration(statements, node)
	return transformers.SingleOrMany(statements, tx.Factory())
}

func (tx *CommonJSModuleTransformer) visitTopLevelImportEqualsDeclaration(node *ast.ImportEqualsDeclaration) *ast.Node {
	if !ast.IsExternalModuleImportEqualsDeclaration(node.AsNode()) {
		// import m = n;
		panic("import= for internal module references should be handled in an earlier transformer.")
	}

	var statements []*ast.Statement
	if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) {
		// export import m = require("mod");
		statement := tx.Factory().NewExpressionStatement(
			tx.createExportExpression(
				node.Name(),
				tx.createRequireCall(node.AsNode()),
				&node.Loc,
				false, /*liveBinding*/
			),
		)

		tx.EmitContext().SetOriginal(statement, node.AsNode())
		tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
		statements = append(statements, statement)
	} else {
		// import m = require("mod");
		statement := tx.Factory().NewVariableStatement(
			nil, /*modifiers*/
			tx.Factory().NewVariableDeclarationList(
				tx.Factory().NewNodeList([]*ast.VariableDeclarationNode{
					tx.Factory().NewVariableDeclaration(
						node.Name().Clone(tx.Factory()),
						nil, /*exclamationToken*/
						nil, /*typeNode*/
						tx.createRequireCall(node.AsNode()),
					),
				}),
				ast.NodeFlagsConst,
			),
		)
		tx.EmitContext().SetOriginal(statement, node.AsNode())
		tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
		statements = append(statements, statement)
	}

	statements = tx.appendExportsOfDeclaration(statements, node.AsNode(), nil /*seen*/, false /*liveBinding*/)
	return transformers.SingleOrMany(statements, tx.Factory())
}

func (tx *CommonJSModuleTransformer) visitTopLevelExportDeclaration(node *ast.ExportDeclaration) *ast.Node {
	if node.ModuleSpecifier == nil {
		// Elide export declarations with no module specifier as they are handled
		// elsewhere.
		return nil
	}

	generatedName := tx.Factory().NewGeneratedNameForNode(node.AsNode())
	if node.ExportClause != nil && ast.IsNamedExports(node.ExportClause) {
		// export { x, y } from "mod";
		var statements []*ast.Statement
		varStatement := tx.Factory().NewVariableStatement(
			nil, /*modifiers*/
			tx.Factory().NewVariableDeclarationList(
				tx.Factory().NewNodeList([]*ast.VariableDeclarationNode{
					tx.Factory().NewVariableDeclaration(
						generatedName,
						nil, /*exclamationToken*/
						nil, /*type*/
						tx.createRequireCall(node.AsNode()),
					),
				}),
				ast.NodeFlagsNone,
			),
		)
		tx.EmitContext().SetOriginal(varStatement, node.AsNode())
		tx.EmitContext().AssignCommentAndSourceMapRanges(varStatement, node.AsNode())
		statements = append(statements, varStatement)

		for _, specifier := range node.ExportClause.Elements() {
			specifierName := specifier.PropertyNameOrName()
			exportNeedsImportDefault := ast.ModuleExportNameIsDefault(specifierName)

			var target *ast.Node
			if exportNeedsImportDefault {
				target = tx.Factory().NewImportDefaultHelper(generatedName)
			} else {
				target = generatedName
			}

			var exportName *ast.Node
			if ast.IsStringLiteral(specifier.Name()) {
				exportName = tx.Factory().NewStringLiteralFromNode(specifier.Name())
			} else {
				exportName = tx.Factory().GetExportName(specifier.AsNode())
			}

			var exportedValue *ast.Node
			if ast.IsStringLiteral(specifierName) {
				exportedValue = tx.Factory().NewElementAccessExpression(target, nil /*questionDotToken*/, specifierName, ast.NodeFlagsNone)
			} else {
				exportedValue = tx.Factory().NewPropertyAccessExpression(target, nil /*questionDotToken*/, specifierName, ast.NodeFlagsNone)
			}
			statement := tx.Factory().NewExpressionStatement(
				tx.createExportExpression(
					exportName,
					exportedValue,
					nil,  /*location*/
					true, /*liveBinding*/
				),
			)
			tx.EmitContext().SetOriginal(statement, specifier.AsNode())
			tx.EmitContext().AssignCommentAndSourceMapRanges(statement, specifier.AsNode())
			statements = append(statements, statement)
		}

		return transformers.SingleOrMany(statements, tx.Factory())
	}

	if node.ExportClause != nil {
		// export * as ns from "mod";
		// export * as default from "mod";
		var exportName *ast.Node
		if ast.IsStringLiteral(node.ExportClause.Name()) {
			exportName = tx.Factory().NewStringLiteralFromNode(node.ExportClause.Name())
		} else {
			exportName = node.ExportClause.Name().Clone(tx.Factory())
		}
		statement := tx.Factory().NewExpressionStatement(
			tx.createExportExpression(
				exportName,
				tx.getHelperExpressionForExport(
					node,
					tx.createRequireCall(node.AsNode()),
				),
				nil,   /*location*/
				false, /*liveBinding*/
			),
		)
		tx.EmitContext().SetOriginal(statement, node.AsNode())
		tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
		return statement
	}

	// export * from "mod";
	statement := tx.Factory().NewExpressionStatement(
		tx.Visitor().VisitNode(tx.Factory().NewExportStarHelper(tx.createRequireCall(node.AsNode()), tx.Factory().NewIdentifier("exports"))),
	)
	tx.EmitContext().SetOriginal(statement, node.AsNode())
	tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
	return statement
}

func (tx *CommonJSModuleTransformer) visitTopLevelExportAssignment(node *ast.ExportAssignment) *ast.Node {
	if node.IsExportEquals {
		return nil
	}

	return tx.createExportStatement(
		tx.Factory().NewIdentifier("default"),
		tx.Visitor().VisitNode(node.Expression),
		&node.Loc, /*location*/
		true,      /*allowComments*/
		false,     /*liveBinding*/
	)
}

func (tx *CommonJSModuleTransformer) visitTopLevelFunctionDeclaration(node *ast.FunctionDeclaration) *ast.Node {
	if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) {
		return tx.Factory().UpdateFunctionDeclaration(
			node,
			transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^ast.ModifierFlagsExportDefault),
			node.AsteriskToken,
			tx.Factory().GetDeclarationName(node.AsNode()),
			nil, /*typeParameters*/
			tx.Visitor().VisitNodes(node.Parameters),
			nil, /*type*/
			nil, /*fullSignature*/
			tx.Visitor().VisitNode(node.Body),
		)
	} else {
		return tx.Visitor().VisitEachChild(node.AsNode())
	}
}

func (tx *CommonJSModuleTransformer) visitTopLevelClassDeclaration(node *ast.ClassDeclaration) *ast.Node {
	var statements []*ast.Statement
	if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) {
		statements = append(statements, tx.Factory().UpdateClassDeclaration(
			node,
			tx.Visitor().VisitModifiers(transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^ast.ModifierFlagsExportDefault)),
			tx.Factory().GetDeclarationName(node.AsNode()),
			nil, /*typeParameters*/
			tx.Visitor().VisitNodes(node.HeritageClauses),
			tx.Visitor().VisitNodes(node.Members),
		))
	} else {
		statements = append(statements, tx.Visitor().VisitEachChild(node.AsNode()))
	}
	statements = tx.appendExportsOfClassOrFunctionDeclaration(statements, node.AsNode())
	return transformers.SingleOrMany(statements, tx.Factory())
}

func (tx *CommonJSModuleTransformer) visitTopLevelVariableStatement(node *ast.VariableStatement) *ast.Node {
	var statements []*ast.Statement
	if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) {
		// export var a = b;
		var variables []*ast.VariableDeclarationNode
		var expressions []*ast.Expression
		var modifiers *ast.ModifierList

		commitPendingVariables := func() {
			if len(variables) > 0 {
				variableList := tx.Factory().NewNodeList(variables)
				statement := tx.Factory().UpdateVariableStatement(
					node,
					modifiers,
					tx.Factory().UpdateVariableDeclarationList(
						node.DeclarationList.AsVariableDeclarationList(),
						variableList,
						node.DeclarationList.Flags,
					),
				)
				if len(statements) > 0 {
					tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments)
				}
				statements = append(statements, statement)
				variables = nil
			}
		}

		commitPendingExpressions := func() {
			if len(expressions) > 0 {
				statement := tx.Factory().NewExpressionStatement(tx.Factory().InlineExpressions(expressions))
				tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
				if len(statements) > 0 {
					tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments)
				}
				statements = append(statements, statement)
				expressions = nil
			}
		}

		pushVariable := func(variable *ast.VariableDeclarationNode) {
			commitPendingExpressions()
			variables = append(variables, variable)
		}

		pushExpression := func(expression *ast.Expression) {
			commitPendingVariables()
			expressions = append(expressions, expression)
		}

		// If we're exporting these variables, then these just become assignments to 'exports.x'.
		for _, variable := range node.DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
			v := variable.AsVariableDeclaration()

			if ast.IsIdentifier(v.Name()) && transformers.IsLocalName(tx.EmitContext(), v.Name()) {
				// A "local name" generally means a variable declaration that *shouldn't* be
				// converted to `exports.x = ...`, even if the declaration is exported. This
				// usually indicates a class or function declaration that was converted into
				// a variable declaration, as most references to the declaration will remain
				// untransformed (i.e., `new C` rather than `new exports.C`). In these cases,
				// an `export { x }` declaration will follow.

				if modifiers == nil {
					modifiers = transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^ast.ModifierFlagsExportDefault)
				}

				if v.Initializer != nil {
					variable = tx.Factory().UpdateVariableDeclaration(
						v,
						v.Name(),
						nil, /*exclamationToken*/
						nil, /*type*/
						tx.createExportExpression(
							v.Name(),
							tx.Visitor().VisitNode(v.Initializer),
							nil,
							false, /*liveBinding*/
						),
					)
				}

				pushVariable(variable)
			} else if v.Initializer != nil && !ast.IsBindingPattern(v.Name()) && (ast.IsArrowFunction(v.Initializer) || ast.IsFunctionExpression(v.Initializer) || ast.IsClassExpression(v.Initializer)) {
				// preserve variable declarations for functions and classes to assign names

				pushVariable(tx.Factory().NewVariableDeclaration(
					v.Name(),
					v.ExclamationToken,
					v.Type,
					tx.Visitor().VisitNode(v.Initializer),
				))

				propertyAccess := tx.Factory().NewPropertyAccessExpression(
					tx.Factory().NewIdentifier("exports"),
					nil, /*questionDotToken*/
					v.Name(),
					ast.NodeFlagsNone,
				)
				tx.EmitContext().AssignCommentAndSourceMapRanges(propertyAccess, v.Name())

				pushExpression(tx.Factory().NewAssignmentExpression(
					propertyAccess,
					v.Name().Clone(tx.Factory()),
				))
			} else if ast.IsIdentifier(v.Name()) {
				expression := tx.transformInitializedVariable(v)
				if expression != nil {
					pushExpression(tx.Visitor().VisitNode(expression))
				}
			} else if ast.IsBindingPattern(v.Name()) {
				// For binding patterns with export modifier, use flattenDestructuringAssignment
				// to decompose into individual export assignments
				expression := tx.transformInitializedVariable(v)
				if expression != nil {
					pushExpression(expression)
				}
			} else {
				// For binding patterns, we can't do exports.{pattern} = value
				// Just emit the assignment and let appendExportsOfVariableStatement handle the exports
				expression := transformers.ConvertVariableDeclarationToAssignmentExpression(tx.EmitContext(), v)
				if expression != nil {
					pushExpression(tx.Visitor().VisitNode(expression))
				}
			}
		}

		commitPendingVariables()
		commitPendingExpressions()
		statements = tx.appendExportsOfVariableStatement(statements, node)
		return transformers.SingleOrMany(statements, tx.Factory())
	}
	return tx.visitTopLevelNestedVariableStatement(node)
}

func (tx *CommonJSModuleTransformer) transformInitializedVariable(node *ast.VariableDeclaration) *ast.Expression {
	if node.Initializer == nil {
		return nil
	}
	name := node.Name()
	if ast.IsBindingPattern(name) {
		// Convert the binding pattern into an equivalent assignment expression and visit it
		// as a destructuring assignment. This preserves native destructuring (and therefore
		// iterator semantics for array patterns) whenever each leaf identifier can be
		// substituted to an export reference. Only when the destructuring would assign to
		// re-aliased or multi-exported names (where native destructuring cannot update all
		// targets) does `visitDestructuringAssignment` fall back to flattening.
		assignment := transformers.ConvertVariableDeclarationToAssignmentExpression(tx.EmitContext(), node)
		grandparentNode := tx.pushNode(assignment)
		defer tx.popNode(grandparentNode)
		return tx.visitDestructuringAssignment(assignment.AsBinaryExpression(), true /*valueIsDiscarded*/)
	}
	propertyAccess := tx.Factory().NewPropertyAccessExpression(
		tx.Factory().NewIdentifier("exports"),
		nil, /*questionDotToken*/
		name,
		ast.NodeFlagsNone,
	)
	tx.EmitContext().AssignCommentAndSourceMapRanges(propertyAccess, name)
	return tx.Factory().NewAssignmentExpression(propertyAccess, node.Initializer)
}

// Visits a top-level nested variable statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedVariableStatement(node *ast.VariableStatement) *ast.Node {
	var statements []*ast.Statement
	statements = append(statements, tx.Visitor().VisitEachChild(node.AsNode()))
	statements = tx.appendExportsOfVariableStatement(statements, node)
	return transformers.SingleOrMany(statements, tx.Factory())
}

// Visits a top-level nested `for` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedForStatement(node *ast.ForStatement) *ast.Node {
	if node.Initializer != nil && ast.IsVariableDeclarationList(node.Initializer) && node.Initializer.Flags&ast.NodeFlagsBlockScoped == 0 {
		exportStatements := tx.appendExportsOfVariableDeclarationList(nil /*statements*/, node.Initializer.AsVariableDeclarationList(), false /*isForInOrOfInitializer*/)
		if len(exportStatements) > 0 {
			// given:
			//   export { x }
			//   for (var x = 0; ;) { }
			// emits:
			//   var x = 0;
			//   exports.x = x;
			//   for (; ;) { }

			var statements []*ast.Statement
			varDeclList := tx.discardedValueVisitor.VisitNode(node.Initializer)
			varStatement := tx.Factory().NewVariableStatement(nil /*modifiers*/, varDeclList)
			statements = append(statements, varStatement)
			statements = append(statements, exportStatements...)

			condition := tx.Visitor().VisitNode(node.Condition)
			incrementor := tx.discardedValueVisitor.VisitNode(node.Incrementor)
			body := tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor)
			statements = append(statements, tx.Factory().UpdateForStatement(
				node,
				nil, /*initializer*/
				condition,
				incrementor,
				body,
			))
			return transformers.SingleOrMany(statements, tx.Factory())
		}
	}
	return tx.Factory().UpdateForStatement(
		node,
		tx.discardedValueVisitor.VisitNode(node.Initializer),
		tx.Visitor().VisitNode(node.Condition),
		tx.discardedValueVisitor.VisitNode(node.Incrementor),
		tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor),
	)
}

// Visits a top-level nested `for..in` or `for..of` statement as it may contain `var` declarations that are hoisted and
// may still be exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedForInOrOfStatement(node *ast.ForInOrOfStatement) *ast.Node {
	if ast.IsVariableDeclarationList(node.Initializer) && node.Initializer.Flags&ast.NodeFlagsBlockScoped == 0 {
		exportStatements := tx.appendExportsOfVariableDeclarationList(nil /*statements*/, node.Initializer.AsVariableDeclarationList(), true /*isForInOrOfInitializer*/)
		if len(exportStatements) > 0 {
			// given:
			//   export { x }
			//   for (var x in y) {
			//     ...
			//   }
			// emits:
			//   for (var x in y) {
			//     exports.x = x;
			//     ...
			//   }

			initializer := tx.discardedValueVisitor.VisitNode(node.Initializer)
			expression := tx.Visitor().VisitNode(node.Expression)
			body := tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor)
			if ast.IsBlock(body) {
				block := body.AsBlock()
				bodyStatements := append(exportStatements, block.Statements.Nodes...)
				bodyStatementList := tx.Factory().NewNodeList(bodyStatements)
				bodyStatementList.Loc = block.Statements.Loc
				body = tx.Factory().UpdateBlock(block, bodyStatementList, block.MultiLine)
			} else {
				bodyStatements := append(exportStatements, body)
				body = tx.Factory().NewBlock(tx.Factory().NewNodeList(bodyStatements), true /*multiLine*/)
			}
			return tx.Factory().UpdateForInOrOfStatement(node, node.AwaitModifier, initializer, expression, body)
		}
	}
	return tx.Factory().UpdateForInOrOfStatement(
		node,
		node.AwaitModifier,
		tx.discardedValueVisitor.VisitNode(node.Initializer),
		tx.Visitor().VisitNode(node.Expression),
		tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor),
	)
}

// Visits a top-level nested `do` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedDoStatement(node *ast.DoStatement) *ast.Node {
	return tx.Factory().UpdateDoStatement(
		node,
		tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor),
		tx.Visitor().VisitNode(node.Expression),
	)
}

// Visits a top-level nested `while` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedWhileStatement(node *ast.WhileStatement) *ast.Node {
	return tx.Factory().UpdateWhileStatement(
		node,
		tx.Visitor().VisitNode(node.Expression),
		tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor),
	)
}

// Visits a top-level nested labeled statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedLabeledStatement(node *ast.LabeledStatement) *ast.Node {
	statement := tx.topLevelNestedVisitor.VisitEmbeddedStatement(node.Statement)
	if statement == nil {
		statement = tx.Factory().NewEmptyStatement()
	}
	return tx.Factory().UpdateLabeledStatement(node, node.Label, statement)
}

// Visits a top-level nested `with` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedWithStatement(node *ast.WithStatement) *ast.Node {
	return tx.Factory().UpdateWithStatement(
		node,
		tx.Visitor().VisitNode(node.Expression),
		tx.topLevelNestedVisitor.VisitEmbeddedStatement(node.Statement),
	)
}

// Visits a top-level nested `if` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedIfStatement(node *ast.IfStatement) *ast.Node {
	expression := tx.Visitor().VisitNode(node.Expression)
	thenStatement := tx.topLevelNestedVisitor.VisitEmbeddedStatement(node.ThenStatement)
	if thenStatement == nil {
		thenStatement = tx.Factory().NewBlock(tx.Factory().NewNodeList(nil), false /*multiLine*/)
	}
	elseStatement := tx.topLevelNestedVisitor.VisitEmbeddedStatement(node.ElseStatement)
	return tx.Factory().UpdateIfStatement(node, expression, thenStatement, elseStatement)
}

// Visits a top-level nested `switch` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedSwitchStatement(node *ast.SwitchStatement) *ast.Node {
	return tx.Factory().UpdateSwitchStatement(
		node,
		tx.Visitor().VisitNode(node.Expression),
		tx.topLevelNestedVisitor.VisitNode(node.CaseBlock),
	)
}

// Visits a top-level nested case block as it may contain `var` declarations that are hoisted and may still be exported
// with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedCaseBlock(node *ast.CaseBlock) *ast.Node {
	return tx.topLevelNestedVisitor.VisitEachChild(node.AsNode())
}

// Visits a top-level nested `case` or `default` clause as it may contain `var` declarations that are hoisted and may
// still be exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedCaseOrDefaultClause(node *ast.CaseOrDefaultClause) *ast.Node {
	return tx.Factory().UpdateCaseOrDefaultClause(
		node,
		tx.Visitor().VisitNode(node.Expression),
		tx.topLevelNestedVisitor.VisitNodes(node.Statements),
	)
}

// Visits a top-level nested `try` statement as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedTryStatement(node *ast.TryStatement) *ast.Node {
	return tx.topLevelNestedVisitor.VisitEachChild(node.AsNode())
}

// Visits a top-level nested `catch` clause as it may contain `var` declarations that are hoisted and may still be
// exported with `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedCatchClause(node *ast.CatchClause) *ast.Node {
	return tx.Factory().UpdateCatchClause(
		node,
		node.VariableDeclaration,
		tx.topLevelNestedVisitor.VisitNode(node.Block),
	)
}

// Visits a top-level nested block as it may contain `var` declarations that are hoisted and may still be exported with
// `export {}`.
func (tx *CommonJSModuleTransformer) visitTopLevelNestedBlock(node *ast.Block) *ast.Node {
	return tx.topLevelNestedVisitor.VisitEachChild(node.AsNode())
}

func (tx *CommonJSModuleTransformer) visitForStatement(node *ast.ForStatement) *ast.Node {
	return tx.Factory().UpdateForStatement(
		node,
		tx.discardedValueVisitor.VisitNode(node.Initializer),
		tx.Visitor().VisitNode(node.Condition),
		tx.discardedValueVisitor.VisitNode(node.Incrementor),
		tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor),
	)
}

func (tx *CommonJSModuleTransformer) visitForInOrOfStatement(node *ast.ForInOrOfStatement) *ast.Node {
	return tx.Factory().UpdateForInOrOfStatement(
		node,
		node.AwaitModifier,
		tx.discardedValueVisitor.VisitNode(node.Initializer),
		tx.Visitor().VisitNode(node.Expression),
		tx.EmitContext().VisitIterationBody(node.Statement, tx.topLevelNestedVisitor),
	)
}

// Visits an expression statement whose value will be discarded at runtime.
func (tx *CommonJSModuleTransformer) visitExpressionStatement(node *ast.ExpressionStatement) *ast.Node {
	return tx.discardedValueVisitor.VisitEachChild(node.AsNode())
}

// Visits a `void` expression whose value will be discarded at runtime.
func (tx *CommonJSModuleTransformer) visitVoidExpression(node *ast.VoidExpression) *ast.Node {
	return tx.discardedValueVisitor.VisitEachChild(node.AsNode())
}

// Visits a parenthesized expression whose value may be discarded at runtime.
func (tx *CommonJSModuleTransformer) visitParenthesizedExpression(node *ast.ParenthesizedExpression, resultIsDiscarded bool) *ast.Node {
	expression := core.IfElse(resultIsDiscarded, tx.discardedValueVisitor, tx.Visitor()).VisitNode(node.Expression)
	return tx.Factory().UpdateParenthesizedExpression(node, expression)
}

// Visits a partially emitted expression whose value may be discarded at runtime.
func (tx *CommonJSModuleTransformer) visitPartiallyEmittedExpression(node *ast.PartiallyEmittedExpression, resultIsDiscarded bool) *ast.Node {
	expression := core.IfElse(resultIsDiscarded, tx.discardedValueVisitor, tx.Visitor()).VisitNode(node.Expression)
	return tx.Factory().UpdatePartiallyEmittedExpression(node, expression)
}

// Visits a binary expression whose value may be discarded, or which might contain an assignment to an exported
// identifier.
func (tx *CommonJSModuleTransformer) visitBinaryExpression(node *ast.BinaryExpression, resultIsDiscarded bool) *ast.Node {
	if ast.IsDestructuringAssignment(node.AsNode()) {
		return tx.visitDestructuringAssignment(node, resultIsDiscarded)
	}

	if ast.IsAssignmentExpression(node.AsNode(), false /*excludeCompoundAssignment*/) {
		return tx.visitAssignmentExpression(node)
	}

	if ast.IsCommaExpression(node.AsNode()) {
		return tx.visitCommaExpression(node, resultIsDiscarded)
	}

	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *CommonJSModuleTransformer) visitAssignmentExpression(node *ast.BinaryExpression) *ast.Node {
	// When we see an assignment expression whose left-hand side is an exported symbol,
	// we should ensure all exports of that symbol are updated with the correct value.
	//
	// - We do not transform generated identifiers unless they are file-level reserved names.
	// - We do not transform identifiers tagged with the LocalName flag.
	// - We only transform identifiers that are exported at the top level.
	if ast.IsIdentifier(node.Left) &&
		(!transformers.IsGeneratedIdentifier(tx.EmitContext(), node.Left) || isFileLevelReservedGeneratedIdentifier(tx.EmitContext(), node.Left)) &&
		!transformers.IsLocalName(tx.EmitContext(), node.Left) {
		exportedNames := tx.getExports(node.Left)
		if len(exportedNames) > 0 {
			// For each additional export of the declaration, apply an export assignment.
			expression := tx.Visitor().VisitEachChild(node.AsNode())
			for _, exportName := range exportedNames {
				expression = tx.createExportExpression(exportName, expression, &node.Loc /*location*/, false /*liveBinding*/)
			}
			return expression
		}
	}

	return tx.Visitor().VisitEachChild(node.AsNode())
}

// Visits a destructuring assignment which might target an exported identifier.
func (tx *CommonJSModuleTransformer) visitDestructuringAssignment(node *ast.BinaryExpression, valueIsDiscarded bool) *ast.Node {
	if tx.destructuringNeedsFlattening(node.Left) {
		return transformers.FlattenDestructuringAssignment(
			&tx.Transformer,
			node.AsNode(),
			!valueIsDiscarded, /*needsValue*/
			transformers.FlattenLevelAll,
			tx.createAllExportExpressions,
		)
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

// destructuringNeedsFlattening checks whether a destructuring assignment target contains any
// exported identifiers that need to be flattened into individual export assignments.
func (tx *CommonJSModuleTransformer) destructuringNeedsFlattening(node *ast.Node) bool {
	if ast.IsObjectLiteralExpression(node) {
		for _, elem := range node.Properties() {
			switch elem.Kind {
			case ast.KindPropertyAssignment:
				if tx.destructuringNeedsFlattening(elem.Initializer()) {
					return true
				}
			case ast.KindShorthandPropertyAssignment:
				if tx.destructuringNeedsFlattening(elem.Name()) {
					return true
				}
			case ast.KindSpreadAssignment:
				if tx.destructuringNeedsFlattening(elem.Expression()) {
					return true
				}
			case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
				return false
			}
		}
	} else if ast.IsArrayLiteralExpression(node) {
		for _, elem := range node.AsArrayLiteralExpression().Elements.Nodes {
			if ast.IsSpreadElement(elem) {
				if tx.destructuringNeedsFlattening(elem.Expression()) {
					return true
				}
			} else if tx.destructuringNeedsFlattening(elem) {
				return true
			}
		}
	} else if ast.IsIdentifier(node) {
		exportedNames := tx.getExports(node)
		if transformers.IsExportName(tx.EmitContext(), node) {
			// The identifier is already wrapped to be an export reference; tolerate up to one
			// matching export.
			return len(exportedNames) > 1
		}
		if len(exportedNames) == 0 {
			return false
		}
		// A single direct export whose export name matches the identifier text can be handled
		// natively: substitution will rewrite the identifier to `exports.X`, so no flattening
		// is needed. Re-aliased exports (where the export name differs from the local name) or
		// multi-exported names cannot be expressed natively in a destructuring assignment.
		if len(exportedNames) == 1 && tx.isDirectExport(node) && exportedNames[0].Text() == node.Text() {
			return false
		}
		return true
	}
	return false
}

// createAllExportExpressions is the callback used during destructuring flattening to create
// export expressions for each exported identifier binding.
func (tx *CommonJSModuleTransformer) createAllExportExpressions(name *ast.IdentifierNode, value *ast.Expression, location *core.TextRange) *ast.Expression {
	exportedNames := tx.getExports(name)
	if len(exportedNames) > 0 {
		// If the name is directly exported (i.e., `export let x`), assign to exports.name directly.
		// Otherwise, assign to the local binding first (i.e., `let x; export { x }`).
		var expression *ast.Expression
		if tx.isDirectExport(name) {
			// Create exports.name = value to handle the direct export assignment,
			// since the Go port doesn't have an onSubstituteNode mechanism to rewrite identifiers.
			exportName := name.Clone(tx.Factory())
			tx.EmitContext().AddEmitFlags(exportName, printer.EFNoComments|printer.EFNoSourceMap)
			propertyAccess := tx.Factory().NewPropertyAccessExpression(
				tx.Factory().NewIdentifier("exports"),
				nil, /*questionDotToken*/
				exportName,
				ast.NodeFlagsNone,
			)
			tx.EmitContext().AddEmitFlags(propertyAccess, printer.EFNoComments)
			expression = tx.Factory().NewAssignmentExpression(propertyAccess, value)
			tx.EmitContext().AssignCommentAndSourceMapRanges(expression, name)
		} else {
			expression = tx.Factory().NewAssignmentExpression(name, value)
		}
		for _, exportName := range exportedNames {
			expression = tx.createExportExpression(exportName, expression, location, false /*liveBinding*/)
		}
		return expression
	}
	// If the identifier is directly exported but has no additional export aliases,
	// still write to exports.name.
	if tx.isDirectExport(name) {
		exportName := name.Clone(tx.Factory())
		tx.EmitContext().AddEmitFlags(exportName, printer.EFNoComments|printer.EFNoSourceMap)
		propertyAccess := tx.Factory().NewPropertyAccessExpression(
			tx.Factory().NewIdentifier("exports"),
			nil, /*questionDotToken*/
			exportName,
			ast.NodeFlagsNone,
		)
		tx.EmitContext().AddEmitFlags(propertyAccess, printer.EFNoComments)
		result := tx.Factory().NewAssignmentExpression(propertyAccess, value)
		tx.EmitContext().AssignCommentAndSourceMapRanges(result, name)
		return result
	}
	return tx.Factory().NewAssignmentExpression(name, value)
}

// isDirectExport checks whether the identifier is directly exported from the source file
// (e.g., `export let x` or `export function f()`), as opposed to being re-exported via
// `export { x }` for a locally-declared variable.
func (tx *CommonJSModuleTransformer) isDirectExport(name *ast.IdentifierNode) bool {
	exportContainer := tx.resolver.GetReferencedExportContainer(tx.EmitContext().MostOriginal(name), false /*prefixLocals*/)
	return exportContainer != nil && ast.IsSourceFile(exportContainer)
}

func (tx *CommonJSModuleTransformer) visitAssignmentProperty(node *ast.PropertyAssignment) *ast.Node {
	return tx.Factory().UpdatePropertyAssignment(
		node,
		nil, /*modifiers*/
		tx.Visitor().VisitNode(node.Name()),
		nil, /*postfixToken*/
		nil, /*typeNode*/
		tx.assignmentPatternVisitor.VisitNode(node.Initializer),
	)
}

func (tx *CommonJSModuleTransformer) visitShorthandAssignmentProperty(node *ast.ShorthandPropertyAssignment) *ast.Node {
	target := tx.visitDestructuringAssignmentTargetNoStack(node.Name())
	if ast.IsIdentifier(target) {
		return tx.Factory().UpdateShorthandPropertyAssignment(
			node,
			nil, /*modifiers*/
			target,
			nil, /*postfixToken*/
			nil, /*typeNode*/
			node.EqualsToken,
			tx.Visitor().VisitNode(node.ObjectAssignmentInitializer),
		)
	}
	if node.ObjectAssignmentInitializer != nil {
		equalsToken := node.EqualsToken
		if equalsToken == nil {
			equalsToken = tx.Factory().NewToken(ast.KindEqualsToken)
		}
		target = tx.Factory().NewBinaryExpression(
			nil, /*modifiers*/
			target,
			nil, /*typeNode*/
			equalsToken,
			tx.Visitor().VisitNode(node.ObjectAssignmentInitializer),
		)
	}
	updated := tx.Factory().NewPropertyAssignment(
		nil, /*modifiers*/
		node.Name(),
		nil, /*postfixToken*/
		nil, /*typeNode*/
		target,
	)
	tx.EmitContext().SetOriginal(updated, node.AsNode())
	tx.EmitContext().AssignCommentAndSourceMapRanges(updated, node.AsNode())
	return updated
}

func (tx *CommonJSModuleTransformer) visitAssignmentRestProperty(node *ast.SpreadAssignment) *ast.Node {
	return tx.Factory().UpdateSpreadAssignment(
		node,
		tx.visitDestructuringAssignmentTarget(node.Expression),
	)
}

func (tx *CommonJSModuleTransformer) visitAssignmentRestElement(node *ast.SpreadElement) *ast.Node {
	return tx.Factory().UpdateSpreadElement(
		node,
		tx.visitDestructuringAssignmentTarget(node.Expression),
	)
}

func (tx *CommonJSModuleTransformer) visitAssignmentElement(node *ast.Node) *ast.Node {
	if ast.IsBinaryExpression(node) {
		n := node.AsBinaryExpression()
		if n.OperatorToken.Kind == ast.KindEqualsToken {
			return tx.Factory().UpdateBinaryExpression(
				n,
				nil, /*modifiers*/
				tx.visitDestructuringAssignmentTarget(n.Left),
				nil, /*typeNode*/
				n.OperatorToken,
				tx.Visitor().VisitNode(n.Right),
			)
		}
	}

	return tx.visitDestructuringAssignmentTargetNoStack(node)
}

func (tx *CommonJSModuleTransformer) visitDestructuringAssignmentTarget(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	switch node.Kind {
	case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression:
		node = tx.visitAssignmentPatternNoStack(node)
	default:
		node = tx.visitDestructuringAssignmentTargetNoStack(node)
	}
	return node
}

func (tx *CommonJSModuleTransformer) visitDestructuringAssignmentTargetNoStack(node *ast.Node) *ast.Node {
	if ast.IsIdentifier(node) &&
		(!transformers.IsGeneratedIdentifier(tx.EmitContext(), node) || isFileLevelReservedGeneratedIdentifier(tx.EmitContext(), node)) &&
		!transformers.IsLocalName(tx.EmitContext(), node) {
		expression := tx.visitExpressionIdentifier(node)
		exportedNames := tx.getExports(node)
		if len(exportedNames) > 0 {
			// transforms:
			//  var x;
			//  export { x }
			//  { x: x } = y
			// to:
			//  { x: { set value(v) { exports.x = x = v; } }.value } = y

			value := tx.Factory().NewUniqueNameEx("value", printer.AutoGenerateOptions{
				Flags: printer.GeneratedIdentifierFlagsOptimistic,
			})
			expression = tx.Factory().NewAssignmentExpression(expression, value)

			for _, exportName := range exportedNames {
				expression = tx.createExportExpression(exportName, expression, nil /*location*/, false /*liveBinding*/)
			}

			statement := tx.Factory().NewExpressionStatement(expression)
			statementList := tx.Factory().NewNodeList([]*ast.Node{statement})
			param := tx.Factory().NewParameterDeclaration(
				nil, /*modifiers*/
				nil, /*dotDotDotToken*/
				value,
				nil, /*questionToken*/
				nil, /*type*/
				nil, /*initializer*/
			)
			valueSetter := tx.Factory().NewSetAccessorDeclaration(
				nil, /*modifiers*/
				tx.Factory().NewIdentifier("value"),
				nil, /*typeParameters*/
				tx.Factory().NewNodeList([]*ast.Node{param}),
				nil, /*returnType*/
				nil, /*fullSignature*/
				tx.Factory().NewBlock(statementList, false /*multiLine*/),
			)
			propertyList := tx.Factory().NewNodeList([]*ast.Node{valueSetter})
			expression = tx.Factory().NewObjectLiteralExpression(propertyList, false /*multiLine*/)
			expression = tx.Factory().NewPropertyAccessExpression(expression, nil /*questionDotToken*/, tx.Factory().NewIdentifier("value"), ast.NodeFlagsNone)
		}
		return expression
	}

	return tx.visitNoStack(node, false /*resultIsDiscarded*/)
}

// Visits a comma expression whose left-hand value is always discard, and whose right-hand value may be discarded at runtime.
func (tx *CommonJSModuleTransformer) visitCommaExpression(node *ast.BinaryExpression, resultIsDiscarded bool) *ast.Node {
	left := tx.discardedValueVisitor.VisitNode(node.Left)
	right := core.IfElse(resultIsDiscarded, tx.discardedValueVisitor, tx.Visitor()).VisitNode(node.Right)
	return tx.Factory().UpdateBinaryExpression(node, nil /*modifiers*/, left, nil /*typeNode*/, node.OperatorToken, right)
}

// Visits a prefix unary expression that might modify an exported identifier.
func (tx *CommonJSModuleTransformer) visitPrefixUnaryExpression(node *ast.PrefixUnaryExpression, resultIsDiscarded bool) *ast.Node {
	// When we see a prefix increment expression whose operand is an exported
	// symbol, we should ensure all exports of that symbol are updated with the correct
	// value.
	//
	// - We do not transform generated identifiers for any reason.
	// - We do not transform identifiers tagged with the LocalName flag.
	// - We do not transform identifiers that were originally the name of an enum or
	//   namespace due to how they are transformed in TypeScript.
	// - We only transform identifiers that are exported at the top level.
	if (node.Operator == ast.KindPlusPlusToken || node.Operator == ast.KindMinusMinusToken) &&
		ast.IsIdentifier(node.Operand) &&
		!transformers.IsLocalName(tx.EmitContext(), node.Operand) {
		exportedNames := tx.getExports(node.Operand)
		if len(exportedNames) > 0 {
			// given:
			//   var x = 0;
			//   export { x }
			//   ++x;
			// emits:
			//   var x = 0;
			//   exports.x = x;
			//   exports.x = ++x;
			// note:
			//   after the operation, `exports.x` will hold the value of `x` after the increment.

			expression := tx.Factory().UpdatePrefixUnaryExpression(node, node.Operator, tx.Visitor().VisitNode(node.Operand))
			for _, exportName := range exportedNames {
				expression = tx.createExportExpression(exportName, expression, nil /*location*/, false /*liveBinding*/)
				tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())
			}
			return expression
		}
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

// Visits a postfix unary expression that might modify an exported identifier.
func (tx *CommonJSModuleTransformer) visitPostfixUnaryExpression(node *ast.PostfixUnaryExpression, resultIsDiscarded bool) *ast.Node {
	// When we see a postfix increment expression whose operand is an exported
	// symbol, we should ensure all exports of that symbol are updated with the correct
	// value.
	//
	// - We do not transform generated identifiers for any reason.
	// - We do not transform identifiers tagged with the LocalName flag.
	// - We do not transform identifiers that were originally the name of an enum or
	//   namespace due to how they are transformed in TypeScript.
	// - We only transform identifiers that are exported at the top level.
	if (node.Operator == ast.KindPlusPlusToken || node.Operator == ast.KindMinusMinusToken) &&
		ast.IsIdentifier(node.Operand) &&
		!transformers.IsLocalName(tx.EmitContext(), node.Operand) {
		exportedNames := tx.getExports(node.Operand)
		if len(exportedNames) > 0 {
			// given (value is discarded):
			//   var x = 0;
			//   export { x }
			//   x++;
			// emits:
			//   var x = 0, y;
			//   exports.x = x;
			//   exports.x = (x++, x);
			// note:
			//   after the operation, `exports.x` will hold the value of `x` after the increment.
			//
			// given (value is not discarded):
			//   var x = 0, y;
			//   export { x }
			//   y = x++;
			// emits:
			//   var _a;
			//   var x = 0, y;
			//   exports.x = x;
			//   y = (exports.x = (_a = x++, x), _a);
			// note:
			//   after the operation, `exports.x` will hold the value of `x` after the increment, while
			//   `y` will hold the value of `x` before the increment.

			var temp *ast.IdentifierNode
			expression := tx.Factory().UpdatePostfixUnaryExpression(node, tx.Visitor().VisitNode(node.Operand), node.Operator)
			if !resultIsDiscarded {
				temp = tx.Factory().NewTempVariable()
				tx.EmitContext().AddVariableDeclaration(temp)

				expression = tx.Factory().NewAssignmentExpression(temp, expression)
				tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())
			}

			expression = tx.Factory().NewCommaExpression(expression, node.Operand.Clone(tx.Factory()))
			tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())

			for _, exportName := range exportedNames {
				expression = tx.createExportExpression(exportName, expression, nil /*location*/, false /*liveBinding*/)
				tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())
			}

			if temp != nil {
				expression = tx.Factory().NewCommaExpression(expression, temp.AsNode())
				tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())
			}

			return expression
		}
	}

	return tx.Visitor().VisitEachChild(node.AsNode())
}

// Visits a call expression that might reference an imported symbol and thus require an indirect call, or that might
// be an `import()` or `require()` call that may need to be rewritten.
func (tx *CommonJSModuleTransformer) visitCallExpression(node *ast.CallExpression) *ast.Node {
	needsRewrite := false
	if tx.compilerOptions.RewriteRelativeImportExtensions.IsTrue() {
		if ast.IsImportCall(node.AsNode()) && len(node.Arguments.Nodes) > 0 ||
			ast.IsInJSFile(node.AsNode()) && ast.IsRequireCall(node.AsNode(), false /*requireStringLiteralLikeArgument*/) {
			needsRewrite = true
		}
	}
	if ast.IsImportCall(node.AsNode()) && tx.shouldTransformImportCall() {
		return tx.visitImportCallExpression(node, needsRewrite)
	}
	if needsRewrite {
		return tx.shimOrRewriteImportOrRequireCall(node.AsCallExpression())
	}
	if ast.IsIdentifier(node.Expression) {
		// given:
		//   import { f } from "mod";
		//   f();
		// emits:
		//   const mod_1 = require("mod");
		//   (0, mod_1.f)();
		// note:
		//   the indirect call is applied by the printer by way of the `EFIndirectCall` emit flag.
		expression := tx.visitExpressionIdentifier(node.Expression)
		updated := tx.Factory().UpdateCallExpression(
			node,
			expression,
			node.QuestionDotToken,
			nil, /*typeArguments*/
			tx.Visitor().VisitNodes(node.Arguments),
			node.Flags,
		)
		if !ast.IsIdentifier(expression) && !transformers.IsHelperName(tx.EmitContext(), node.Expression) {
			tx.EmitContext().AddEmitFlags(updated, printer.EFIndirectCall)
		}
		return updated
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *CommonJSModuleTransformer) shouldTransformImportCall() bool {
	return ast.ShouldTransformImportCall(tx.currentSourceFile.FileName(), tx.compilerOptions, tx.getEmitModuleFormatOfFile(tx.currentSourceFile))
}

func (tx *CommonJSModuleTransformer) visitImportCallExpression(node *ast.CallExpression, rewriteOrShim bool) *ast.Node {
	if tx.moduleKind == core.ModuleKindNone && tx.languageVersion >= core.ScriptTargetES2020 {
		return tx.Visitor().VisitEachChild(node.AsNode())
	}

	externalModuleName := getExternalModuleNameLiteral(tx.Factory(), node.AsNode(), tx.currentSourceFile, nil /*host*/, nil /*resolver*/, tx.compilerOptions)
	firstArgument := tx.Visitor().VisitNode(core.FirstOrNil(node.Arguments.Nodes))

	// Only use the external module name if it differs from the first argument. This allows us to preserve the quote style of the argument on output.
	var argument *ast.Expression
	if externalModuleName != nil && (firstArgument == nil || !ast.IsStringLiteral(firstArgument) || firstArgument.Text() != externalModuleName.Text()) {
		argument = externalModuleName
	} else if firstArgument != nil && rewriteOrShim {
		if ast.IsStringLiteral(firstArgument) {
			argument = rewriteModuleSpecifier(tx.EmitContext(), firstArgument, tx.compilerOptions)
		} else {
			argument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(firstArgument, tx.compilerOptions.Jsx == core.JsxEmitPreserve)
		}
	} else {
		argument = firstArgument
	}
	return tx.createImportCallExpressionCommonJS(argument)
}

func (tx *CommonJSModuleTransformer) createImportCallExpressionCommonJS(arg *ast.Expression) *ast.Expression {
	// import(x)
	// emit as
	// Promise.resolve(`${x}`).then((s) => require(s)) /*CommonJS Require*/
	// We have to wrap require in then callback so that require is done in asynchronously
	// if we simply do require in resolve callback in Promise constructor. We will execute the loading immediately
	// If the arg is not inlineable, we have to evaluate and ToString() it in the current scope
	// Otherwise, we inline it in require() so that it's statically analyzable

	needSyncEval := arg != nil && !isSimpleInlineableExpression(arg)

	var promiseResolveArguments []*ast.Expression
	if needSyncEval {
		promiseResolveArguments = []*ast.Expression{
			tx.Factory().NewTemplateExpression(
				tx.Factory().NewTemplateHead("", "", ast.TokenFlagsNone),
				tx.Factory().NewNodeList([]*ast.TemplateSpanNode{
					tx.Factory().NewTemplateSpan(arg, tx.Factory().NewTemplateTail("", "", ast.TokenFlagsNone)),
				}),
			),
		}
	}
	promiseResolveCall := tx.Factory().NewCallExpression(
		tx.Factory().NewPropertyAccessExpression(
			tx.Factory().NewIdentifier("Promise"),
			nil, /*questionDotToken*/
			tx.Factory().NewIdentifier("resolve"),
			ast.NodeFlagsNone,
		),
		nil, /*questionDotToken*/
		nil, /*typeArguments*/
		tx.Factory().NewNodeList(promiseResolveArguments),
		ast.NodeFlagsNone,
	)

	var requireArguments []*ast.Expression
	if needSyncEval {
		requireArguments = []*ast.Expression{
			tx.Factory().NewIdentifier("s"),
		}
	} else if arg != nil {
		requireArguments = []*ast.Expression{arg}
	}

	requireCall := tx.Factory().NewImportStarHelper(
		tx.Factory().NewCallExpression(
			tx.Factory().NewIdentifier("require"),
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Factory().NewNodeList(requireArguments),
			ast.NodeFlagsNone,
		),
	)

	var parameters []*ast.ParameterDeclarationNode
	if needSyncEval {
		parameters = []*ast.ParameterDeclarationNode{
			tx.Factory().NewParameterDeclaration(
				nil, /*modifiers*/
				nil, /*dotDotDotToken*/
				tx.Factory().NewIdentifier("s"),
				nil, /*questionToken*/
				nil, /*type*/
				nil, /*initializer*/
			),
		}
	}

	function := tx.Factory().NewArrowFunction(
		nil, /*modifiers*/
		nil, /*typeParameters*/
		tx.Factory().NewNodeList(parameters),
		nil, /*type*/
		nil, /*fullSignature*/
		tx.Factory().NewToken(ast.KindEqualsGreaterThanToken), /*equalsGreaterThanToken*/
		requireCall,
	)

	downleveledImport := tx.Factory().NewCallExpression(
		tx.Factory().NewPropertyAccessExpression(
			promiseResolveCall,
			nil, /*questionDotToken*/
			tx.Factory().NewIdentifier("then"),
			ast.NodeFlagsNone,
		),
		nil, /*questionDotToken*/
		nil, /*typeArguments*/
		tx.Factory().NewNodeList([]*ast.Expression{function}),
		ast.NodeFlagsNone,
	)
	return downleveledImport
}

func (tx *CommonJSModuleTransformer) shimOrRewriteImportOrRequireCall(node *ast.CallExpression) *ast.Node {
	expression := tx.Visitor().VisitNode(node.Expression)
	argumentsList := node.Arguments
	if len(node.Arguments.Nodes) > 0 {
		firstArgument := node.Arguments.Nodes[0]
		firstArgumentChanged := false
		if ast.IsStringLiteralLike(firstArgument) {
			rewritten := rewriteModuleSpecifier(tx.EmitContext(), firstArgument, tx.compilerOptions)
			firstArgumentChanged = rewritten != firstArgument
			firstArgument = rewritten
		} else {
			firstArgument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(firstArgument, tx.compilerOptions.Jsx == core.JsxEmitPreserve)
			firstArgumentChanged = true
		}

		rest, restChanged := tx.Visitor().VisitSlice(node.Arguments.Nodes[1:])
		if firstArgumentChanged || restChanged {
			arguments := append([]*ast.Expression{firstArgument}, rest...)
			argumentsList = tx.Factory().NewNodeList(arguments)
			argumentsList.Loc = node.Arguments.Loc
		}
	}

	return tx.Factory().UpdateCallExpression(
		node,
		expression,
		node.QuestionDotToken,
		nil, /*typeArguments*/
		argumentsList,
		node.Flags,
	)
}

// Visits a tagged template expression that might reference an imported symbol and thus require an indirect call.
func (tx *CommonJSModuleTransformer) visitTaggedTemplateExpression(node *ast.TaggedTemplateExpression) *ast.Node {
	if ast.IsIdentifier(node.Tag) {
		// given:
		//   import { f } from "mod";
		//   f``;
		// emits:
		//   const mod_1 = require("mod");
		//   (0, mod_1.f) ``;
		// note:
		//   the indirect call is applied by the printer by way of the `EFIndirectCall` emit flag.

		expression := tx.visitExpressionIdentifier(node.Tag)
		updated := tx.Factory().UpdateTaggedTemplateExpression(
			node,
			expression,
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Visitor().VisitNode(node.Template),
			node.Flags,
		)
		if !ast.IsIdentifier(expression) && !transformers.IsHelperName(tx.EmitContext(), node.Tag) {
			tx.EmitContext().AddEmitFlags(updated, printer.EFIndirectCall)
		}
		return updated
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

// Visits a shorthand property assignment that might reference an imported or exported symbol.
func (tx *CommonJSModuleTransformer) visitShorthandPropertyAssignment(node *ast.ShorthandPropertyAssignment) *ast.Node {
	name := node.Name()
	exportedOrImportedName := tx.visitExpressionIdentifier(name)
	if exportedOrImportedName != name {
		// A shorthand property with an assignment initializer is probably part of a
		// destructuring assignment
		expression := exportedOrImportedName
		if node.ObjectAssignmentInitializer != nil {
			expression = tx.Factory().NewAssignmentExpression(
				expression,
				tx.Visitor().VisitNode(node.ObjectAssignmentInitializer),
			)
		}
		assignment := tx.Factory().NewPropertyAssignment(nil /*modifiers*/, name, nil /*postfixToken*/, nil /*typeNode*/, expression)
		assignment.Loc = node.Loc
		tx.EmitContext().AssignCommentAndSourceMapRanges(assignment, node.AsNode())
		return assignment
	}
	return tx.Factory().UpdateShorthandPropertyAssignment(
		node,
		nil, /*modifiers*/
		exportedOrImportedName,
		nil, /*postfixToken*/
		nil, /*typeNode*/
		node.EqualsToken,
		tx.Visitor().VisitNode(node.ObjectAssignmentInitializer),
	)
}

// Visits an identifier that, if it is in an expression position, might reference an imported or exported symbol.
func (tx *CommonJSModuleTransformer) visitIdentifier(node *ast.IdentifierNode) *ast.Node {
	if transformers.IsIdentifierReference(node, tx.parentNode) {
		return tx.visitExpressionIdentifier(node)
	}
	return node
}

// Visits an identifier in an expression position that might reference an imported or exported symbol.
func (tx *CommonJSModuleTransformer) visitExpressionIdentifier(node *ast.IdentifierNode) *ast.Node {
	if info := tx.EmitContext().GetAutoGenerateInfo(node); !(info != nil && !info.Flags.HasAllowNameSubstitution()) &&
		!transformers.IsHelperName(tx.EmitContext(), node) &&
		!transformers.IsLocalName(tx.EmitContext(), node) &&
		!isDeclarationNameOfEnumOrNamespace(tx.EmitContext(), node) {
		exportContainer := tx.resolver.GetReferencedExportContainer(tx.EmitContext().MostOriginal(node), transformers.IsExportName(tx.EmitContext(), node))
		if exportContainer != nil && ast.IsSourceFile(exportContainer) {
			reference := tx.Factory().NewPropertyAccessExpression(
				tx.Factory().NewIdentifier("exports"),
				nil, /*questionDotToken*/
				node.Clone(tx.Factory()),
				ast.NodeFlagsNone,
			)
			tx.EmitContext().AssignCommentAndSourceMapRanges(reference, node)
			reference.Loc = node.Loc
			return reference
		}

		importDeclaration := tx.resolver.GetReferencedImportDeclaration(tx.EmitContext().MostOriginal(node))
		if importDeclaration != nil {
			if ast.IsImportClause(importDeclaration) {
				reference := tx.Factory().NewPropertyAccessExpression(
					tx.Factory().NewGeneratedNameForNode(importDeclaration.Parent), //nolint:customlint // Resolver returns parse-tree declarations; Parent is used to find the owning import declaration.
					nil, /*questionDotToken*/
					tx.Factory().NewIdentifier("default"),
					ast.NodeFlagsNone,
				)
				tx.EmitContext().AssignCommentAndSourceMapRanges(reference, node)
				reference.Loc = node.Loc
				return reference
			}
			if ast.IsImportSpecifier(importDeclaration) {
				name := importDeclaration.AsImportSpecifier().PropertyNameOrName()
				decl := ast.FindAncestor(importDeclaration, ast.IsImportDeclaration)
				target := tx.Factory().NewGeneratedNameForNode(core.Coalesce(decl, importDeclaration))
				var reference *ast.Node
				if ast.IsStringLiteral(name) {
					reference = tx.Factory().NewElementAccessExpression(
						target,
						nil, /*questionDotToken*/
						tx.Factory().NewStringLiteralFromNode(name),
						ast.NodeFlagsNone,
					)
				} else {
					referenceName := name.Clone(tx.Factory())
					tx.EmitContext().AddEmitFlags(referenceName, printer.EFNoSourceMap|printer.EFNoComments)
					reference = tx.Factory().NewPropertyAccessExpression(
						target,
						nil, /*questionDotToken*/
						referenceName,
						ast.NodeFlagsNone,
					)
				}
				tx.EmitContext().AssignCommentAndSourceMapRanges(reference, node)
				reference.Loc = node.Loc
				return reference
			}
		}
	}
	return node
}

// Gets the exported names of an identifier, if it is exported.
func (tx *CommonJSModuleTransformer) getExports(name *ast.IdentifierNode) []*ast.ModuleExportName {
	if !transformers.IsGeneratedIdentifier(tx.EmitContext(), name) {
		importDeclaration := tx.resolver.GetReferencedImportDeclaration(tx.EmitContext().MostOriginal(name))
		if importDeclaration != nil {
			return tx.currentModuleInfo.exportedBindings.Get(importDeclaration)
		}

		// An exported namespace or enum may merge with an ambient declaration, which won't show up in .js emit, so
		// we analyze all value exports of a symbol.
		var bindingsSet collections.Set[*ast.ModuleExportName]
		var bindings []*ast.ModuleExportName
		declarations := tx.resolver.GetReferencedValueDeclarations(tx.EmitContext().MostOriginal(name))
		if declarations != nil {
			for _, declaration := range declarations {
				exportedBindings := tx.currentModuleInfo.exportedBindings.Get(declaration)
				for _, binding := range exportedBindings {
					if !bindingsSet.Has(binding) {
						bindingsSet.Add(binding)
						bindings = append(bindings, binding)
					}
				}
			}
			return bindings
		}
	} else if isFileLevelReservedGeneratedIdentifier(tx.EmitContext(), name) {
		exportSpecifiers := tx.currentModuleInfo.exportSpecifiers.Get(name.Text())
		if exportSpecifiers != nil {
			var exportedNames []*ast.ModuleExportName
			for _, exportSpecifier := range exportSpecifiers {
				exportedNames = append(exportedNames, exportSpecifier.Name())
			}
			return exportedNames
		}
	}
	return nil
}
