package declarations

import (
	"fmt"
	"iter"
	"slices"
	"strings"

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/collections"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/debug"
	"github.com/microsoft/typescript-go/internal/diagnostics"
	"github.com/microsoft/typescript-go/internal/jsnum"
	"github.com/microsoft/typescript-go/internal/modulespecifiers"
	"github.com/microsoft/typescript-go/internal/nodebuilder"
	"github.com/microsoft/typescript-go/internal/printer"
	"github.com/microsoft/typescript-go/internal/scanner"
	"github.com/microsoft/typescript-go/internal/transformers"
	"github.com/microsoft/typescript-go/internal/tspath"
)

type ReferencedFilePair struct {
	file *ast.SourceFile
	ref  *ast.FileReference
}

type OutputPaths interface {
	DeclarationFilePath() string
	JsFilePath() string
}

// Used to be passed in the TransformationContext, which is now just an EmitContext
type DeclarationEmitHost interface {
	modulespecifiers.ModuleSpecifierGenerationHost
	GetCurrentDirectory() string
	UseCaseSensitiveFileNames() bool
	GetSourceFileFromReference(origin *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile

	GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) OutputPaths
	GetResolutionModeOverride(node *ast.Node) core.ResolutionMode
	GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags
	GetEmitResolver() printer.EmitResolver
}

type thisPropertyAssignmentKey struct {
	name      string
	node      *ast.Node
	isStatic  bool
	isPrivate bool
}

func getThisPropertyAssignmentKey(name *ast.Node, node *ast.Node, isStatic bool) thisPropertyAssignmentKey {
	isPrivate := ast.IsPrivateIdentifier(name)
	if name != nil && !ast.IsDynamicName(name) {
		if nameText, ok := ast.TryGetTextOfPropertyName(name); ok {
			return thisPropertyAssignmentKey{name: nameText, isStatic: isStatic, isPrivate: isPrivate}
		}
	}
	return thisPropertyAssignmentKey{node: node, isStatic: isStatic, isPrivate: isPrivate}
}

type DeclarationTransformer struct {
	transformers.Transformer
	host                DeclarationEmitHost
	compilerOptions     *core.CompilerOptions
	tracker             *SymbolTrackerImpl
	state               *SymbolTrackerSharedState
	resolver            printer.EmitResolver
	declarationFilePath string
	declarationMapPath  string

	needsDeclare                     bool
	needsScopeFixMarker              bool
	resultHasScopeMarker             bool
	enclosingDeclaration             *ast.Node
	resultHasExternalModuleIndicator bool
	suppressNewDiagnosticContexts    bool
	witnessedCjsExports              collections.Set[string]
	lateStatementReplacementMap      map[ast.NodeId]*ast.Node
	expandoHosts                     map[ast.NodeId]*ast.Node               // store the result of transforming expando hosts so they can be inserted later if the host is actually referenced
	expandoMembers                   map[ast.NodeId][]*ast.Node             // store any found expando _members_ after transforming them so *if* the host is referenced, they can be emitted alongside it
	deferredExpandoAssignments       map[ast.NodeId][]*ast.BinaryExpression // expando assignments whose host wasn't visible when collected, processed if the host is late-marked visible
	seenProperties                   collections.Set[thisPropertyAssignmentKey]
	thisPropertyAssignmentsCollected []*ast.Node
	rawReferencedFiles               []ReferencedFilePair
	rawTypeReferenceDirectives       []*ast.FileReference
	rawLibReferenceDirectives        []*ast.FileReference
	bindingNameVisitor               *ast.NodeVisitor
	expressionVisitor                *ast.NodeVisitor
	cjsExportAssignmentVisitor       *ast.NodeVisitor
	exportStrippingVisitor           *ast.NodeVisitor
	thisPropertyVisitor              *ast.NodeVisitor

	cjsExportAssignment          *ast.Node
	cjsExportMembers             []*ast.Node
	cjsExportAssignmentName      *ast.Node // tracks the name node used for `export =` in CJS module.exports assignments
	declareStrippingVisitor      *ast.NodeVisitor
	inClassExpressionDeclaration bool // true when serializing members of a class expression kept as a class declaration
}

// TODO: Convert to transformers.TransformerFactory signature to allow more automatic composition with other transforms
func NewDeclarationTransformer(host DeclarationEmitHost, context *printer.EmitContext, compilerOptions *core.CompilerOptions, declarationFilePath string, declarationMapPath string) *DeclarationTransformer {
	resolver := host.GetEmitResolver()
	state := &SymbolTrackerSharedState{isolatedDeclarations: compilerOptions.IsolatedDeclarations.IsTrue(), stripInternal: compilerOptions.StripInternal.IsTrue(), resolver: resolver}
	tracker := NewSymbolTracker(host, resolver, state)
	// TODO: Use new host GetOutputPathsFor method instead of passing in entrypoint paths (which will also better support bundled emit)
	tx := &DeclarationTransformer{
		host:                host,
		compilerOptions:     compilerOptions,
		tracker:             tracker,
		state:               state,
		resolver:            resolver,
		declarationFilePath: declarationFilePath,
		declarationMapPath:  declarationMapPath,
	}
	tx.state.reportExpandoFunctionErrors = func(node *ast.Node) {
		if !tx.state.isolatedDeclarations {
			return
		}
		props := resolver.GetPropertiesOfContainerFunction(node)
		for _, p := range props {
			if ast.IsExpandoPropertyDeclaration(p.ValueDeclaration) {
				errorTarget := p.ValueDeclaration
				if ast.IsBinaryExpression(errorTarget) {
					errorTarget = errorTarget.AsBinaryExpression().Left
				}
				tx.state.addDiagnostic(createDiagnosticForNode(errorTarget, diagnostics.Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function))
			}
		}
	}
	tx.NewTransformer(tx.visit, context)
	tx.bindingNameVisitor = tx.EmitContext().NewNodeVisitor(tx.visitBindingName)
	tx.expressionVisitor = tx.EmitContext().NewNodeVisitor(tx.visitNestedExpression)
	tx.exportStrippingVisitor = tx.EmitContext().NewNodeVisitor(tx.stripExportModifiers)
	tx.thisPropertyVisitor = tx.EmitContext().NewNodeVisitor(tx.visitThisPropertyAssignments)
	tx.cjsExportAssignmentVisitor = tx.EmitContext().NewNodeVisitor(tx.visitCJSExportAssignments)
	tx.declareStrippingVisitor = tx.EmitContext().NewNodeVisitor(tx.stripDeclareModifiers)
	return tx
}

func (tx *DeclarationTransformer) GetDiagnostics() []*ast.Diagnostic {
	return tx.state.diagnostics
}

func (tx *DeclarationTransformer) shouldStripInternal(node *ast.Node) bool {
	return tx.state.stripInternal && node != nil && tx.isInternalDeclaration(node, tx.state.currentSourceFile)
}

func (tx *DeclarationTransformer) isInternalDeclaration(node *ast.Node, sourceFile *ast.SourceFile) bool {
	if node == nil {
		return false
	}
	parseTreeNode := tx.EmitContext().MostOriginal(node)
	if !ast.IsParseTreeNode(parseTreeNode) {
		return false
	}
	if parseTreeNode.Kind == ast.KindParameter {
		params := parseTreeNode.Parent.Parameters()
		paramIdx := slices.IndexFunc(params, func(p *ast.ParameterDeclarationNode) bool {
			return p.AsNode() == parseTreeNode
		})
		var previousSibling *ast.Node
		if paramIdx > 0 {
			previousSibling = params[paramIdx-1].AsNode()
		}

		text := sourceFile.Text()
		var commentRanges []ast.CommentRange

		if previousSibling != nil {
			// to handle
			// ... parameters, /** @internal */
			// public param: string
			trailingPos := scanner.SkipTriviaEx(text, previousSibling.End()+1, &scanner.SkipTriviaOptions{StopAtComments: true})
			for comment := range scanner.GetTrailingCommentRanges(tx.Factory().AsNodeFactory(), text, trailingPos) {
				commentRanges = append(commentRanges, comment)
			}
			for comment := range scanner.GetLeadingCommentRanges(tx.Factory().AsNodeFactory(), text, node.Pos()) {
				commentRanges = append(commentRanges, comment)
			}
		} else {
			trailingPos := scanner.SkipTriviaEx(text, node.Pos(), &scanner.SkipTriviaOptions{StopAtComments: true})
			for comment := range scanner.GetTrailingCommentRanges(tx.Factory().AsNodeFactory(), text, trailingPos) {
				commentRanges = append(commentRanges, comment)
			}
		}

		if len(commentRanges) > 0 {
			return hasInternalAnnotation(commentRanges[len(commentRanges)-1], sourceFile)
		}
		return false
	}

	for commentRange := range tx.getLeadingCommentRangesOfNode(parseTreeNode, sourceFile) {
		if hasInternalAnnotation(commentRange, sourceFile) {
			return true
		}
	}
	return false
}

func (tx *DeclarationTransformer) getLeadingCommentRangesOfNode(node *ast.Node, sourceFile *ast.SourceFile) iter.Seq[ast.CommentRange] {
	if node == nil || node.Kind == ast.KindJsxText {
		return nil
	}
	return scanner.GetLeadingCommentRanges(tx.Factory().AsNodeFactory(), sourceFile.Text(), node.Pos())
}

func hasInternalAnnotation(commentRange ast.CommentRange, sourceFile *ast.SourceFile) bool {
	comment := sourceFile.Text()[commentRange.Pos():commentRange.End()]
	return strings.Contains(comment, "@internal")
}

const declarationEmitNodeBuilderFlags = nodebuilder.FlagsMultilineObjectLiterals |
	nodebuilder.FlagsWriteClassExpressionAsTypeLiteral |
	nodebuilder.FlagsUseTypeOfFunction |
	nodebuilder.FlagsUseStructuralFallback |
	nodebuilder.FlagsAllowEmptyTuple |
	nodebuilder.FlagsGenerateNamesForShadowedTypeParams |
	nodebuilder.FlagsNoTruncation

const declarationEmitInternalNodeBuilderFlags = nodebuilder.InternalFlagsAllowUnresolvedNames

// functions as both `visitDeclarationStatements` and `transformRoot`, utilitzing SyntaxList nodes
func (tx *DeclarationTransformer) visit(node *ast.Node) *ast.Node {
	if node == nil {
		return nil
	}
	switch node.Kind {
	case ast.KindSourceFile:
		return tx.visitSourceFile(node.AsSourceFile())
	// statements we keep but do something to
	case ast.KindFunctionDeclaration,
		ast.KindModuleDeclaration,
		ast.KindImportEqualsDeclaration,
		ast.KindInterfaceDeclaration,
		ast.KindClassDeclaration,
		ast.KindJSTypeAliasDeclaration,
		ast.KindTypeAliasDeclaration,
		ast.KindEnumDeclaration,
		ast.KindVariableStatement,
		ast.KindImportDeclaration,
		ast.KindJSImportDeclaration,
		ast.KindExportDeclaration,
		ast.KindExportAssignment:
		return tx.visitDeclarationStatements(node)
	// statements we elide
	case ast.KindBreakStatement,
		ast.KindContinueStatement,
		ast.KindDebuggerStatement,
		ast.KindDoStatement,
		ast.KindEmptyStatement,
		ast.KindForInStatement,
		ast.KindForOfStatement,
		ast.KindForStatement,
		ast.KindIfStatement,
		ast.KindLabeledStatement,
		ast.KindReturnStatement,
		ast.KindSwitchStatement,
		ast.KindThrowStatement,
		ast.KindTryStatement,
		ast.KindWhileStatement,
		ast.KindWithStatement,
		ast.KindNotEmittedStatement,
		ast.KindBlock,
		ast.KindMissingDeclaration,
		ast.KindExpressionStatement:
		return nil
	// parts of things, things we just visit children of
	default:
		return tx.visitDeclarationSubtree(node)
	}
}

func throwDiagnostic(result printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic {
	panic("Diagnostic emitted without context")
}

func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node {
	tx.cjsExportAssignmentName = nil
	if node.IsDeclarationFile {
		return node.AsNode()
	}

	tx.needsDeclare = true
	tx.needsScopeFixMarker = false
	tx.resultHasScopeMarker = false
	tx.enclosingDeclaration = node.AsNode()
	tx.state.getSymbolAccessibilityDiagnostic = throwDiagnostic
	tx.resultHasExternalModuleIndicator = false
	tx.suppressNewDiagnosticContexts = false
	tx.state.lateMarkedStatements = make([]*ast.Node, 0)
	tx.lateStatementReplacementMap = make(map[ast.NodeId]*ast.Node)
	tx.expandoHosts = make(map[ast.NodeId]*ast.Node)
	tx.expandoMembers = make(map[ast.NodeId][]*ast.Node)
	tx.deferredExpandoAssignments = make(map[ast.NodeId][]*ast.BinaryExpression)
	tx.rawReferencedFiles = make([]ReferencedFilePair, 0)
	tx.rawTypeReferenceDirectives = make([]*ast.FileReference, 0)
	tx.rawLibReferenceDirectives = make([]*ast.FileReference, 0)
	tx.witnessedCjsExports.Clear()
	tx.state.currentSourceFile = node
	tx.collectFileReferences(node)
	tx.resolver.PrecalculateDeclarationEmitVisibility(tx.EmitContext().MostOriginal(node.AsNode()).AsSourceFile())
	updated := tx.transformSourceFile(node)
	tx.state.currentSourceFile = nil
	return updated
}

func (tx *DeclarationTransformer) collectFileReferences(sourceFile *ast.SourceFile) {
	tx.rawReferencedFiles = append(tx.rawReferencedFiles, core.Map(sourceFile.ReferencedFiles, func(ref *ast.FileReference) ReferencedFilePair { return ReferencedFilePair{file: sourceFile, ref: ref} })...)
	tx.rawTypeReferenceDirectives = append(tx.rawTypeReferenceDirectives, sourceFile.TypeReferenceDirectives...)
	tx.rawLibReferenceDirectives = append(tx.rawLibReferenceDirectives, sourceFile.LibReferenceDirectives...)
}

func nodeOrSyntaxListChildren(node *ast.Node) []*ast.Node {
	if ast.IsSyntaxList(node) {
		return node.AsSyntaxList().Children
	}
	return []*ast.Node{node}
}

func flattenSyntaxLists(nodes []*ast.Node) []*ast.Node {
	return core.FlatMap(nodes, nodeOrSyntaxListChildren)
}

func (tx *DeclarationTransformer) appendCjsExports(combinedStatements *ast.StatementList) *ast.StatementList {
	result := []*ast.Node{}
	if tx.cjsExportAssignment != nil {
		result = append(result, tx.cjsExportAssignment)
	}
	result = append(result, tx.cjsExportMembers...)
	result = append(result, combinedStatements.Nodes...)
	statementNodes := flattenSyntaxLists(result)
	if len(statementNodes) != len(combinedStatements.Nodes) {
		combinedStatements = tx.Factory().NewNodeList(statementNodes)
	}
	return combinedStatements
}

func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast.Node {
	tx.cjsExportAssignment = nil
	tx.cjsExportAssignmentName = nil
	tx.cjsExportMembers = nil
	defer func() {
		tx.cjsExportAssignment = nil
		tx.cjsExportAssignmentName = nil
		tx.cjsExportMembers = nil
	}()
	tx.cjsExportAssignmentVisitor.VisitNode(node.AsNode()) // collect nested module.exports= assignments
	tx.expressionVisitor.VisitNode(node.AsNode())          // collect expando members (requires any export assignment be located in advance)
	var combinedStatements *ast.StatementList
	statements := tx.Visitor().VisitNodes(node.Statements)
	combinedStatements = tx.transformAndReplaceLatePaintedStatements(statements)
	combinedStatements = tx.appendCjsExports(combinedStatements)
	combinedStatements.Loc = statements.Loc // setTextRange
	if ast.IsExternalOrCommonJSModule(node) {
		if ast.IsInJSFile(node.AsNode()) {
			if exportEquals := node.Symbol.Exports[ast.InternalSymbolNameExportEquals]; exportEquals != nil && len(exportEquals.Declarations) > 1 {
				for _, node := range exportEquals.Declarations {
					tx.state.addDiagnostic(createDiagnosticForNode(node, diagnostics.Multiple_module_exports_assignments_cannot_be_serialized_for_declaration_emit))
				}
			}
		}
		if !tx.resultHasExternalModuleIndicator || (tx.needsScopeFixMarker && !tx.resultHasScopeMarker) {
			marker := createEmptyExports(tx.Factory().AsNodeFactory())
			newList := append(combinedStatements.Nodes, marker)
			withMarker := tx.Factory().NewNodeList(newList)
			withMarker.Loc = combinedStatements.Loc
			combinedStatements = withMarker
		}
	}
	outputFilePath := tspath.GetDirectoryPath(tspath.NormalizeSlashes(tx.declarationFilePath))
	result := tx.Factory().UpdateSourceFile(node, combinedStatements, node.EndOfFileToken)
	result.AsSourceFile().LibReferenceDirectives = tx.getLibReferences()
	result.AsSourceFile().TypeReferenceDirectives = tx.getTypeReferences()
	result.AsSourceFile().IsDeclarationFile = true
	result.AsSourceFile().ReferencedFiles = tx.getReferencedFiles(outputFilePath)
	return result.AsNode()
}

func createEmptyExports(factory *ast.NodeFactory) *ast.Node {
	return factory.NewExportDeclaration(nil /*isTypeOnly*/, false, factory.NewNamedExports(factory.NewNodeList([]*ast.Node{})), nil, nil)
}

func (tx *DeclarationTransformer) transformAndReplaceLatePaintedStatements(statements *ast.StatementList) *ast.StatementList {
	// This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during
	// error handling which must now be included in the output and themselves checked for errors.
	// For example:
	// ```
	// module A {
	//   export module Q {}
	//   import B = Q;
	//   import C = B;
	//   export import D = C;
	// }
	// ```
	// In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must
	// be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of
	// dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases.
	for true {
		if len(tx.state.lateMarkedStatements) == 0 {
			break
		}

		next := tx.state.lateMarkedStatements[0]
		tx.state.lateMarkedStatements = tx.state.lateMarkedStatements[1:]

		saveNeedsDeclare := tx.needsDeclare
		tx.needsDeclare = next.Parent != nil && ast.IsSourceFile(next.Parent)

		result := tx.transformTopLevelDeclaration(next)

		tx.needsDeclare = saveNeedsDeclare
		original := tx.EmitContext().MostOriginal(next)
		id := ast.GetNodeId(original)
		tx.lateStatementReplacementMap[id] = result
	}

	// And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list
	// (and remove them from the set to examine for outter declarations)
	results := make([]*ast.Node, 0, len(statements.Nodes))
	for _, statement := range statements.Nodes {
		if !ast.IsLateVisibilityPaintedStatement(statement) {
			results = append(results, statement)
			continue
		}
		original := tx.EmitContext().MostOriginal(statement)
		id := ast.GetNodeId(original)
		replacement, ok := tx.lateStatementReplacementMap[id]
		if !ok {
			results = append(results, statement)
			continue // not replaced
		}
		if replacement == nil {
			continue // deleted
		}
		if replacement.Kind == ast.KindSyntaxList {
			if !tx.needsScopeFixMarker || !tx.resultHasExternalModuleIndicator {
				for _, elem := range replacement.AsSyntaxList().Children {
					if needsScopeMarker(elem) {
						tx.needsScopeFixMarker = true
					}
					if ast.IsSourceFile(statement.Parent) && ast.IsExternalModuleIndicator(elem) {
						tx.resultHasExternalModuleIndicator = true
					}
				}
			}
			results = append(results, replacement.AsSyntaxList().Children...)
		} else {
			if needsScopeMarker(replacement) {
				tx.needsScopeFixMarker = true
			}
			if ast.IsSourceFile(statement.Parent) && ast.IsExternalModuleIndicator(replacement) {
				tx.resultHasExternalModuleIndicator = true
			}
			results = append(results, replacement)
		}
	}

	return tx.Factory().NewNodeList(results)
}

func (tx *DeclarationTransformer) getReferencedFiles(outputFilePath string) (results []*ast.FileReference) {
	// Handle path rewrites for triple slash ref comments
	for _, pair := range tx.rawReferencedFiles {
		sourceFile := pair.file
		ref := pair.ref

		if !ref.Preserve {
			continue
		}

		file := tx.host.GetSourceFileFromReference(sourceFile, ref)
		if file == nil {
			continue
		}

		var declFileName string
		if file.IsDeclarationFile {
			declFileName = file.FileName()
		} else {
			paths := tx.host.GetOutputPathsFor(file, true)
			// Try to use output path for referenced file, or output js path if that doesn't exist, or the input path if all else fails
			declFileName = paths.DeclarationFilePath()
			if len(declFileName) == 0 {
				declFileName = paths.JsFilePath()
			}
			if len(declFileName) == 0 {
				declFileName = file.FileName()
			}
		}
		// Should only be missing if the source file is missing a fileName (at which point we can't name a reference to it anyway)
		// TODO: Shouldn't this be a crash or assert instead of a silent continue?
		if len(declFileName) == 0 {
			continue
		}

		fileName := tspath.GetRelativePathToDirectoryOrUrl(
			outputFilePath,
			declFileName,
			false, // TODO: Probably unsafe to assume this isn't a URL, but that's what strada does
			tspath.ComparePathsOptions{
				CurrentDirectory:          tx.host.GetCurrentDirectory(),
				UseCaseSensitiveFileNames: tx.host.UseCaseSensitiveFileNames(),
			},
		)

		results = append(results, &ast.FileReference{
			TextRange:      core.NewTextRange(-1, -1),
			FileName:       fileName,
			ResolutionMode: ref.ResolutionMode,
			Preserve:       ref.Preserve,
		})
	}
	return results
}

func (tx *DeclarationTransformer) getLibReferences() (result []*ast.FileReference) {
	// clone retained references
	for _, ref := range tx.rawLibReferenceDirectives {
		if !ref.Preserve {
			continue
		}
		result = append(result, &ast.FileReference{
			TextRange:      core.NewTextRange(-1, -1),
			FileName:       ref.FileName,
			ResolutionMode: ref.ResolutionMode,
			Preserve:       ref.Preserve,
		})
	}
	return result
}

func (tx *DeclarationTransformer) getTypeReferences() (result []*ast.FileReference) {
	// clone retained references
	for _, ref := range tx.rawTypeReferenceDirectives {
		if !ref.Preserve {
			continue
		}
		result = append(result, &ast.FileReference{
			TextRange:      core.NewTextRange(-1, -1),
			FileName:       ref.FileName,
			ResolutionMode: ref.ResolutionMode,
			Preserve:       ref.Preserve,
		})
	}
	return result
}

func (tx *DeclarationTransformer) setupDiagnosticContext(input *ast.Node) (bool, func()) {
	canProdiceDiagnostic := canProduceDiagnostics(input)
	oldWithinObjectLiteralType := tx.suppressNewDiagnosticContexts
	shouldEnterSuppressNewDiagnosticsContextContext := (input.Kind == ast.KindTypeLiteral || input.Kind == ast.KindMappedType) && !(input.Parent.Kind == ast.KindTypeAliasDeclaration || input.Parent.Kind == ast.KindJSTypeAliasDeclaration)

	oldDiag := tx.state.getSymbolAccessibilityDiagnostic
	if canProdiceDiagnostic && !tx.suppressNewDiagnosticContexts {
		tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input)
	}
	oldName := tx.state.errorNameNode

	if shouldEnterSuppressNewDiagnosticsContextContext {
		tx.suppressNewDiagnosticContexts = true
	}

	return canProdiceDiagnostic, func() {
		tx.state.getSymbolAccessibilityDiagnostic = oldDiag
		tx.state.errorNameNode = oldName
		tx.suppressNewDiagnosticContexts = oldWithinObjectLiteralType
	}
}

func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast.Node {
	if tx.shouldStripInternal(input) {
		return nil
	}
	if ast.IsDeclaration(input) {
		if isDeclarationAndNotVisible(tx.EmitContext(), tx.resolver, input) {
			return nil
		}
		if ast.HasDynamicName(input) {
			if tx.state.isolatedDeclarations {
				// Classes and object literals usually elide properties with computed names that are not of a literal type
				// In isolated declarations TSC needs to error on these as we don't know the type in a DTE.
				if !tx.resolver.IsDefinitelyReferenceToGlobalSymbolObject(input.Name().Expression()) {
					if ast.IsClassDeclaration(input.Parent) || ast.IsObjectLiteralExpression(input.Parent) {
						tx.state.addDiagnostic(createDiagnosticForNode(input, diagnostics.Computed_property_names_on_class_or_object_literals_cannot_be_inferred_with_isolatedDeclarations))
						return nil
					} else if (ast.IsInterfaceDeclaration(input.Parent) || ast.IsTypeLiteralNode(input.Parent)) && !ast.IsEntityNameExpression(input.Name().Expression()) {
						// Type declarations just need to double-check that the input computed name is an entity name expression
						tx.state.addDiagnostic(createDiagnosticForNode(input, diagnostics.Computed_properties_must_be_number_or_string_literals_variables_or_dotted_expressions_with_isolatedDeclarations))
						return nil
					}
				}
			} else if !tx.resolver.IsLateBound(tx.EmitContext().ParseNode(input)) || !ast.IsEntityNameExpression(input.Name().Expression()) {
				return nil
			}
		}
	}

	// Elide implementation signatures from overload sets
	if ast.IsFunctionLike(input) && tx.resolver.IsImplementationOfOverload(input) {
		return nil
	}

	if input.Kind == ast.KindSemicolonClassElement {
		return nil
	}

	if ast.IsHeritageClause(input) && (len(input.AsHeritageClause().Types.Nodes) == 0 || (len(input.AsHeritageClause().Types.Nodes) == 1 && ast.NodeIsMissing(input.AsHeritageClause().Types.Nodes[0]))) {
		return nil
	}

	previousEnclosingDeclaration := tx.enclosingDeclaration
	if isEnclosingDeclaration(input) {
		tx.enclosingDeclaration = input
	}

	canProdiceDiagnostic, cleanupDiagnosticContext := tx.setupDiagnosticContext(input)
	defer cleanupDiagnosticContext()

	var result *ast.Node

	switch input.Kind {
	case ast.KindMappedType:
		result = tx.transformMappedTypeNode(input.AsMappedTypeNode())
	case ast.KindHeritageClause:
		result = tx.transformHeritageClause(input.AsHeritageClause())
	case ast.KindMethodSignature:
		result = tx.transformMethodSignatureDeclaration(input.AsMethodSignatureDeclaration())
	case ast.KindMethodDeclaration:
		result = tx.transformMethodDeclaration(input.AsMethodDeclaration())
	case ast.KindConstructSignature:
		result = tx.transformConstructSignatureDeclaration(input.AsConstructSignatureDeclaration())
	case ast.KindConstructor:
		result = tx.transformConstructorDeclaration(input.AsConstructorDeclaration())
	case ast.KindGetAccessor:
		result = tx.transformGetAccesorDeclaration(input.AsGetAccessorDeclaration())
	case ast.KindSetAccessor:
		result = tx.transformSetAccessorDeclaration(input.AsSetAccessorDeclaration())
	case ast.KindPropertyDeclaration:
		result = tx.transformPropertyDeclaration(input.AsPropertyDeclaration())
	case ast.KindPropertySignature:
		result = tx.transformPropertySignatureDeclaration(input.AsPropertySignatureDeclaration())
	case ast.KindCallSignature:
		result = tx.transformCallSignatureDeclaration(input.AsCallSignatureDeclaration())
	case ast.KindIndexSignature:
		result = tx.transformIndexSignatureDeclaration(input.AsIndexSignatureDeclaration())
	case ast.KindVariableDeclaration:
		result = tx.transformVariableDeclaration(input.AsVariableDeclaration())
	case ast.KindTypeParameter:
		result = tx.transformTypeParameterDeclaration(input.AsTypeParameterDeclaration())
	case ast.KindExpressionWithTypeArguments:
		result = tx.transformExpressionWithTypeArguments(input.AsExpressionWithTypeArguments())
	case ast.KindTypeReference:
		result = tx.transformTypeReference(input.AsTypeReferenceNode())
	case ast.KindConditionalType:
		result = tx.transformConditionalTypeNode(input.AsConditionalTypeNode())
	case ast.KindFunctionType:
		result = tx.transformFunctionTypeNode(input.AsFunctionTypeNode())
	case ast.KindConstructorType:
		result = tx.transformConstructorTypeNode(input.AsConstructorTypeNode())
	case ast.KindImportType:
		result = tx.transformImportTypeNode(input.AsImportTypeNode())
	case ast.KindTypeQuery:
		tx.checkEntityNameVisibility(input.AsTypeQueryNode().ExprName, tx.enclosingDeclaration)
		result = tx.Visitor().VisitEachChild(input)
	case ast.KindTupleType:
		result = tx.Visitor().VisitEachChild(input)
		if result != nil {
			if transformers.IsOriginalNodeSingleLine(tx.EmitContext(), input) {
				tx.EmitContext().AddEmitFlags(result, printer.EFSingleLine)
			}
		}
	case ast.KindJSDocTypeExpression:
		result = tx.transformJSDocTypeExpression(input.AsJSDocTypeExpression())
	case ast.KindJSDocTypeLiteral:
		result = tx.transformJSDocTypeLiteral(input.AsJSDocTypeLiteral())
	case ast.KindJSDocPropertyTag:
		result = tx.transformJSDocPropertyTag(input.AsJSDocParameterOrPropertyTag())
	case ast.KindJSDocAllType:
		result = tx.transformJSDocAllType(input.AsJSDocAllType())
	case ast.KindJSDocNullableType:
		result = tx.transformJSDocNullableType(input.AsJSDocNullableType())
	case ast.KindJSDocNonNullableType:
		result = tx.transformJSDocNonNullableType(input.AsJSDocNonNullableType())
	case ast.KindJSDocOptionalType:
		result = tx.transformJSDocOptionalType(input.AsJSDocOptionalType())
	case ast.KindJSDocVariadicType:
		result = tx.transformJSDocVariadicType(input.AsJSDocVariadicType())
	default:
		result = tx.Visitor().VisitEachChild(input)
	}

	if result != nil && canProdiceDiagnostic && ast.HasDynamicName(input) {
		tx.checkName(input)
	}

	tx.enclosingDeclaration = previousEnclosingDeclaration
	return result
}

func (tx *DeclarationTransformer) checkName(node *ast.Node) {
	oldDiag := tx.state.getSymbolAccessibilityDiagnostic
	if !tx.suppressNewDiagnosticContexts {
		tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node)
	}
	tx.state.errorNameNode = node.Name()
	debug.Assert(ast.HasDynamicName(node)) // Should only be called with dynamic names
	entityName := node.Name().Expression()
	tx.checkEntityNameVisibility(entityName, tx.enclosingDeclaration)
	if !tx.suppressNewDiagnosticContexts {
		tx.state.getSymbolAccessibilityDiagnostic = oldDiag
	}
	tx.state.errorNameNode = nil
}

func (tx *DeclarationTransformer) transformMappedTypeNode(input *ast.MappedTypeNode) *ast.Node {
	// handle missing template type nodes, since the printer does not
	var typeNode *ast.Node
	if input.Type == nil {
		typeNode = tx.Factory().NewKeywordTypeNode(ast.KindAnyKeyword)
	} else {
		typeNode = tx.Visitor().Visit(input.Type)
	}
	return tx.Factory().UpdateMappedTypeNode(
		input,
		input.ReadonlyToken,
		tx.Visitor().Visit(input.TypeParameter),
		tx.Visitor().Visit(input.NameType),
		input.QuestionToken,
		typeNode,
		nil,
	)
}

func (tx *DeclarationTransformer) transformHeritageClause(clause *ast.HeritageClause) *ast.Node {
	retainedClauses := core.Filter(clause.Types.Nodes, func(t *ast.Node) bool {
		return ast.IsEntityNameExpression(t.AsExpressionWithTypeArguments().Expression) ||
			(clause.Token == ast.KindExtendsKeyword && t.Expression().Kind == ast.KindNullKeyword)
	})
	if len(retainedClauses) == 0 {
		return nil // elide empty clause
	}
	if len(retainedClauses) == len(clause.Types.Nodes) {
		return tx.Visitor().VisitEachChild(clause.AsNode())
	}
	return tx.Factory().UpdateHeritageClause(
		clause,
		clause.Token,
		tx.Visitor().VisitNodes(tx.Factory().NewNodeList(retainedClauses)),
	)
}

func (tx *DeclarationTransformer) transformImportTypeNode(input *ast.ImportTypeNode) *ast.Node {
	if !ast.IsLiteralImportTypeNode(input.AsNode()) {
		return input.AsNode()
	}
	return tx.Factory().UpdateImportTypeNode(
		input,
		input.IsTypeOf,
		tx.Factory().UpdateLiteralTypeNode(
			input.Argument.AsLiteralTypeNode(),
			tx.rewriteModuleSpecifier(input.AsNode(), input.Argument.AsLiteralTypeNode().Literal),
		),
		input.Attributes,
		input.Qualifier,
		tx.Visitor().VisitNodes(input.TypeArguments),
	)
}

func (tx *DeclarationTransformer) transformConstructorTypeNode(input *ast.ConstructorTypeNode) *ast.Node {
	return tx.Factory().UpdateConstructorTypeNode(
		input,
		tx.ensureModifiers(input.AsNode()),
		tx.Visitor().VisitNodes(input.TypeParameters),
		tx.updateParamList(input.AsNode(), input.Parameters),
		tx.Visitor().Visit(input.Type),
	)
}

func (tx *DeclarationTransformer) transformFunctionTypeNode(input *ast.FunctionTypeNode) *ast.Node {
	return tx.Factory().UpdateFunctionTypeNode(
		input,
		tx.Visitor().VisitNodes(input.TypeParameters),
		tx.updateParamList(input.AsNode(), input.Parameters),
		tx.Visitor().Visit(input.Type),
	)
}

func (tx *DeclarationTransformer) transformConditionalTypeNode(input *ast.ConditionalTypeNode) *ast.Node {
	checkType := tx.Visitor().Visit(input.CheckType)
	extendsType := tx.Visitor().Visit(input.ExtendsType)
	oldEnclosingDecl := tx.enclosingDeclaration
	tx.enclosingDeclaration = input.TrueType
	trueType := tx.Visitor().Visit(input.TrueType)
	tx.enclosingDeclaration = oldEnclosingDecl
	falseType := tx.Visitor().Visit(input.FalseType)

	return tx.Factory().UpdateConditionalTypeNode(
		input,
		checkType,
		extendsType,
		trueType,
		falseType,
	)
}

func (tx *DeclarationTransformer) transformTypeReference(input *ast.TypeReferenceNode) *ast.Node {
	tx.checkEntityNameVisibility(input.TypeName, tx.enclosingDeclaration)
	return tx.Visitor().VisitEachChild(input.AsNode())
}

func (tx *DeclarationTransformer) transformExpressionWithTypeArguments(input *ast.ExpressionWithTypeArguments) *ast.Node {
	if ast.IsEntityName(input.Expression) || ast.IsEntityNameExpression(input.Expression) {
		tx.checkEntityNameVisibility(input.Expression, tx.enclosingDeclaration)
	}
	return tx.Visitor().VisitEachChild(input.AsNode())
}

func (tx *DeclarationTransformer) transformTypeParameterDeclaration(input *ast.TypeParameterDeclaration) *ast.Node {
	if isPrivateMethodTypeParameter(tx.host, input) && (input.DefaultType != nil || input.Constraint != nil) {
		return tx.Factory().UpdateTypeParameterDeclaration(
			input,
			input.Modifiers(),
			input.Name(),
			nil,
			input.Expression,
			nil,
		)
	}
	return tx.Visitor().VisitEachChild(input.AsNode())
}

func (tx *DeclarationTransformer) transformVariableDeclaration(input *ast.VariableDeclaration) *ast.Node {
	if tx.state.currentSourceFile.CommonJSModuleIndicator != nil && ast.IsVariableDeclarationInitializedToRequire(input.AsNode()) {
		return tx.transformCjsRequireVariableDeclaration(input)
	}
	if ast.IsBindingPattern(input.Name()) {
		return tx.recreateBindingPattern(input.Name().AsBindingPattern())
	}
	// Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types
	tx.suppressNewDiagnosticContexts = true
	return tx.Factory().UpdateVariableDeclaration(
		input,
		input.Name(),
		nil,
		tx.ensureType(input.AsNode(), false),
		tx.ensureNoInitializer(input.AsNode()),
	)
}

func (tx *DeclarationTransformer) transformCjsRequireVariableDeclaration(input *ast.VariableDeclaration) *ast.Node {
	specifier := tx.rewriteModuleSpecifier(input.AsNode(), input.Initializer.AsCallExpression().Arguments.Nodes[0])
	if ast.IsIdentifier(input.Name()) {
		// `const x = require("something")` -> `import x = require("something")`
		return tx.Factory().NewImportEqualsDeclaration(nil, false, input.Name(), tx.Factory().NewExternalModuleReference(specifier))
	} else if ast.IsArrayBindingPattern(input.Name()) {
		// TODO: Is this actually reachable? should we error on this?
		return nil
	} else { // object binding pattern

		// `const {x, y: z} = require("something")` -> `import {x, y as z} from "something"`
		b := input.Name().AsBindingPattern()
		var importSpecifiers []*ast.Node
		for _, elem := range b.Elements.Nodes {
			if !ast.IsIdentifier(elem.Name()) {
				continue // nested destructuring, bail
			}
			importSpecifiers = append(importSpecifiers, tx.Factory().NewImportSpecifier(false, elem.PropertyName(), elem.Name()))
		}
		return tx.Factory().NewImportDeclaration(
			nil,
			tx.Factory().NewImportClause(
				ast.KindUnknown,
				nil,
				tx.Factory().NewNamedImports(tx.Factory().NewNodeList(importSpecifiers)),
			),
			specifier,
			nil,
		)
	}
}

func (tx *DeclarationTransformer) recreateBindingPattern(input *ast.BindingPattern) *ast.Node {
	var results []*ast.Node
	for _, elem := range input.Elements.Nodes {
		result := tx.recreateBindingElement(elem.AsBindingElement())
		if result == nil {
			continue
		}
		if result.Kind == ast.KindSyntaxList {
			results = append(results, result.AsSyntaxList().Children...)
		} else {
			results = append(results, result)
		}
	}
	if len(results) == 0 {
		return nil
	}
	if len(results) == 1 {
		return results[0]
	}
	return tx.Factory().NewSyntaxList(results)
}

func (tx *DeclarationTransformer) recreateBindingElement(e *ast.BindingElement) *ast.Node {
	if e.Name() == nil {
		return nil
	}
	if !getBindingNameVisible(tx.resolver, e.AsNode()) {
		return nil
	}
	if ast.IsBindingPattern(e.Name()) {
		return tx.recreateBindingPattern(e.Name().AsBindingPattern())
	}
	return tx.Factory().NewVariableDeclaration(
		e.Name(),
		nil,
		tx.ensureType(e.AsNode(), false),
		nil, // TODO: possible strada bug - not emitting const initialized binding pattern elements?
	)
}

func (tx *DeclarationTransformer) transformIndexSignatureDeclaration(input *ast.IndexSignatureDeclaration) *ast.Node {
	t := tx.Visitor().Visit(input.Type)
	if t == nil {
		t = tx.Factory().NewKeywordTypeNode(ast.KindAnyKeyword)
	}
	return tx.Factory().UpdateIndexSignatureDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		tx.updateParamList(input.AsNode(), input.Parameters),
		t,
	)
}

func (tx *DeclarationTransformer) transformCallSignatureDeclaration(input *ast.CallSignatureDeclaration) *ast.Node {
	return tx.Factory().UpdateCallSignatureDeclaration(
		input,
		tx.ensureTypeParams(input.AsNode(), input.TypeParameters),
		tx.updateParamList(input.AsNode(), input.Parameters),
		tx.ensureType(input.AsNode(), false),
	)
}

func (tx *DeclarationTransformer) transformPropertySignatureDeclaration(input *ast.PropertySignatureDeclaration) *ast.Node {
	if ast.IsPrivateIdentifier(input.Name()) {
		return nil
	}
	result := tx.Factory().UpdatePropertySignatureDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		input.PostfixToken,
		tx.ensureType(input.AsNode(), false),
		tx.ensureNoInitializer(input.AsNode()), // TODO: possible strada bug (fixed here) - const property signatures never initialized
	)
	tx.preservePartialJsDoc(result, input.AsNode())
	return result
}

func (tx *DeclarationTransformer) transformPropertyDeclaration(input *ast.PropertyDeclaration) *ast.Node {
	if ast.IsPrivateIdentifier(input.Name()) {
		return nil
	}
	// Remove definite assignment assertion (!) from declaration files
	postfixToken := input.PostfixToken
	if postfixToken != nil && postfixToken.Kind == ast.KindExclamationToken {
		postfixToken = nil
	}
	return tx.Factory().UpdatePropertyDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		postfixToken,
		tx.ensureType(input.AsNode(), false),
		tx.ensureNoInitializer(input.AsNode()),
	)
}

func (tx *DeclarationTransformer) transformSetAccessorDeclaration(input *ast.SetAccessorDeclaration) *ast.Node {
	if ast.IsPrivateIdentifier(input.Name()) {
		return nil
	}

	return tx.Factory().UpdateSetAccessorDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		nil, // accessors shouldn't have type params
		tx.updateAccessorParamList(input.AsNode(), tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0),
		nil,
		nil,
		nil,
	)
}

func (tx *DeclarationTransformer) transformGetAccesorDeclaration(input *ast.GetAccessorDeclaration) *ast.Node {
	if ast.IsPrivateIdentifier(input.Name()) {
		return nil
	}
	return tx.Factory().UpdateGetAccessorDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		nil, // accessors shouldn't have type params
		tx.updateAccessorParamList(input.AsNode(), tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0),
		tx.ensureType(input.AsNode(), false),
		nil,
		nil,
	)
}

func (tx *DeclarationTransformer) updateAccessorParamList(input *ast.Node, isPrivate bool) *ast.ParameterList {
	var newParams []*ast.Node
	if !isPrivate {
		thisParam := ast.GetThisParameter(input)
		if thisParam != nil {
			newParams = append(newParams, tx.ensureParameter(thisParam.AsParameterDeclaration()))
		}
	}
	if ast.IsSetAccessorDeclaration(input) {
		var valueParam *ast.Node
		if !isPrivate {
			if len(newParams) == 1 && len(input.AsSetAccessorDeclaration().Parameters.Nodes) >= 2 {
				valueParam = tx.ensureParameter(input.AsSetAccessorDeclaration().Parameters.Nodes[1].AsParameterDeclaration())
			} else if len(newParams) == 0 && len(input.AsSetAccessorDeclaration().Parameters.Nodes) >= 1 {
				valueParam = tx.ensureParameter(input.AsSetAccessorDeclaration().Parameters.Nodes[0].AsParameterDeclaration())
			}
		}
		if valueParam == nil {
			// When synthesizing a missing value parameter, emit `value: any` for non-private accessors to match TypeScript's declaration emit behavior.
			var t *ast.Node
			if !isPrivate {
				t = tx.Factory().NewKeywordTypeNode(ast.KindAnyKeyword)
			}
			valueParam = tx.Factory().NewParameterDeclaration(
				nil,
				nil,
				tx.Factory().NewIdentifier("value"),
				nil,
				t,
				nil,
			)
		}
		newParams = append(newParams, valueParam)
	}
	return tx.Factory().NewNodeList(newParams)
}

func (tx *DeclarationTransformer) transformConstructorDeclaration(input *ast.ConstructorDeclaration) *ast.Node {
	// A constructor declaration may not have a type annotation
	return tx.Factory().UpdateConstructorDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		nil, // no type params
		tx.updateParamList(input.AsNode(), input.Parameters),
		nil, // no return type
		nil,
		nil,
	)
}

func (tx *DeclarationTransformer) transformConstructSignatureDeclaration(input *ast.ConstructSignatureDeclaration) *ast.Node {
	return tx.Factory().UpdateConstructSignatureDeclaration(
		input,
		tx.ensureTypeParams(input.AsNode(), input.TypeParameters),
		tx.updateParamList(input.AsNode(), input.Parameters),
		tx.ensureType(input.AsNode(), false),
	)
}

func (tx *DeclarationTransformer) omitPrivateMethodType(input *ast.Node) *ast.Node {
	if input.Symbol() != nil && len(input.Symbol().Declarations) > 0 && input.Symbol().Declarations[0] != input {
		return nil
	}
	result := tx.Factory().NewPropertyDeclaration(
		tx.ensureModifiers(input),
		input.Name(),
		nil,
		nil,
		nil,
	)
	tx.preserveJsDoc(result, input)
	return result
}

func (tx *DeclarationTransformer) transformMethodSignatureDeclaration(input *ast.MethodSignatureDeclaration) *ast.Node {
	if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0 {
		return tx.omitPrivateMethodType(input.AsNode())
	} else if ast.IsPrivateIdentifier(input.Name()) {
		return nil
	} else {
		return tx.Factory().UpdateMethodSignatureDeclaration(
			input,
			tx.ensureModifiers(input.AsNode()),
			input.Name(),
			input.PostfixToken,
			tx.ensureTypeParams(input.AsNode(), input.TypeParameters),
			tx.updateParamList(input.AsNode(), input.Parameters),
			tx.ensureType(input.AsNode(), false),
		)
	}
}

func (tx *DeclarationTransformer) transformMethodDeclaration(input *ast.MethodDeclaration) *ast.Node {
	if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(input.AsNode()), ast.ModifierFlagsPrivate) != 0 {
		return tx.omitPrivateMethodType(input.AsNode())
	} else if ast.IsPrivateIdentifier(input.Name()) {
		return nil
	} else {
		return tx.Factory().UpdateMethodDeclaration(
			input,
			tx.ensureModifiers(input.AsNode()),
			nil,
			input.Name(),
			input.PostfixToken,
			tx.ensureTypeParams(input.AsNode(), input.TypeParameters),
			tx.updateParamList(input.AsNode(), input.Parameters),
			tx.ensureType(input.AsNode(), false),
			nil,
			nil,
		)
	}
}

func (tx *DeclarationTransformer) visitDeclarationStatements(input *ast.Node) *ast.Node {
	if tx.shouldStripInternal(input) {
		return nil
	}
	switch input.Kind {
	case ast.KindExportDeclaration:
		if ast.IsSourceFile(input.Parent) {
			tx.resultHasExternalModuleIndicator = true
		}
		tx.resultHasScopeMarker = true
		// Rewrite external module names if necessary
		return tx.Factory().UpdateExportDeclaration(
			input.AsExportDeclaration(),
			input.Modifiers(),
			input.IsTypeOnly(),
			input.AsExportDeclaration().ExportClause,
			tx.rewriteModuleSpecifier(input, input.ModuleSpecifier()),
			tx.tryGetResolutionModeOverride(input.AsExportDeclaration().Attributes),
		)
	case ast.KindExportAssignment:
		return tx.transformExportAssignment(input, input, input.Expression(), input.AsExportAssignment().IsExportEquals)
	default:
		id := ast.GetNodeId(tx.EmitContext().MostOriginal(input))
		if tx.lateStatementReplacementMap[id] == nil {
			// Don't actually transform yet; just leave as original node - will be elided/swapped by late pass
			tx.lateStatementReplacementMap[id] = tx.transformTopLevelDeclaration(input)
		}
		return input
	}
}

func (tx *DeclarationTransformer) tryGetNameOfAssignedExpression(unwrapped *ast.Node) *ast.Node {
	var nameNode *ast.Node
	var nameText string
	if !ast.IsPropertyAccessExpression(unwrapped) && unwrapped.Name() != nil {
		nameText = unwrapped.Name().Text()
	} else if ast.IsIdentifier(unwrapped) {
		nameText = unwrapped.Text()
	}
	if nameText != "" && nameText != "default" {
		if tx.resolver.IsNameResolvable(tx.enclosingDeclaration, nameText) {
			// create a unique name that shares the same text as its' base
			nameNode = tx.Factory().NewUniqueNameEx(nameText, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
		} else {
			// use the node's name as-is, since it's not otherwise in-scope
			nameNode = tx.Factory().NewIdentifier(nameText)
		}
	}
	return nameNode
}

func (tx *DeclarationTransformer) getNameOfExportedAssignedExpression(unwrapped *ast.Node, isExportEquals bool) *ast.Node {
	nameNode := tx.tryGetNameOfAssignedExpression(unwrapped)
	if nameNode == nil {
		// fallback to a default name
		if isExportEquals && ast.IsSourceFileJS(tx.state.currentSourceFile) {
			// only JS files prefer to use `_exports` for export assignments - TS has always used `_default` for both `export=` and `export default`
			nameNode = tx.Factory().NewUniqueNameEx("_exports", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
		} else {
			nameNode = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
		}
	}
	tx.cjsExportAssignmentName = nameNode
	return nameNode
}

func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, assignment *ast.Node, expression *ast.Node, isExportEquals bool) *ast.Node {
	if ast.IsSourceFile(input.Parent) {
		tx.resultHasExternalModuleIndicator = true
	}
	tx.resultHasScopeMarker = true
	if ast.IsIdentifier(expression) && (ast.IsSourceFile(input.Parent) || ast.IsModuleBlock(input.Parent)) {
		exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, expression)
		tx.preserveJsDoc(exportAssignment, input)
		return exportAssignment
	}

	// Check if the expression is a class expression - emit as a class declaration + export assignment
	unwrapped := ast.SkipOuterExpressions(expression, ast.OEKExpressionTypePassthrough)
	newId := tx.getNameOfExportedAssignedExpression(unwrapped, isExportEquals)
	if ast.IsClassExpression(unwrapped) {
		var mods []*ast.Node
		if tx.needsDeclare {
			mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword))
		}
		classDecl := tx.transformClassExpressionToDeclaration(unwrapped, newId, tx.Factory().NewModifierList(mods))
		tx.preserveJsDoc(classDecl, input)
		// Reuse the same name node for the export so unique names resolve consistently
		exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, newId)
		tx.removeAllComments(exportAssignment)
		return tx.Factory().NewSyntaxList([]*ast.Node{exportAssignment, classDecl})
	} else if ast.IsFunctionLike(unwrapped) {
		// Promote function or arrow function expressions to a function declaration
		var mods []*ast.Node
		if tx.needsDeclare {
			mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword))
		}
		funcDecl := tx.transformFunctionLikeToDeclaration(unwrapped, newId, tx.Factory().NewModifierList(mods))
		tx.preserveJsDoc(funcDecl, input)
		// Reuse the same name node for the export so unique names resolve consistently
		exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, newId)
		tx.removeAllComments(exportAssignment)
		return tx.Factory().NewSyntaxList([]*ast.Node{exportAssignment, funcDecl})
	}

	// expression is non-identifier, create _default typed variable to reference
	tx.state.getSymbolAccessibilityDiagnostic = func(_ printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic {
		return &SymbolAccessibilityDiagnostic{
			diagnosticMessage: diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0,
			errorNode:         input,
		}
	}
	tx.cjsExportAssignmentName = newId
	tx.tracker.PushErrorFallbackNode(assignment)
	var type_, initializer *ast.Node
	if ast.IsPrimitiveLiteralValue(unwrapParenthesizedExpression(expression), true) {
		initializer = tx.resolver.CreateLiteralConstValue(tx.EmitContext(), tx.EmitContext().ParseNode(assignment), tx.tracker)
	}
	if initializer == nil {
		type_ = tx.ensureType(assignment, false)
	}
	varDecl := tx.Factory().NewVariableDeclaration(newId, nil, type_, initializer)
	tx.tracker.PopErrorFallbackNode()
	var modList *ast.ModifierList
	if tx.needsDeclare {
		modList = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindDeclareKeyword)})
	} else {
		modList = tx.Factory().NewModifierList([]*ast.Node{})
	}
	statement := tx.Factory().NewVariableStatement(modList, tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), ast.NodeFlagsConst))
	exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, newId)
	// Remove comments from the export declaration and copy them onto the synthetic _default declaration
	tx.preserveJsDoc(statement, input)
	return tx.Factory().NewSyntaxList([]*ast.Node{statement, exportAssignment})
}

func (tx *DeclarationTransformer) transformFunctionLikeToDeclaration(unwrapped *ast.Node, funcName *ast.Node, mods *ast.ModifierList) *ast.Node {
	d := unwrapped.FunctionLikeData()
	return tx.Factory().NewFunctionDeclaration(
		mods,
		nil,
		funcName,
		tx.ensureTypeParams(unwrapped, d.TypeParameters),
		tx.updateParamList(unwrapped, d.Parameters),
		tx.ensureType(unwrapped, false),
		tx.Visitor().VisitNode(d.FullSignature),
		nil,
	)
}

func (tx *DeclarationTransformer) transformBinaryExpressionToExportDeclaration(input *ast.Node, name *ast.Node) *ast.Node {
	propertyName := input.AsBinaryExpression().Right

	// track alias target so referenced declarations are included in the output
	tx.tracker.handleSymbolAccessibilityError(tx.resolver.IsEntityNameVisible(propertyName, tx.enclosingDeclaration))

	if ast.IsIdentifier(name) && propertyName.Text() == name.Text() {
		propertyName = nil
	}

	return tx.Factory().NewExportDeclaration(
		nil,
		false,
		tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{tx.Factory().NewExportSpecifier(false, propertyName, name)})),
		nil,
		nil,
	)
}

func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name *ast.Node) *ast.Node {
	res := tx.transformCommonJSExportWorker(input, name)
	if res == nil {
		return res
	}
	return tx.wrapInCJSExportNamespace(res)
}

func (tx *DeclarationTransformer) transformCommonJSExportWorker(input *ast.Node, name *ast.Node) *ast.Node {
	var nameText string
	if ast.IsIdentifier(name) || ast.IsStringLiteral(name) {
		nameText = name.Text()
	}
	if tx.witnessedCjsExports.Has(nameText) && nameText != "" {
		return nil // Already emitted this export name
	}
	tx.witnessedCjsExports.Add(nameText)
	tx.resultHasExternalModuleIndicator = true
	tx.resultHasScopeMarker = true
	// only transform cjs exports to shorthand at the top-level of a source file, otherwise we uniformly emit nested exports with a type annotation
	if isCommonJSAliasExport(input) && ast.IsExpressionStatement(input.Parent) && ast.IsSourceFile(input.Parent.Parent) {
		// export { name }
		// export { source as name }
		return tx.transformBinaryExpressionToExportDeclaration(input, name)
	}

	// Check if the RHS is a class expression - emit as a class declaration instead of a typed variable
	if ast.IsBinaryExpression(input) {
		if rhs := unwrapParenthesizedExpression(input.AsBinaryExpression().Right); ast.IsClassExpression(rhs) {
			ce := rhs.AsClassExpression()
			classExprName := ce.Name()
			hasExprName := classExprName != nil && len(classExprName.Text()) > 0

			if hasExprName {
				// Set up TrackSymbol watch to detect if the class expression's own
				// symbol is referenced during member type serialization.
				tx.tracker.watchedClassSymbol = rhs.Symbol()
				tx.tracker.classSymbolTracked = false
				defer func() {
					tx.tracker.watchedClassSymbol = nil
					tx.tracker.classSymbolTracked = false
				}()

				// Serialize class members using the class expression name, which
				// triggers TrackSymbol for any self-referential member types.
				className := tx.Factory().NewIdentifier(classExprName.Text())
				classMods := []*ast.Node{tx.Factory().NewModifier(ast.KindExportKeyword)}
				classDecl := tx.transformClassExpressionToDeclaration(rhs, className, tx.Factory().NewModifierList(classMods))
				tx.preserveJsDoc(classDecl, input)

				// Determine if namespace isolation is needed:
				// - The class expression name differs from the export name, OR
				// - The class's own symbol was used in a member's serialized type
				namesDiffer := !ast.IsIdentifier(name) || classExprName.Text() != name.Text()
				needsIsolation := namesDiffer || tx.tracker.classSymbolTracked

				if needsIsolation {
					nsName := tx.Factory().NewUniqueNameEx("_ns", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
					var nsMods []*ast.Node
					if tx.needsDeclare {
						nsMods = append(nsMods, tx.Factory().NewModifier(ast.KindDeclareKeyword))
					}
					nsDecl := tx.Factory().NewModuleDeclaration(
						tx.Factory().NewModifierList(nsMods),
						ast.KindNamespaceKeyword,
						nsName,
						tx.Factory().NewModuleBlock(tx.Factory().NewNodeList([]*ast.Node{classDecl})),
					)

					aliasBase := "_exported"
					if nameText := name.Text(); ast.IsIdentifier(name) && scanner.IsIdentifierText("_"+nameText, core.LanguageVariantStandard) {
						aliasBase = "_" + nameText
					}
					importAlias := tx.Factory().NewUniqueNameEx(aliasBase, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
					qualifiedName := tx.Factory().NewQualifiedName(nsName, className)
					importDecl := tx.Factory().NewImportEqualsDeclaration(nil, false, importAlias, qualifiedName)

					exportSpecifier := tx.Factory().NewExportSpecifier(false, importAlias, name)
					exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{exportSpecifier})), nil, nil)
					tx.removeAllComments(exportDecl)

					return tx.Factory().NewSyntaxList(append([]*ast.Node{nsDecl, importDecl}, exportDecl))
				}

				// No isolation needed: names match and no self-references.
				// Update modifiers to include declare if needed.
				var mods []*ast.Node
				mods = append(mods, tx.Factory().NewModifier(ast.KindExportKeyword))
				if tx.needsDeclare {
					mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword))
				}
				classDecl = tx.Factory().UpdateClassDeclaration(
					classDecl.AsClassDeclaration(),
					tx.Factory().NewModifierList(mods),
					classDecl.AsClassDeclaration().Name(),
					classDecl.AsClassDeclaration().TypeParameters,
					classDecl.AsClassDeclaration().HeritageClauses,
					classDecl.AsClassDeclaration().Members,
				)
				return classDecl
			}
			var mods []*ast.Node
			mods = append(mods, tx.Factory().NewModifier(ast.KindExportKeyword))
			if tx.needsDeclare {
				mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword))
			}
			className := name
			if !ast.IsIdentifier(className) {
				className = tx.Factory().NewUniqueNameEx("_class", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
			}
			classDecl := tx.transformClassExpressionToDeclaration(rhs, className, tx.Factory().NewModifierList(mods))
			tx.preserveJsDoc(classDecl, input)
			if !ast.IsIdentifier(name) {
				// Non-identifier name: emit class declaration + named export
				exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{tx.Factory().NewExportSpecifier(false, className, name)})), nil, nil)
				tx.removeAllComments(exportDecl)
				return tx.Factory().NewSyntaxList([]*ast.Node{classDecl, exportDecl})
			}
			return classDecl
		}
	}

	if ast.IsIdentifier(name) {
		if name.Text() == "default" {
			// const _default: Type; export default _default;
			newId := tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
			tx.state.getSymbolAccessibilityDiagnostic = func(_ printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic {
				return &SymbolAccessibilityDiagnostic{
					diagnosticMessage: diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0,
					errorNode:         input,
				}
			}
			tx.tracker.PushErrorFallbackNode(input)
			type_ := tx.ensureType(input, false)
			varDecl := tx.Factory().NewVariableDeclaration(newId, nil, type_, nil)
			tx.tracker.PopErrorFallbackNode()
			var modList *ast.ModifierList
			if tx.needsDeclare {
				modList = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindDeclareKeyword)})
			} else {
				modList = tx.Factory().NewModifierList([]*ast.Node{})
			}
			statement := tx.Factory().NewVariableStatement(modList, tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), ast.NodeFlagsConst))

			assignment := tx.Factory().NewExportAssignment(input.Modifiers(), false, nil, newId)
			// Remove comments from the export declaration and copy them onto the synthetic _default declaration
			tx.preserveJsDoc(statement, input)
			tx.removeAllComments(assignment)
			return tx.Factory().NewSyntaxList([]*ast.Node{statement, assignment})
		} else if tx.host.GetEmitResolver().GetReferencedValueDeclaration(name) == input || tx.host.GetEmitResolver().GetReferencedValueDeclaration(name) == nil {
			// only inline to a export var if the `name` lookup points at this assignment or nothing - if it points at something else, we must use a temp name
			// export var name: Type
			tx.tracker.PushErrorFallbackNode(input)
			type_ := tx.ensureType(input, false)
			varDecl := tx.Factory().NewVariableDeclaration(name, nil, type_, nil)
			tx.tracker.PopErrorFallbackNode()
			var modList *ast.ModifierList
			if tx.needsDeclare {
				modList = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindExportKeyword), tx.Factory().NewModifier(ast.KindDeclareKeyword)})
			} else {
				modList = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindExportKeyword)})
			}
			return tx.Factory().NewVariableStatement(modList, tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), ast.NodeFlagsNone))
		}
	}
	// const _exported: Type; export {_exported as "name"};
	newId := tx.Factory().NewUniqueNameEx("_exported", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
	tx.state.getSymbolAccessibilityDiagnostic = func(_ printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic {
		return &SymbolAccessibilityDiagnostic{
			diagnosticMessage: diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0,
			errorNode:         input,
		}
	}
	tx.tracker.PushErrorFallbackNode(input)
	type_ := tx.ensureType(input, false)
	varDecl := tx.Factory().NewVariableDeclaration(newId, nil, type_, nil)
	tx.tracker.PopErrorFallbackNode()
	var modList *ast.ModifierList
	if tx.needsDeclare {
		modList = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindDeclareKeyword)})
	} else {
		modList = tx.Factory().NewModifierList([]*ast.Node{})
	}
	statement := tx.Factory().NewVariableStatement(modList, tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), ast.NodeFlagsConst))

	assignment := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{tx.Factory().NewExportSpecifier(false, newId, name)})), nil, nil)
	// Remove comments from the export declaration and copy them onto the synthetic _default declaration
	tx.preserveJsDoc(statement, input)
	tx.removeAllComments(assignment)
	return tx.Factory().NewSyntaxList([]*ast.Node{statement, assignment})
}

func (tx *DeclarationTransformer) wrapInCJSExportNamespace(content *ast.Node) *ast.Node {
	if tx.cjsExportAssignmentName == nil {
		return content
	}
	// Reuse the same name node so unique names resolve consistently with the class/export
	nsName := tx.cjsExportAssignmentName
	var members []*ast.Node
	if content.Kind == ast.KindSyntaxList {
		members = content.AsSyntaxList().Children
	} else {
		members = []*ast.Node{content}
	}
	var nsMods []*ast.Node
	if tx.needsDeclare {
		nsMods = append(nsMods, tx.Factory().NewModifier(ast.KindDeclareKeyword))
	}
	members, _ = tx.declareStrippingVisitor.VisitSlice(members)
	return tx.Factory().NewModuleDeclaration(
		tx.Factory().NewModifierList(nsMods),
		ast.KindNamespaceKeyword,
		nsName,
		tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(members)),
	)
}

func isCommonJSAliasExport(node *ast.Node) bool {
	if ast.IsBinaryExpression(node) && ast.IsIdentifier(node.AsBinaryExpression().Right) {
		if symbol := node.Symbol(); symbol != nil && len(symbol.Declarations) == 1 {
			return true
		}
	}
	return false
}

// transformClassExpressionToDeclaration converts a class expression into a class declaration
// for use in CJS export declarations (e.g., exports.K = class K {} or module.exports = class Thing {}).
// This delegates to the shared buildClassMembers helper to stay in sync with transformClassDeclaration.
func (tx *DeclarationTransformer) transformClassExpressionToDeclaration(classExpr *ast.Node, className *ast.Node, modifiers *ast.ModifierList) *ast.Node {
	previousEnclosingDeclaration := tx.enclosingDeclaration
	tx.enclosingDeclaration = classExpr
	previousInClassExpressionDeclaration := tx.inClassExpressionDeclaration
	tx.inClassExpressionDeclaration = true
	defer func() {
		tx.enclosingDeclaration = previousEnclosingDeclaration
		tx.inClassExpressionDeclaration = previousInClassExpressionDeclaration
	}()

	var extraMembers []*ast.Node
	if ast.IsInJSFile(classExpr) {
		extraMembers = tx.collectThisPropertyAssignments(classExpr)
	}
	members := tx.buildClassMembers(classExpr, extraMembers...)
	typeParameters := tx.ensureTypeParams(classExpr, classExpr.AsClassExpression().TypeParameters)
	heritageClauses := tx.Visitor().VisitNodes(classExpr.AsClassExpression().HeritageClauses)

	return tx.Factory().NewClassDeclaration(
		modifiers,
		className,
		typeParameters,
		heritageClauses,
		members,
	)
}

func (tx *DeclarationTransformer) rewriteModuleSpecifier(parent *ast.Node, input *ast.Node) *ast.Node {
	if input == nil {
		return nil
	}
	tx.resultHasExternalModuleIndicator = tx.resultHasExternalModuleIndicator || (parent.Kind != ast.KindModuleDeclaration && parent.Kind != ast.KindImportType)
	return input
}

func (tx *DeclarationTransformer) tryGetResolutionModeOverride(node *ast.Node) *ast.Node {
	if node == nil {
		return node
	}
	mode := tx.host.GetResolutionModeOverride(node)
	if mode != core.ResolutionModeNone {
		return node
	}
	return nil
}

func (tx *DeclarationTransformer) preserveJsDoc(updated *ast.Node, original *ast.Node) {
	// Copy comment range from original to updated node so JSDoc comments are preserved
	tx.EmitContext().AssignCommentRange(updated, original)
}

func (tx *DeclarationTransformer) preservePartialJsDoc(updated *ast.Node, original *ast.Node) {
	if original.Flags&ast.NodeFlagsReparsed == 0 {
		return
	}
	jsdoc := core.FirstOrNil(original.EagerJSDoc(ast.GetSourceFileOfNode(original)))
	if jsdoc == nil {
		return
	}
	description := scanner.GetTextOfJSDocComment(jsdoc.AsJSDoc().Comment)
	if description == "" {
		return
	}
	comment := "*\n * " + strings.ReplaceAll(description, "\n", "\n * ") + "\n "
	tx.EmitContext().AddSyntheticLeadingComment(updated, ast.KindMultiLineCommentTrivia, comment, true /*hasTrailingNewLine*/)
}

func (tx *DeclarationTransformer) removeAllComments(node *ast.Node) {
	tx.EmitContext().AddEmitFlags(node, printer.EFNoComments)
	// !!! TODO: Also remove synthetic trailing/leading comments added by transforms
	// emitNode.leadingComments = undefined;
	// emitNode.trailingComments = undefined;
}

func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) *ast.Node {
	if !ignorePrivate && tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 {
		// Private nodes emit no types (except private parameter properties, whose parameter types are actually visible)
		return nil
	}

	if tx.shouldPrintWithInitializer(node) {
		// Literal const declarations will have an initializer ensured rather than a type
		return nil
	}

	// Should be removed createTypeOfDeclaration will actually now reuse the existing annotation so there is no real need to duplicate type walking
	// Left in for now to minimize diff during syntactic type node builder refactor
	if !ast.IsExportAssignment(node) && !ast.IsBindingElement(node) && node.Type() != nil && (!ast.IsParameterDeclaration(node) || !tx.resolver.RequiresAddingImplicitUndefined(node, nil, tx.enclosingDeclaration)) {
		if tx.state.currentSourceFile.IsJS() {
			// JS types have a heap of constructs we can't directly emit into .d.ts files; the node builder contains logic to remap those where possible, so we invoke it here
			// In strada we always built js declarations symbolically, so all js type nodes went through this postprocessing
			jsFlags := declarationEmitNodeBuilderFlags
			if tx.inClassExpressionDeclaration {
				jsFlags &^= nodebuilder.FlagsWriteClassExpressionAsTypeLiteral
			}
			res := tx.resolver.TryJSTypeNodeToTypeNode(tx.EmitContext(), node.Type(), tx.enclosingDeclaration, jsFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)
			if res != nil {
				return res
			}
			// otherwise, fall back to full serialization
		} else {
			return tx.Visitor().Visit(node.Type())
		}
	}

	oldErrorNameNode := tx.state.errorNameNode
	tx.state.errorNameNode = node.Name()
	var oldDiag GetSymbolAccessibilityDiagnostic
	if !tx.suppressNewDiagnosticContexts {
		oldDiag = tx.state.getSymbolAccessibilityDiagnostic
		if canProduceDiagnostics(node) {
			tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node)
		}
	}
	var typeNode *ast.Node

	flags := declarationEmitNodeBuilderFlags
	if tx.inClassExpressionDeclaration {
		flags &^= nodebuilder.FlagsWriteClassExpressionAsTypeLiteral
	}
	if ast.HasInferredType(node) {
		typeNode = tx.resolver.CreateTypeOfDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker)
	} else if ast.IsFunctionLike(node) {
		typeNode = tx.resolver.CreateReturnTypeOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker)
	} else {
		debug.AssertNever(node)
	}

	tx.state.errorNameNode = oldErrorNameNode
	if !tx.suppressNewDiagnosticContexts {
		tx.state.getSymbolAccessibilityDiagnostic = oldDiag
	}
	if typeNode == nil {
		return tx.Factory().NewKeywordTypeNode(ast.KindAnyKeyword)
	}
	return typeNode
}

func (tx *DeclarationTransformer) shouldPrintWithInitializer(node *ast.Node) bool {
	return canHaveLiteralInitializer(tx.host, node) && node.Initializer() != nil && tx.resolver.IsLiteralConstDeclaration(tx.EmitContext().MostOriginal(node))
}

func (tx *DeclarationTransformer) checkEntityNameVisibility(entityName *ast.Node, enclosingDeclaration *ast.Node) {
	visibilityResult := tx.resolver.IsEntityNameVisible(entityName, enclosingDeclaration)
	tx.tracker.handleSymbolAccessibilityError(visibilityResult)
}

// Transforms the direct child of a source file into zero or more replacement statements
func (tx *DeclarationTransformer) transformTopLevelDeclaration(input *ast.Node) *ast.Node {
	if len(tx.state.lateMarkedStatements) > 0 {
		// Remove duplicates of the current statement from the deferred work queue (this was done via orderedRemoveItem in strada - why? to ensure the same backing array? microop?)
		tx.state.lateMarkedStatements = core.Filter(tx.state.lateMarkedStatements, func(node *ast.Node) bool { return node != input })
	}
	if tx.shouldStripInternal(input) {
		return nil
	}
	if input.Kind == ast.KindImportEqualsDeclaration {
		return tx.transformImportEqualsDeclaration(input.AsImportEqualsDeclaration())
	}
	if input.Kind == ast.KindImportDeclaration || input.Kind == ast.KindJSImportDeclaration {
		res := tx.transformImportDeclaration(input.AsImportDeclaration())
		if res != nil && res.Kind != ast.KindImportDeclaration {
			res := res.Clone(tx.Factory())
			res.Kind = ast.KindImportDeclaration
			return res
		}
		return res
	}
	if ast.IsDeclaration(input) && isDeclarationAndNotVisible(tx.EmitContext(), tx.resolver, input) {
		return nil
	}

	// !!! TODO: JSDoc support
	// if (isJSDocImportTag(input)) return;

	// Elide implementation signatures from overload sets
	if ast.IsFunctionLike(input) && tx.resolver.IsImplementationOfOverload(input) {
		return nil
	}
	original := tx.EmitContext().MostOriginal(input)
	id := ast.GetNodeId(original)
	_, isExpandoHost := tx.expandoHosts[id]
	_, hasDeferredExpandoAssignments := tx.deferredExpandoAssignments[id]
	if isExpandoHost || hasDeferredExpandoAssignments {
		return tx.createFullExpandoBlock(id)
	}

	previousEnclosingDeclaration := tx.enclosingDeclaration
	if isEnclosingDeclaration(input) {
		tx.enclosingDeclaration = input
	}

	canProdiceDiagnostic := canProduceDiagnostics(input)
	oldDiag := tx.state.getSymbolAccessibilityDiagnostic
	oldName := tx.state.errorNameNode
	if canProdiceDiagnostic {
		tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input)
	}
	saveNeedsDeclare := tx.needsDeclare

	var result *ast.Node
	switch input.Kind {
	case ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration:
		result = tx.transformTypeAliasDeclaration(input.AsTypeAliasDeclaration())
	case ast.KindInterfaceDeclaration:
		result = tx.transformInterfaceDeclaration(input.AsInterfaceDeclaration())
	case ast.KindFunctionDeclaration:
		result = tx.transformFunctionDeclaration(input.AsFunctionDeclaration())
	case ast.KindModuleDeclaration:
		result = tx.transformModuleDeclaration(input.AsModuleDeclaration())
	case ast.KindClassDeclaration:
		result = tx.transformClassDeclaration(input.AsClassDeclaration())
	case ast.KindVariableStatement:
		result = tx.transformVariableStatement(input.AsVariableStatement())
	case ast.KindEnumDeclaration:
		result = tx.transformEnumDeclaration(input.AsEnumDeclaration())
	default:
		// Anything left unhandled is an error, so this should be unreachable
		panic(fmt.Sprintf("Unhandled top-level node in declaration emit: %q", input.Kind))
	}

	tx.enclosingDeclaration = previousEnclosingDeclaration
	tx.state.getSymbolAccessibilityDiagnostic = oldDiag
	tx.needsDeclare = saveNeedsDeclare
	tx.state.errorNameNode = oldName
	return result
}

func (tx *DeclarationTransformer) transformTypeAliasDeclaration(input *ast.TypeAliasDeclaration) *ast.Node {
	tx.needsDeclare = false
	return tx.Factory().UpdateTypeAliasDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		tx.Visitor().VisitNodes(input.TypeParameters),
		tx.Visitor().Visit(input.Type),
	)
}

func (tx *DeclarationTransformer) transformInterfaceDeclaration(input *ast.InterfaceDeclaration) *ast.Node {
	return tx.Factory().UpdateInterfaceDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		tx.Visitor().VisitNodes(input.TypeParameters),
		tx.Visitor().VisitNodes(input.HeritageClauses),
		tx.Visitor().VisitNodes(input.Members),
	)
}

func (tx *DeclarationTransformer) transformFunctionDeclaration(input *ast.FunctionDeclaration) *ast.Node {
	if tx.resolver.IsExpandoFunctionDeclaration(input.AsNode()) {
		tx.state.reportExpandoFunctionErrors(input.AsNode())
	}
	return tx.Factory().UpdateFunctionDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		nil,
		input.Name(),
		tx.ensureTypeParams(input.AsNode(), input.TypeParameters),
		tx.updateParamList(input.AsNode(), input.Parameters),
		tx.ensureType(input.AsNode(), false),
		nil, /*fullSignature*/
		nil,
	)
}

func (tx *DeclarationTransformer) transformModuleDeclaration(input *ast.ModuleDeclaration) *ast.Node {
	// !!! TODO: module declarations are now parsed into nested module objects with export modifiers
	// It'd be good to collapse those back in the declaration output, but the AST can't represent the
	// `namespace a.b.c` shape for the printer (without using invalid identifier names).
	mods := tx.ensureModifiers(input.AsNode())
	saveNeedsDeclare := tx.needsDeclare
	tx.needsDeclare = false
	inner := input.Body
	keyword := input.Keyword
	if keyword != ast.KindGlobalKeyword && (input.Name() == nil || !ast.IsStringLiteral(input.Name())) {
		keyword = ast.KindNamespaceKeyword
	}

	if inner != nil && inner.Kind == ast.KindModuleBlock {
		oldNeedsScopeFix := tx.needsScopeFixMarker
		oldHasScopeFix := tx.resultHasScopeMarker
		tx.resultHasScopeMarker = false
		tx.needsScopeFixMarker = false
		statements := tx.Visitor().VisitNodes(inner.StatementList())
		lateStatements := tx.transformAndReplaceLatePaintedStatements(statements)
		if input.Flags&ast.NodeFlagsAmbient != 0 {
			tx.needsScopeFixMarker = false // If it was `declare`'d everything is implicitly exported already, ignore late printed "privates"
		}
		// With the final list of statements, there are 3 possibilities:
		// 1. There's an export assignment or export declaration in the namespace - do nothing
		// 2. Everything is exported and there are no export assignments or export declarations - strip all export modifiers
		// 3. Some things are exported, some are not, and there's no marker - add an empty marker
		if !ast.IsGlobalScopeAugmentation(input.AsNode()) && !tx.resultHasScopeMarker && !hasScopeMarker(lateStatements) {
			if tx.needsScopeFixMarker {
				lateStatements = tx.Factory().NewNodeList(append(lateStatements.Nodes, createEmptyExports(tx.Factory().AsNodeFactory())))
			} else {
				lateStatements = tx.exportStrippingVisitor.VisitNodes(lateStatements)
			}
		}

		body := tx.Factory().UpdateModuleBlock(inner.AsModuleBlock(), lateStatements)
		tx.needsDeclare = saveNeedsDeclare
		tx.needsScopeFixMarker = oldNeedsScopeFix
		tx.resultHasScopeMarker = oldHasScopeFix

		return tx.Factory().UpdateModuleDeclaration(
			input,
			mods,
			keyword,
			input.Name(),
			body,
		)
	}
	if inner != nil {
		// trigger visit. ignore result (is deferred, so is just inner unless elided)
		tx.Visitor().Visit(inner)
		// eagerly transform nested namespaces (the nesting doesn't need any elision or painting done)
		original := tx.EmitContext().MostOriginal(inner)
		id := ast.GetNodeId(original)
		body, _ := tx.lateStatementReplacementMap[id]
		delete(tx.lateStatementReplacementMap, id)
		return tx.Factory().UpdateModuleDeclaration(
			input,
			mods,
			keyword,
			input.Name(),
			body,
		)
	}
	return tx.Factory().UpdateModuleDeclaration(
		input,
		mods,
		keyword,
		input.Name(),
		nil,
	)
}

func (tx *DeclarationTransformer) stripExportModifiers(statement *ast.Node) *ast.Node {
	if statement == nil {
		return nil
	}
	parseNode := tx.EmitContext().ParseNode(statement)
	if ast.IsImportEqualsDeclaration(statement) || (parseNode != nil && tx.host.GetEffectiveDeclarationFlags(parseNode, ast.ModifierFlagsDefault) != 0) || !ast.CanHaveModifiers(statement) {
		// `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace
		// Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too
		return statement
	}

	oldFlags := ast.GetCombinedModifierFlags(statement)
	if oldFlags&ast.ModifierFlagsExport == 0 {
		return statement
	}
	newFlags := oldFlags & (ast.ModifierFlagsAll ^ ast.ModifierFlagsExport)
	modifiers := ast.CreateModifiersFromModifierFlags(newFlags, tx.Factory().NewModifier)
	return ast.ReplaceModifiers(tx.Factory().AsNodeFactory(), statement, tx.Factory().NewModifierList(modifiers))
}

// buildClassMembers builds the member list for a class-like node (ClassDeclaration or ClassExpression).
// It handles parameter properties, private identifiers, late-bound index signatures, and visited members.
// Extra members (e.g., this-property assignments from JS files) can be passed via extraMembers.
func (tx *DeclarationTransformer) buildClassMembers(classNode *ast.Node, extraMembers ...*ast.Node) *ast.NodeList {
	ctor := ast.GetFirstConstructorWithBody(classNode)
	var parameterProperties []*ast.Node
	if ctor != nil {
		oldDiag := tx.state.getSymbolAccessibilityDiagnostic
		for _, param := range ctor.AsConstructorDeclaration().Parameters.Nodes {
			if !ast.HasSyntacticModifier(param, ast.ModifierFlagsParameterPropertyModifier) || tx.shouldStripInternal(param) {
				continue
			}
			tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param)
			if param.Name().Kind == ast.KindIdentifier {
				updated := tx.Factory().NewPropertyDeclaration(
					tx.ensureModifiers(param),
					param.Name(),
					param.QuestionToken(),
					tx.ensureType(param, false),
					tx.ensureNoInitializer(param),
				)
				tx.preserveJsDoc(updated, param)
				parameterProperties = append(parameterProperties, updated)
			} else {
				// Pattern - this is currently an error, but we emit declarations for it somewhat correctly
				parameterProperties = append(parameterProperties, tx.walkBindingPattern(param.Name().AsBindingPattern(), param)...)
			}
		}
		tx.state.getSymbolAccessibilityDiagnostic = oldDiag
	}

	// When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior
	// Prevents other classes with the same public members from being used in place of the current class
	var privateIdentifier *ast.Node
	if core.Some(classNode.ClassLikeData().Members.Nodes, func(member *ast.Node) bool {
		return member.Name() != nil && ast.IsPrivateIdentifier(member.Name())
	}) {
		privateIdentifier = tx.Factory().NewPropertyDeclaration(nil, tx.Factory().NewPrivateIdentifier("#private"), nil, nil, nil)
	}

	lateIndexes := tx.resolver.CreateLateBoundIndexSignatures(
		tx.EmitContext(),
		classNode,
		tx.enclosingDeclaration,
		declarationEmitNodeBuilderFlags,
		declarationEmitInternalNodeBuilderFlags,
		tx.tracker,
	)

	memberNodes := make([]*ast.Node, 0, len(classNode.ClassLikeData().Members.Nodes))
	if privateIdentifier != nil {
		memberNodes = append(memberNodes, privateIdentifier)
	}
	memberNodes = append(memberNodes, lateIndexes...)
	memberNodes = append(memberNodes, parameterProperties...)
	memberNodes = append(memberNodes, extraMembers...)
	visitResult := tx.Visitor().VisitNodes(classNode.ClassLikeData().Members)
	if visitResult != nil && len(visitResult.Nodes) > 0 {
		memberNodes = append(memberNodes, visitResult.Nodes...)
	}
	return tx.Factory().NewNodeList(memberNodes)
}

func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDeclaration) *ast.Node {
	previousEnclosingDeclaration := tx.enclosingDeclaration
	tx.enclosingDeclaration = input.AsNode()
	defer func() { tx.enclosingDeclaration = previousEnclosingDeclaration }()

	tx.state.errorNameNode = input.Name()
	tx.tracker.PushErrorFallbackNode(input.AsNode())
	defer tx.tracker.PopErrorFallbackNode()

	modifiers := tx.ensureModifiers(input.AsNode())
	typeParameters := tx.ensureTypeParams(input.AsNode(), input.TypeParameters)

	// Collect this.x property assignments from constructors and static blocks in JS files
	var extraMembers []*ast.Node
	if ast.IsInJSFile(input.AsNode()) {
		extraMembers = tx.collectThisPropertyAssignments(input.AsNode())
	}

	members := tx.buildClassMembers(input.AsNode(), extraMembers...)

	extendsClause := getEffectiveBaseTypeNode(input.AsNode())

	if extendsClause != nil && !ast.IsEntityNameExpression(extendsClause.AsExpressionWithTypeArguments().Expression) && extendsClause.AsExpressionWithTypeArguments().Expression.Kind != ast.KindNullKeyword {
		tx.tracker.ReportInferenceFallback(extendsClause.AsExpressionWithTypeArguments().Expression) // Add an isolated declarations error on this extends clause
		oldId := "default"
		if ast.NodeIsPresent(input.Name()) && ast.IsIdentifier(input.Name()) && len(input.Name().Text()) > 0 {
			oldId = input.Name().Text()
		}
		newId := tx.Factory().NewUniqueNameEx(oldId+"_base", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic})
		tx.state.getSymbolAccessibilityDiagnostic = func(_ printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic {
			return &SymbolAccessibilityDiagnostic{
				diagnosticMessage: diagnostics.X_extends_clause_of_exported_class_0_has_or_is_using_private_name_1,
				errorNode:         extendsClause,
				typeName:          input.Name(),
			}
		}

		varDecl := tx.Factory().NewVariableDeclaration(
			newId,
			nil,
			tx.resolver.CreateTypeOfExpression(tx.EmitContext(), extendsClause.Expression(), input.AsNode(), declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker),
			nil,
		)
		var mods *ast.ModifierList
		if tx.needsDeclare {
			mods = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindDeclareKeyword)})
		}
		statement := tx.Factory().NewVariableStatement(
			mods,
			tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), ast.NodeFlagsConst),
		)
		newHeritageClause := tx.Factory().UpdateHeritageClause(
			extendsClause.Parent.AsHeritageClause(),
			extendsClause.Parent.AsHeritageClause().Token,
			tx.Factory().NewNodeList([]*ast.Node{
				tx.Factory().UpdateExpressionWithTypeArguments(
					extendsClause.AsExpressionWithTypeArguments(),
					newId,
					tx.Visitor().VisitNodes(extendsClause.AsExpressionWithTypeArguments().TypeArguments),
				),
			}),
		)
		retainedHeritageClauses := tx.Visitor().VisitNodes(input.HeritageClauses) // should just be `implements`
		heritageList := []*ast.Node{
			newHeritageClause,
		}
		if retainedHeritageClauses != nil && len(retainedHeritageClauses.Nodes) > 0 {
			heritageList = append(heritageList, retainedHeritageClauses.Nodes...)
		}
		heritageClauses := tx.Factory().NewNodeList(heritageList)

		return tx.Factory().NewSyntaxList([]*ast.Node{
			statement,
			tx.Factory().UpdateClassDeclaration(
				input,
				modifiers,
				input.Name(),
				typeParameters,
				heritageClauses,
				members,
			),
		})
	}

	return tx.Factory().UpdateClassDeclaration(
		input,
		modifiers,
		input.Name(),
		typeParameters,
		tx.Visitor().VisitNodes(input.HeritageClauses),
		members,
	)
}

func (tx *DeclarationTransformer) visitThisPropertyAssignments(node *ast.Node) *ast.Node {
	var thisTarget *ast.Node
	isStatic := false
	thisContainer := ast.GetThisContainer(node, false, false)
	thisTarget = thisContainer.Parent
	if thisTarget == nil {
		return nil // thisContainer was source file, can't have expando-this
	}
	if ast.HasStaticModifier(thisContainer) || ast.IsClassStaticBlockDeclaration(thisContainer) {
		isStatic = true
	}
	if thisTarget != tx.enclosingDeclaration {
		return nil // stop searching within new `this` contexts
	}
caseBlock:
	switch ast.GetAssignmentDeclarationKind(node) {
	case ast.JSDeclarationKindThisProperty:
		name := ast.GetNameOfDeclaration(node)
		base := tx.resolver.GetReferencedMemberValueDeclaration(node)
		key := getThisPropertyAssignmentKey(name, node, isStatic)
		if base == nil || tx.seenProperties.Has(key) {
			break
		}
		tx.seenProperties.Add(key)

		// problem: this prop might be overriding a prop from a base type. The checker has special bails for override compat comparisons for binary expression properties,
		// but what we transform to won't - so we either need to match the base type (for example, if it's a getter/setter) or emit nothing
		// See `checkKindsOfPropertyMemberOverrides` in the checker for what we're trying to satisfy here
		if thisTarget.ClassLikeData().HeritageClauses != nil && len(thisTarget.ClassLikeData().HeritageClauses.Nodes) > 0 && !isClassExtendingNull(thisTarget) {
			// there is a base type any assignments might be "from"
			tx.tracker.ReportInferenceFallback(thisTarget) // Add an isolated declarations error on this class - we can't know how to transform this prop into an assignment without referring to type information
			if tx.resolver.IsThisPropertyAssignmentDeclarationRedundant(node) {
				break caseBlock // skip assignments whose member is already provided by an `extends` base type (an inherited accessor/method, or an identical inherited property)
				// TODO: If the property has an explicit `@type` annotation, we should probably emit it (maybe with an `override` modifier) instead of skipping it
			}
		}

		var mods *ast.ModifierList
		if isStatic {
			mods = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindStaticKeyword)})
		}
		if ast.HasDynamicName(node) {
			if !transformers.IsSimpleInlineableExpression(name) {
				break // Member either becomes an index signature or is a reassignment
			}
			tx.checkName(node)
			name = tx.Factory().NewComputedPropertyName(name) // Convert `this[foo] = expr` to `[foo]: Type`
		}
		if ast.GetTextOfPropertyName(name) == "constructor" {
			break // `constructor` is a builtin class member, not allowed to redeclare it
		}
		if ast.IsIdentifier(name) && !scanner.IsIdentifierText(name.Text(), core.LanguageVariantStandard) {
			name = tx.Factory().NewStringLiteralFromNode(name)
		}
		prop := tx.Factory().NewPropertyDeclaration(
			mods,
			name,
			nil,
			tx.ensureType(node, false),
			nil,
		)
		if ast.IsExpressionStatement(node.Parent) {
			tx.preserveJsDoc(prop, node.Parent)
		}
		tx.thisPropertyAssignmentsCollected = append(tx.thisPropertyAssignmentsCollected, prop)
	}
	return tx.thisPropertyVisitor.VisitEachChild(node)
}

func isClassExtendingNull(node *ast.Node) bool {
	if node == nil {
		return false
	}
	heritage := node.ClassLikeData().HeritageClauses
	if heritage == nil {
		return false
	}
	if len(heritage.Nodes) > 1 || len(heritage.Nodes) == 0 {
		return false
	}
	for _, expA := range heritage.Nodes[0].AsHeritageClause().Types.Nodes {
		expr := expA.AsExpressionWithTypeArguments().Expression
		if expr != nil && expr.Kind == ast.KindNullKeyword {
			return true
		}
	}
	return false
}

// collectThisPropertyAssignments finds `this.x = expr` assignments in constructors, methods, and static blocks
// of JS classes and synthesizes PropertyDeclaration nodes for each unique property name.
func (tx *DeclarationTransformer) collectThisPropertyAssignments(classNode *ast.Node) []*ast.Node {
	members := classNode.ClassLikeData().Members
	seen := collections.Set[thisPropertyAssignmentKey]{}
	// Pre-populate seen with existing direct member nodes to avoid duplicates
	for _, member := range members.Nodes {
		if member.Name() != nil {
			isStatic := ast.IsStatic(member)
			seen.Add(getThisPropertyAssignmentKey(member.Name(), member, isStatic))
		}
	}
	tx.seenProperties = seen
	defer tx.seenProperties.Clear()
	tx.thisPropertyAssignmentsCollected = []*ast.Node{}
	defer func() {
		tx.thisPropertyAssignmentsCollected = nil
	}()

	for _, n := range members.Nodes {
		tx.thisPropertyVisitor.VisitEachChild(n)
	}
	return tx.thisPropertyAssignmentsCollected
}

func (tx *DeclarationTransformer) walkBindingPattern(pattern *ast.BindingPattern, param *ast.Node) []*ast.Node {
	var elems []*ast.Node
	for _, elem := range pattern.Elements.Nodes {
		if ast.IsOmittedExpression(elem) {
			continue
		}
		if ast.IsBindingPattern(elem.Name()) {
			elems = append(elems, tx.walkBindingPattern(elem.Name().AsBindingPattern(), param)...)
			continue
		}
		elems = append(elems, tx.Factory().NewPropertyDeclaration(
			tx.ensureModifiers(param),
			elem.Name(),
			nil, /*questionOrExclamationToken*/
			tx.ensureType(elem, false),
			nil, /*initializer*/
		))
	}
	return elems
}

func (tx *DeclarationTransformer) transformVariableStatement(input *ast.VariableStatement) *ast.Node {
	visible := false
	for _, decl := range input.DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
		visible = getBindingNameVisible(tx.resolver, decl)
		if visible {
			break
		}
	}
	if !visible {
		return nil
	}

	inputNodes := input.DeclarationList.AsVariableDeclarationList().Declarations.Nodes
	var extraImports []*ast.Node
	if tx.state.currentSourceFile.CommonJSModuleIndicator != nil {
		var normalDeclarations []*ast.Node
		var imports []*ast.Node
		for _, n := range inputNodes {
			if ast.IsVariableDeclarationInitializedToRequire(n) {
				imports = append(imports, n)
			} else {
				normalDeclarations = append(normalDeclarations, n)
			}
		}
		inputNodes = normalDeclarations
		extraImports, _ = tx.Visitor().VisitSlice(imports)
	}

	nodes, _ := tx.Visitor().VisitSlice(inputNodes)
	if len(nodes) == 0 {
		if len(extraImports) > 0 {
			return tx.Factory().NewSyntaxList(extraImports)
		}
		return nil
	}
	nodeList := tx.Factory().NewNodeList(nodes)

	modifiers := tx.ensureModifiers(input.AsNode())

	var declList *ast.Node
	if ast.IsVarUsing(input.DeclarationList) || ast.IsVarAwaitUsing(input.DeclarationList) {
		declList = tx.Factory().NewVariableDeclarationList(nodeList, ast.NodeFlagsConst)
		tx.EmitContext().SetOriginal(declList, input.DeclarationList)
		tx.EmitContext().SetCommentRange(declList, input.DeclarationList.Loc)
		declList.Loc = input.DeclarationList.Loc
	} else {
		declList = tx.Factory().UpdateVariableDeclarationList(input.DeclarationList.AsVariableDeclarationList(), nodeList, input.DeclarationList.Flags)
	}
	res := tx.Factory().UpdateVariableStatement(input, modifiers, declList)
	if len(extraImports) > 0 {
		return tx.Factory().NewSyntaxList(append(extraImports, res))
	}
	return res
}

func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclaration) *ast.Node {
	return tx.Factory().UpdateEnumDeclaration(
		input,
		tx.ensureModifiers(input.AsNode()),
		input.Name(),
		tx.Factory().NewNodeList(core.MapNonNil(input.Members.Nodes, func(m *ast.Node) *ast.Node {
			if tx.shouldStripInternal(m) {
				return nil
			}

			// Rewrite enum values to their constants, if available
			enumValue := tx.resolver.GetEnumMemberValue(m)

			if tx.state.isolatedDeclarations && m.Initializer() != nil && enumValue.HasExternalReferences &&
				// This will be its own compiler error instead, so don't report.
				!ast.IsComputedPropertyName(m.Name()) {
				tx.state.addDiagnostic(createDiagnosticForNode(m, diagnostics.Enum_member_initializers_must_be_computable_without_references_to_external_symbols_with_isolatedDeclarations))
			}

			var newInitializer *ast.Node
			switch value := enumValue.Value.(type) {
			case jsnum.Number:
				if value.IsInf() {
					if value > 0 {
						newInitializer = tx.Factory().NewIdentifier("Infinity")
					} else {
						newInitializer = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewIdentifier("Infinity"))
					}
				} else if value.IsNaN() {
					newInitializer = tx.Factory().NewIdentifier("NaN")
				} else if value >= 0 {
					newInitializer = tx.Factory().NewNumericLiteral(value.String(), ast.TokenFlagsNone)
				} else {
					newInitializer = tx.Factory().NewPrefixUnaryExpression(
						ast.KindMinusToken,
						tx.Factory().NewNumericLiteral((-value).String(), ast.TokenFlagsNone),
					)
				}
			case string:
				newInitializer = tx.Factory().NewStringLiteral(value, ast.TokenFlagsNone)
			default:
				// nil
				newInitializer = nil
			}
			result := tx.Factory().UpdateEnumMember(m.AsEnumMember(), m.Name(), newInitializer)
			tx.preserveJsDoc(result, m)
			return result
		})),
	)
}

func (tx *DeclarationTransformer) ensureModifiers(node *ast.Node) *ast.ModifierList {
	currentFlags := ast.GetCombinedModifierFlags(tx.EmitContext().ParseNode(node)) & ast.ModifierFlagsAll
	newFlags := tx.ensureModifierFlags(node)
	if currentFlags == newFlags {
		// Elide decorators
		mods := node.Modifiers()
		if mods == nil {
			return mods
		}
		if canReuseModifierNodes(mods.Nodes) {
			return tx.Factory().NewModifierList(core.Filter(mods.Nodes, ast.IsModifier))
		}
	}
	result := ast.CreateModifiersFromModifierFlags(newFlags, tx.Factory().NewModifier)
	if len(result) == 0 {
		return nil
	}
	return tx.Factory().NewModifierList(result)
}

func (tx *DeclarationTransformer) ensureModifierFlags(node *ast.Node) ast.ModifierFlags {
	mask := ast.ModifierFlagsAll ^ (ast.ModifierFlagsPublic | ast.ModifierFlagsAsync | ast.ModifierFlagsOverride) // No async and override modifiers in declaration files
	additions := ast.ModifierFlagsNone
	if tx.needsDeclare && !isAlwaysType(node) {
		additions = ast.ModifierFlagsAmbient
	}
	parentIsFile := node.Parent.Kind == ast.KindSourceFile
	if !parentIsFile {
		mask ^= ast.ModifierFlagsAmbient
		additions = ast.ModifierFlagsNone
	}
	if ast.IsImplicitlyExportedJSDocDeclaration(node) {
		additions |= ast.ModifierFlagsExport
	}
	return maskModifierFlags(node, mask, additions)
}

func (tx *DeclarationTransformer) ensureTypeParams(node *ast.Node, params *ast.TypeParameterList) *ast.TypeParameterList {
	if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 {
		return nil
	}
	var typeParameters *ast.TypeParameterList
	if typeParameters = tx.Visitor().VisitNodes(params); typeParameters != nil {
		return typeParameters
	}
	oldErrorNameNode := tx.state.errorNameNode
	tx.state.errorNameNode = node.Name()
	var oldDiag GetSymbolAccessibilityDiagnostic
	if !tx.suppressNewDiagnosticContexts {
		oldDiag = tx.state.getSymbolAccessibilityDiagnostic
		if canProduceDiagnostics(node) {
			tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node)
		}
	}

	if data := node.FunctionLikeData(); data != nil && data.FullSignature != nil {
		if nodes := tx.resolver.CreateTypeParametersOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker); nodes != nil {
			typeParameters = &ast.TypeParameterList{
				Loc:   node.Loc,
				Nodes: nodes,
			}
		}
	}

	tx.state.errorNameNode = oldErrorNameNode
	if !tx.suppressNewDiagnosticContexts {
		tx.state.getSymbolAccessibilityDiagnostic = oldDiag
	}
	return typeParameters
}

func (tx *DeclarationTransformer) updateParamList(node *ast.Node, params *ast.ParameterList) *ast.ParameterList {
	if tx.host.GetEffectiveDeclarationFlags(tx.EmitContext().ParseNode(node), ast.ModifierFlagsPrivate) != 0 || len(params.Nodes) == 0 {
		return tx.Factory().NewNodeList([]*ast.Node{})
	}
	results := make([]*ast.Node, len(params.Nodes))
	for i, p := range params.Nodes {
		results[i] = tx.ensureParameter(p.AsParameterDeclaration())
	}
	return tx.Factory().NewNodeList(results)
}

func (tx *DeclarationTransformer) ensureParameter(p *ast.ParameterDeclaration) *ast.Node {
	oldDiag := tx.state.getSymbolAccessibilityDiagnostic
	if !tx.suppressNewDiagnosticContexts {
		tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.AsNode())
	}
	var questionToken *ast.TokenNode
	if tx.resolver.IsOptionalParameter(p.AsNode()) {
		if p.QuestionToken != nil {
			questionToken = p.QuestionToken
		} else {
			questionToken = tx.Factory().NewToken(ast.KindQuestionToken)
		}
	}
	result := tx.Factory().UpdateParameterDeclaration(
		p,
		nil,
		p.DotDotDotToken,
		tx.bindingNameVisitor.VisitNode(p.Name()),
		questionToken,
		tx.ensureType(p.AsNode(), true),
		tx.ensureNoInitializer(p.AsNode()),
	)
	tx.state.getSymbolAccessibilityDiagnostic = oldDiag
	return result
}

func (tx *DeclarationTransformer) ensureNoInitializer(node *ast.Node) *ast.Node {
	if tx.shouldPrintWithInitializer(node) {
		unwrappedInitializer := unwrapParenthesizedExpression(node.Initializer())
		if !ast.IsPrimitiveLiteralValue(unwrappedInitializer, true) {
			tx.tracker.ReportInferenceFallback(node)
		}
		return tx.resolver.CreateLiteralConstValue(tx.EmitContext(), tx.EmitContext().ParseNode(node), tx.tracker)
	}
	return nil
}

func (tx *DeclarationTransformer) visitBindingName(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindIdentifier, ast.KindOmittedExpression:
		return node
	case ast.KindArrayBindingPattern, ast.KindObjectBindingPattern:
		return node.VisitEachChild(tx.bindingNameVisitor)
	case ast.KindBindingElement:
		if node.PropertyName() != nil && ast.IsComputedPropertyName(node.PropertyName()) && ast.IsEntityNameExpression(node.PropertyName().Expression()) {
			tx.checkEntityNameVisibility(node.PropertyName().Expression(), tx.enclosingDeclaration)
		}
		return tx.Factory().UpdateBindingElement(node.AsBindingElement(), node.AsBindingElement().DotDotDotToken, node.PropertyName(), tx.bindingNameVisitor.VisitNode(node.Name()), nil /*initializer*/)
	default:
		return node
	}
}

func (tx *DeclarationTransformer) transformImportEqualsDeclaration(decl *ast.ImportEqualsDeclaration) *ast.Node {
	if !tx.resolver.IsDeclarationVisible(decl.AsNode()) {
		return nil
	}
	if decl.ModuleReference.Kind == ast.KindExternalModuleReference {
		// Rewrite external module names if necessary
		specifier := ast.GetExternalModuleImportEqualsDeclarationExpression(decl.AsNode())
		return tx.Factory().UpdateImportEqualsDeclaration(
			decl,
			decl.Modifiers(),
			decl.IsTypeOnly,
			decl.Name(),
			tx.Factory().UpdateExternalModuleReference(decl.ModuleReference.AsExternalModuleReference(), tx.rewriteModuleSpecifier(decl.AsNode(), specifier)),
		)
	} else {
		oldDiag := tx.state.getSymbolAccessibilityDiagnostic
		tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl.AsNode())
		tx.checkEntityNameVisibility(decl.ModuleReference, tx.enclosingDeclaration)
		tx.state.getSymbolAccessibilityDiagnostic = oldDiag
		return decl.AsNode()
	}
}

func (tx *DeclarationTransformer) transformImportDeclaration(decl *ast.ImportDeclaration) *ast.Node {
	if decl.ImportClause == nil {
		// import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc)
		return tx.Factory().UpdateImportDeclaration(
			decl,
			decl.Modifiers(),
			decl.ImportClause,
			tx.rewriteModuleSpecifier(decl.AsNode(), decl.ModuleSpecifier),
			tx.tryGetResolutionModeOverride(decl.Attributes),
		)
	}
	phaseModifier := decl.ImportClause.AsImportClause().PhaseModifier
	if phaseModifier == ast.KindDeferKeyword {
		phaseModifier = ast.KindUnknown
	}
	// The `importClause` visibility corresponds to the default's visibility.
	var visibleDefaultBinding *ast.Node
	if decl.ImportClause != nil && decl.ImportClause.Name() != nil && tx.resolver.IsDeclarationVisible(decl.ImportClause) {
		visibleDefaultBinding = decl.ImportClause.Name()
	}
	if decl.ImportClause.AsImportClause().NamedBindings == nil {
		// No named bindings (either namespace or list), meaning the import is just default or should be elided
		if visibleDefaultBinding == nil {
			return nil
		}
		return tx.Factory().UpdateImportDeclaration(
			decl,
			decl.Modifiers(),
			tx.Factory().UpdateImportClause(
				decl.ImportClause.AsImportClause(),
				phaseModifier,
				visibleDefaultBinding,
				/*namedBindings*/ nil,
			),
			tx.rewriteModuleSpecifier(decl.AsNode(), decl.ModuleSpecifier),
			tx.tryGetResolutionModeOverride(decl.Attributes),
		)
	}
	if decl.ImportClause.AsImportClause().NamedBindings.Kind == ast.KindNamespaceImport {
		// Namespace import (optionally with visible default)
		var namedBindings *ast.Node
		if tx.resolver.IsDeclarationVisible(decl.ImportClause.AsImportClause().NamedBindings) {
			namedBindings = decl.ImportClause.AsImportClause().NamedBindings
		}
		if visibleDefaultBinding == nil && namedBindings == nil {
			return nil
		}
		return tx.Factory().UpdateImportDeclaration(
			decl,
			decl.Modifiers(),
			tx.Factory().UpdateImportClause(
				decl.ImportClause.AsImportClause(),
				phaseModifier,
				visibleDefaultBinding,
				namedBindings,
			),
			tx.rewriteModuleSpecifier(decl.AsNode(), decl.ModuleSpecifier),
			tx.tryGetResolutionModeOverride(decl.Attributes),
		)
	}
	// Named imports (optionally with visible default)
	bindingList := core.Filter(
		decl.ImportClause.AsImportClause().NamedBindings.Elements(),
		func(b *ast.Node) bool {
			return tx.resolver.IsDeclarationVisible(b)
		},
	)
	if len(bindingList) > 0 || visibleDefaultBinding != nil {
		var namedImports *ast.Node
		if len(bindingList) > 0 {
			namedImports = tx.Factory().UpdateNamedImports(
				decl.ImportClause.AsImportClause().NamedBindings.AsNamedImports(),
				tx.Factory().NewNodeList(bindingList),
			)
		}
		return tx.Factory().UpdateImportDeclaration(
			decl,
			decl.Modifiers(),
			tx.Factory().UpdateImportClause(
				decl.ImportClause.AsImportClause(),
				phaseModifier,
				visibleDefaultBinding,
				namedImports,
			),
			tx.rewriteModuleSpecifier(decl.AsNode(), decl.ModuleSpecifier),
			tx.tryGetResolutionModeOverride(decl.Attributes),
		)
	}
	// Augmentation of export depends on import
	if tx.resolver.IsImportRequiredByAugmentation(decl) {
		if tx.state.isolatedDeclarations {
			tx.state.addDiagnostic(createDiagnosticForNode(decl.AsNode(), diagnostics.Declaration_emit_for_this_file_requires_preserving_this_import_for_augmentations_This_is_not_supported_with_isolatedDeclarations))
		}
		return tx.Factory().UpdateImportDeclaration(
			decl,
			decl.Modifiers(),
			/*importClause*/ nil,
			tx.rewriteModuleSpecifier(decl.AsNode(), decl.ModuleSpecifier),
			tx.tryGetResolutionModeOverride(decl.Attributes),
		)
	}
	// Nothing visible
	return nil
}

func (tx *DeclarationTransformer) transformJSDocTypeExpression(input *ast.JSDocTypeExpression) *ast.Node {
	return tx.Visitor().Visit(input.Type)
}

func (tx *DeclarationTransformer) transformJSDocTypeLiteral(input *ast.JSDocTypeLiteral) *ast.Node {
	members, _ := tx.Visitor().VisitSlice(input.JSDocPropertyTags)
	replacement := tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList(members))
	tx.EmitContext().SetOriginal(replacement, input.AsNode())
	return replacement
}

func (tx *DeclarationTransformer) transformJSDocPropertyTag(input *ast.JSDocParameterOrPropertyTag) *ast.Node {
	replacement := tx.Factory().NewPropertySignatureDeclaration(
		nil,
		tx.Visitor().Visit(input.TagName),
		nil,
		tx.Visitor().Visit(input.TypeExpression),
		nil,
	)
	tx.EmitContext().SetOriginal(replacement, input.AsNode())
	return replacement
}

func (tx *DeclarationTransformer) transformJSDocAllType(input *ast.JSDocAllType) *ast.Node {
	replacement := tx.Factory().NewKeywordTypeNode(ast.KindAnyKeyword)
	tx.EmitContext().SetOriginal(replacement, input.AsNode())
	return replacement
}

func (tx *DeclarationTransformer) transformJSDocNullableType(input *ast.JSDocNullableType) *ast.Node {
	replacement := tx.Factory().NewUnionTypeNode(tx.Factory().NewNodeList([]*ast.Node{
		tx.Visitor().Visit(input.Type),
		tx.Factory().NewLiteralTypeNode(tx.Factory().NewKeywordExpression(ast.KindNullKeyword)),
	}))
	tx.EmitContext().SetOriginal(replacement, input.AsNode())
	return replacement
}

func (tx *DeclarationTransformer) transformJSDocNonNullableType(input *ast.JSDocNonNullableType) *ast.Node {
	return tx.Visitor().Visit(input.Type)
}

func (tx *DeclarationTransformer) transformJSDocVariadicType(input *ast.JSDocVariadicType) *ast.Node {
	replacement := tx.Factory().NewArrayTypeNode(tx.Visitor().Visit(input.Type))
	tx.EmitContext().SetOriginal(replacement, input.AsNode())
	return replacement
}

func (tx *DeclarationTransformer) transformJSDocOptionalType(input *ast.JSDocOptionalType) *ast.Node {
	replacement := tx.Factory().NewUnionTypeNode(tx.Factory().NewNodeList([]*ast.Node{
		tx.Visitor().Visit(input.Type),
		tx.Factory().NewKeywordTypeNode(ast.KindUndefinedKeyword),
	}))
	tx.EmitContext().SetOriginal(replacement, input.AsNode())
	return replacement
}

func (tx *DeclarationTransformer) getNameExpressionPreferringIdentifier(nameExpr *ast.Node) *ast.Node {
	if ast.IsNumericLiteral(nameExpr) {
		// Numeric property names are string properties in JS; convert to string literal
		nameExpr = tx.Factory().NewStringLiteral(nameExpr.Text(), ast.TokenFlagsNone)
	}
	if ast.IsStringLiteralLike(nameExpr) && scanner.IsIdentifierText(nameExpr.Text(), core.LanguageVariantStandard) {
		result := tx.Factory().NewIdentifier(nameExpr.Text()) // prefer non-string literal names where possible
		kwKind := scanner.IdentifierToKeywordKind(result.AsIdentifier())
		// keep keywords as strings, except `default`, which has special reformulations in the transformer
		if kwKind == ast.KindUnknown || kwKind == ast.KindDefaultKeyword {
			// fake this into a parse tree node so the reference resolver resolves the node via `resolveName`
			result.Parent = nameExpr.Parent
			result.Flags &^= ast.NodeFlagsSynthesized
			// intentionally leave Loc unset so the string isn't used as the text source of the identifier
			return result
		}
	}
	return nameExpr
}

func isNotDeclareModifier(mod *ast.Modifier) bool {
	return mod.Kind != ast.KindDeclareKeyword
}

func (tx *DeclarationTransformer) stripDeclareModifiers(node *ast.Node) *ast.Node {
	if node == nil {
		return nil
	}
	mods := node.Modifiers()
	if mods != nil {
		flags := node.ModifierFlags()
		if flags&ast.ModifierFlagsAmbient != 0 {
			filtered := core.Filter(mods.Nodes, isNotDeclareModifier)
			node.AsMutable().SetModifiers(tx.Factory().NewModifierList(filtered))
		}
	}
	return node // no need to recur into children, only strip at top-level
}

func (tx *DeclarationTransformer) visitCJSExportAssignments(expression *ast.Node) *ast.Node {
	if expression != nil {
		_, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression)
		defer cleanupDiagnosticContext()
		switch ast.GetAssignmentDeclarationKind(expression) {
		case ast.JSDeclarationKindModuleExports:
			if tx.state.currentSourceFile.CommonJSModuleIndicator != nil {
				result := tx.transformExportAssignment(expression.Parent, expression, expression.AsBinaryExpression().Right, true /*isExportEquals*/)
				if result != nil {
					tx.cjsExportAssignment = result
					tx.resultHasScopeMarker = true
					tx.resultHasExternalModuleIndicator = true
				}
			}
		}
		return tx.cjsExportAssignmentVisitor.VisitEachChild(expression) // recur through the whole tree, looking for module.exports=
	}
	return nil
}

func (tx *DeclarationTransformer) visitNestedExpression(expression *ast.Node) *ast.Node {
	if expression != nil {
		_, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression)
		defer cleanupDiagnosticContext()
		switch ast.GetAssignmentDeclarationKind(expression) {
		case ast.JSDeclarationKindProperty:
			tx.transformExpandoAssignment(expression.AsBinaryExpression())
		case ast.JSDeclarationKindExportsProperty:
			if tx.state.currentSourceFile.CommonJSModuleIndicator != nil {
				result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(ast.GetElementOrPropertyAccessName(expression.AsBinaryExpression().Left)))
				if result != nil {
					tx.cjsExportMembers = append(tx.cjsExportMembers, result)
				}
			}
		case ast.JSDeclarationKindObjectDefinePropertyExports:
			if tx.state.currentSourceFile.CommonJSModuleIndicator != nil {
				result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(expression.Arguments()[1]))
				if result != nil {
					tx.cjsExportMembers = append(tx.cjsExportMembers, result)
				}
			}
		}
		return tx.expressionVisitor.VisitEachChild(expression) // recur through the whole tree, looking for special assignments
	}
	return nil
}

func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExpression) {
	left := node.Left

	symbol := node.Symbol
	if symbol == nil || symbol.Flags&ast.SymbolFlagsAssignment == 0 {
		return
	}

	ns := ast.GetLeftmostAccessExpression(left)
	if ns == nil || ns.Kind != ast.KindIdentifier {
		return
	}

	declaration := tx.resolver.GetReferencedValueDeclaration(ns)
	if declaration == nil {
		return
	}

	if tx.shouldStripInternal(declaration) {
		return
	}

	if ast.IsVariableDeclaration(declaration) && declaration.Type() != nil {
		return
	}

	if ast.IsFunctionDeclaration(declaration) && declaration.FunctionLikeData().FullSignature != nil {
		return
	}

	if ast.IsVariableDeclaration(declaration) && !ast.IsFunctionLike(declaration.Initializer()) {
		return // We're going to add a type, no need to dupe members with a namespace
	}

	host := declaration.Symbol()
	if host == nil {
		return
	}

	name := tx.Factory().NewIdentifier(ns.Text())
	property := tx.tryGetPropertyName(left)
	if property == "" || !scanner.IsIdentifierText(property, core.LanguageVariantStandard) {
		return
	}

	hostId := tx.getExpandoHostId(declaration)

	if ast.IsDeclaration(declaration) && isDeclarationAndNotVisible(tx.EmitContext(), tx.resolver, declaration) {
		// The host isn't visible (yet) - printing the type of a visible declaration may still
		// late-mark it as visible (e.g. an exported variable whose type prints as `typeof host`),
		// so defer the assignment to be processed if and when that happens.
		tx.deferredExpandoAssignments[hostId] = append(tx.deferredExpandoAssignments[hostId], node)
		return
	}

	if ast.IsFunctionDeclaration(declaration) && !shouldEmitFunctionProperties(declaration.AsFunctionDeclaration()) {
		return
	}

	tx.transformExpandoHost(name, declaration)

	exportName := tx.Factory().NewIdentifier(property)
	localName := tx.tryGetNameOfAssignedExpression(node.AsNode())
	if localName == nil && !tx.resolver.IsNameResolvable(tx.enclosingDeclaration, property) && !ast.IsNonContextualKeyword(scanner.StringToToken(exportName.Text())) {
		// use exportName as localName if there won't be any conflicts or keyword issues
		localName = exportName
	}
	if localName == nil || ast.IsNonContextualKeyword(scanner.StringToToken(localName.Text())) {
		// fallback to a generated name if the localName doesn't exist or is a keyword
		localName = tx.Factory().NewGeneratedNameForNode(node.AsNode())
	}

	_, cleanupDiagnosticContext := tx.setupDiagnosticContext(node.AsNode())
	defer cleanupDiagnosticContext()

	if ast.IsIdentifier(node.Right) {
		// alias-like, emit an `export {name}` or `export {name as alias}`
		result := tx.transformBinaryExpressionToExportDeclaration(node.AsNode(), exportName)
		tx.expandoMembers[hostId] = append(tx.expandoMembers[hostId], result)
		return
	}

	preexistingExpandoHasExport := core.Some(tx.expandoMembers[hostId], ast.IsExportDeclaration)
	var varModifiers *ast.ModifierList

	if preexistingExpandoHasExport {
		varModifiers = tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(ast.ModifierFlagsExport, tx.Factory().NewModifier))
	}

	synthesizedNamespace := tx.Factory().NewModuleDeclaration(nil /*modifiers*/, ast.KindNamespaceKeyword, name, tx.Factory().NewModuleBlock(tx.Factory().NewNodeList([]*ast.Node{})))
	synthesizedNamespace.Parent = tx.enclosingDeclaration
	declarationData := synthesizedNamespace.DeclarationData()
	declarationData.Symbol = host
	containerData := synthesizedNamespace.LocalsContainerData()
	containerData.Locals = make(ast.SymbolTable, 0)
	containerData.Locals[localName.Text()] = symbol

	oldEnclosing := tx.enclosingDeclaration
	tx.enclosingDeclaration = synthesizedNamespace
	defer func() {
		tx.enclosingDeclaration = oldEnclosing
	}()

	statements := []*ast.Statement{
		tx.Factory().NewVariableStatement(
			varModifiers,
			tx.Factory().NewVariableDeclarationList(
				tx.Factory().NewNodeList([]*ast.Node{
					tx.Factory().NewVariableDeclaration(localName, nil /*exclamationToken*/, tx.ensureType(node.AsNode(), false), nil /*initializer*/),
				}),
				ast.NodeFlagsNone,
			),
		),
	}

	if localName.Text() != exportName.Text() {
		namedExports := tx.Factory().NewNamedExports(tx.Factory().NewNodeList(
			[]*ast.Node{
				tx.Factory().NewExportSpecifier(false /*isTypeOnly*/, localName, exportName),
			},
		))
		statements = append(statements, tx.Factory().NewExportDeclaration(nil /*modifiers*/, false /*isTypeOnly*/, namedExports, nil /*moduleSpecifier*/, nil /*attributes*/))
	}

	if len(statements) > 1 && !preexistingExpandoHasExport {
		// Add an `export` modifier to all existing expando members so they remain exported after the `export {}` is added
		for _, decl := range tx.expandoMembers[hostId] {
			modifierFlags := ast.ModifierFlagsExport | ast.GetCombinedModifierFlags(decl)
			decl.AsMutable().SetModifiers(tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifierFlags, tx.Factory().NewModifier)))
		}
	}
	tx.expandoMembers[hostId] = append(tx.expandoMembers[hostId], statements...)
}

func (tx *DeclarationTransformer) getExpandoHostId(declaration *ast.Declaration) ast.NodeId {
	root := core.IfElse(ast.IsVariableDeclaration(declaration), declaration.Parent.Parent, declaration)
	id := ast.GetNodeId(tx.EmitContext().MostOriginal(root))
	return id
}

func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declaration *ast.Declaration) {
	root := core.IfElse(ast.IsVariableDeclaration(declaration), declaration.Parent.Parent, declaration)
	id := tx.getExpandoHostId(declaration)

	if _, ok := tx.expandoHosts[id]; ok {
		return
	}

	saveNeedsDeclare := tx.needsDeclare
	tx.needsDeclare = true

	modifierFlags := tx.ensureModifierFlags(root)
	defaultExport := modifierFlags&ast.ModifierFlagsExport != 0 && modifierFlags&ast.ModifierFlagsDefault != 0

	tx.needsDeclare = saveNeedsDeclare

	if defaultExport {
		modifierFlags |= ast.ModifierFlagsAmbient
		modifierFlags ^= ast.ModifierFlagsDefault
		modifierFlags ^= ast.ModifierFlagsExport
	}

	_, cleanupDiagnosticContext := tx.setupDiagnosticContext(declaration)
	defer cleanupDiagnosticContext()

	modifiers := tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifierFlags, tx.Factory().NewModifier))
	replacement := make([]*ast.Node, 0)

	if ast.IsFunctionDeclaration(declaration) {
		typeParameters, parameters, asteriskToken := extractExpandoHostParams(declaration)
		replacement = append(replacement, tx.Factory().UpdateFunctionDeclaration(declaration.AsFunctionDeclaration(), modifiers, asteriskToken, declaration.Name(), tx.ensureTypeParams(declaration, typeParameters), tx.updateParamList(declaration, parameters), tx.ensureType(declaration, false), nil /*fullSignature*/, nil /*body*/))
	} else if ast.IsVariableDeclaration(declaration) && ast.IsFunctionExpressionOrArrowFunction(declaration.Initializer()) {
		fn := declaration.Initializer()
		typeParameters, parameters, asteriskToken := extractExpandoHostParams(fn)
		replacement = append(replacement, tx.Factory().NewFunctionDeclaration(modifiers, asteriskToken, tx.Factory().NewIdentifier(name.Text()), tx.ensureTypeParams(fn, typeParameters), tx.updateParamList(fn, parameters), tx.ensureType(fn, false), nil /*fullSignature*/, nil /*body*/))
	} else {
		tx.expandoHosts[id] = tx.transformTopLevelDeclaration(declaration)
		return
	}

	tx.state.reportExpandoFunctionErrors(declaration)

	if defaultExport {
		if ast.IsSourceFile(declaration.Parent) {
			tx.resultHasExternalModuleIndicator = true
		}
		tx.resultHasScopeMarker = true
		replacement = append(replacement, tx.Factory().NewExportAssignment(nil /*modifiers*/, false /*isExportEquals*/, nil /*typeNode*/, name))
	}

	// store host result to be added to the output when it's actually visited
	tx.expandoHosts[id] = tx.Factory().NewSyntaxList(replacement)
	if _, ok := tx.lateStatementReplacementMap[id]; ok {
		tx.lateStatementReplacementMap[id] = tx.createFullExpandoBlock(id)
	}
}

func (tx *DeclarationTransformer) createFullExpandoBlock(id ast.NodeId) *ast.Node {
	// Process any expando assignments on this host that were skipped because it wasn't
	// visible when they were collected - if it's still not visible, they simply get
	// re-deferred, and are dropped if the host is never late-marked visible.
	if deferred, ok := tx.deferredExpandoAssignments[id]; ok {
		delete(tx.deferredExpandoAssignments, id)
		for _, assignment := range deferred {
			tx.transformExpandoAssignment(assignment)
		}
	}
	n := tx.expandoHosts[id]
	if addOns, ok := tx.expandoMembers[id]; ok {
		var modifiers *ast.ModifierList
		var name *ast.Node
		var host []*ast.Node
		if n != nil && n.Kind == ast.KindSyntaxList {
			// find the first named syntax list element and use its' name & modifiers
			for c := range n.AsSyntaxList().IterChildren() {
				if c.Name() != nil {
					name = c.Name().Clone(tx.Factory())
					if c.Modifiers() != nil {
						modifiers = c.Modifiers().Clone(tx.Factory().AsNodeFactory())
					}
					break
				}
			}
			host = n.AsSyntaxList().Children
		} else if n != nil {
			name = n.Name().Clone(tx.Factory())
			if n.Modifiers() != nil {
				modifiers = n.Modifiers().Clone(tx.Factory().AsNodeFactory())
			}
			host = []*ast.Node{n}
		}
		if name != nil {
			moduleDecl := tx.Factory().NewModuleDeclaration(
				modifiers,
				ast.KindNamespaceKeyword,
				name,
				tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(addOns)),
			)
			members := append(host, moduleDecl)
			return tx.Factory().NewSyntaxList(members)
		}
	}
	return n
}

func extractExpandoHostParams(node *ast.Node) (typeParameters *ast.TypeParameterList, parameters *ast.ParameterList, asteriskToken *ast.TokenNode) {
	switch node.Kind {
	case ast.KindFunctionExpression:
		fn := node.AsFunctionExpression()
		return fn.TypeParameters, fn.Parameters, fn.AsteriskToken
	case ast.KindArrowFunction:
		fn := node.AsArrowFunction()
		return fn.TypeParameters, fn.Parameters, fn.AsteriskToken
	default:
		fn := node.AsFunctionDeclaration()
		return fn.TypeParameters, fn.Parameters, fn.AsteriskToken
	}
}

func (tx *DeclarationTransformer) tryGetPropertyName(node *ast.Node) string {
	if ast.IsElementAccessExpression(node) {
		return tx.resolver.GetElementAccessExpressionName(node.AsElementAccessExpression())
	}
	if ast.IsPropertyAccessExpression(node) {
		return node.Name().Text()
	}
	return ""
}
