package checker

import (
	"maps"
	"slices"
	"sync"

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/binder"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/evaluator"
	"github.com/microsoft/typescript-go/internal/jsnum"
	"github.com/microsoft/typescript-go/internal/nodebuilder"
	"github.com/microsoft/typescript-go/internal/printer"
)

var _ printer.EmitResolver = (*EmitResolver)(nil)

// Links for jsx
type JSXLinks struct {
	importRef *ast.Node
}

// Links for declarations

type DeclarationLinks struct {
	isVisible core.Tristate // if declaration is depended upon by exported declarations
}

type DeclarationFileLinks struct {
	aliasesMarked bool // if file has had alias visibility marked
}

type EmitResolver struct {
	checker                 *Checker
	checkerMu               *sync.Mutex
	isValueAliasDeclaration func(node *ast.Node) bool
	aliasMarkingVisitor     func(node *ast.Node) bool
	referenceResolver       binder.ReferenceResolver
	jsxLinks                core.LinkStore[*ast.Node, JSXLinks]
	declarationLinks        core.LinkStore[*ast.Node, DeclarationLinks]
	declarationFileLinks    core.LinkStore[*ast.Node, DeclarationFileLinks]
}

func newEmitResolver(checker *Checker) *EmitResolver {
	e := &EmitResolver{checker: checker}
	e.isValueAliasDeclaration = e.isValueAliasDeclarationWorker
	e.aliasMarkingVisitor = e.aliasMarkingVisitorWorker
	e.checkerMu = &checker.mu
	return e
}

func (r *EmitResolver) GetJsxFactoryEntity(location *ast.Node) *ast.Node {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.checker.getJsxFactoryEntity(location)
}

func (r *EmitResolver) GetJsxFragmentFactoryEntity(location *ast.Node) *ast.Node {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.checker.getJsxFragmentFactoryEntity(location)
}

func (r *EmitResolver) IsOptionalParameter(node *ast.Node) bool {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.isOptionalParameter(node)
}

func (r *EmitResolver) IsLateBound(node *ast.Node) bool {
	// TODO: Require an emitContext to construct an EmitResolver, remove all emitContext arguments
	// node = r.emitContext.ParseNode(node)
	if node == nil {
		return false
	}
	if !ast.IsParseTreeNode(node) {
		return false
	}
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	symbol := r.checker.getSymbolOfDeclaration(node)
	if symbol == nil {
		return false
	}
	return symbol.CheckFlags&ast.CheckFlagsLate != 0
}

func (r *EmitResolver) GetEnumMemberValue(node *ast.Node) evaluator.Result {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(node) {
		return evaluator.NewResult(nil, false, false, false)
	}
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	r.checker.computeEnumMemberValues(node.Parent)
	if !r.checker.enumMemberLinks.Has(node) {
		return evaluator.NewResult(nil, false, false, false)
	}
	return r.checker.enumMemberLinks.Get(node).value
}

func (r *EmitResolver) IsDeclarationVisible(node *ast.Node) bool {
	// Only lock on external API func to prevent deadlocks
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.isDeclarationVisible(node)
}

func (r *EmitResolver) isDeclarationVisible(node *ast.Node) bool {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(node) {
		return false
	}
	if node == nil {
		return false
	}

	links := r.declarationLinks.Get(node)
	if links.isVisible == core.TSUnknown {
		if r.determineIfDeclarationIsVisible(node) {
			links.isVisible = core.TSTrue
		} else {
			links.isVisible = core.TSFalse
		}
	}
	return links.isVisible == core.TSTrue
}

func (r *EmitResolver) determineIfDeclarationIsVisible(node *ast.Node) bool {
	switch node.Kind {
	case ast.KindJSDocCallbackTag,
		// ast.KindJSDocEnumTag, // !!! TODO: JSDoc @enum support?
		ast.KindJSDocTypedefTag:
		// Top-level jsdoc type aliases are considered exported
		// First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file
		return node.Parent != nil && node.Parent.Parent != nil && node.Parent.Parent.Parent != nil && ast.IsSourceFile(node.Parent.Parent.Parent)
	case ast.KindBindingElement:
		return r.isDeclarationVisible(node.Parent.Parent)
	case ast.KindVariableDeclaration,
		ast.KindModuleDeclaration,
		ast.KindClassDeclaration,
		ast.KindInterfaceDeclaration,
		ast.KindTypeAliasDeclaration,
		ast.KindJSTypeAliasDeclaration,
		ast.KindFunctionDeclaration,
		ast.KindEnumDeclaration,
		ast.KindImportEqualsDeclaration:
		if ast.IsVariableDeclaration(node) {
			if ast.IsBindingPattern(node.Name()) &&
				len(node.Name().Elements()) == 0 {
				// If the binding pattern is empty, this variable declaration is not visible
				return false
			}
			// falls through
		}
		// External module augmentation is always visible
		// A @typedef at top-level in an external module is always visible
		if ast.IsExternalModuleAugmentation(node) || ast.IsImplicitlyExportedJSDocDeclaration(node) {
			return true
		}
		parent := ast.GetDeclarationContainer(node)
		// If the node is not exported or it is not ambient module element (except import declaration)
		if r.checker.getCombinedModifierFlagsCached(node)&ast.ModifierFlagsExport == 0 &&
			!(node.Kind != ast.KindImportEqualsDeclaration && parent.Kind != ast.KindSourceFile && parent.Flags&ast.NodeFlagsAmbient != 0) {
			return ast.IsGlobalSourceFile(parent)
		}
		// Exported members/ambient module elements (exception import declaration) are visible if parent is visible
		return r.isDeclarationVisible(parent)

	case ast.KindPropertyDeclaration,
		ast.KindPropertySignature,
		ast.KindGetAccessor,
		ast.KindSetAccessor,
		ast.KindMethodDeclaration,
		ast.KindMethodSignature:
		if r.checker.GetEffectiveDeclarationFlags(node, ast.ModifierFlagsPrivate|ast.ModifierFlagsProtected) != 0 {
			// Private/protected properties/methods are not visible
			return false
		}
		// Public properties/methods are visible if its parents are visible, so:
		return r.isDeclarationVisible(node.Parent)

	case ast.KindConstructor,
		ast.KindConstructSignature,
		ast.KindCallSignature,
		ast.KindIndexSignature,
		ast.KindParameter,
		ast.KindModuleBlock,
		ast.KindFunctionType,
		ast.KindConstructorType,
		ast.KindTypeLiteral,
		ast.KindTypeReference,
		ast.KindArrayType,
		ast.KindTupleType,
		ast.KindUnionType,
		ast.KindIntersectionType,
		ast.KindParenthesizedType,
		ast.KindNamedTupleMember:
		return r.isDeclarationVisible(node.Parent)

	// Default binding, import specifier and namespace import is visible
	// only on demand so by default it is not visible
	case ast.KindImportClause,
		ast.KindNamespaceImport,
		ast.KindImportSpecifier:
		return false

	// Type parameters are always visible
	case ast.KindTypeParameter:
		return true
	// Source file and namespace export are always visible
	case ast.KindSourceFile,
		ast.KindNamespaceExportDeclaration:
		return true

	// Export assignments do not create name bindings outside the module
	case ast.KindExportAssignment:
		return false

	// An `export {X}` (without a module specifier) is itself a visible re-export of
	// the named binding; it contributes to the symbol's external visibility.
	case ast.KindExportSpecifier:
		exportDecl := node.Parent.Parent
		if ast.IsExportDeclaration(exportDecl) && exportDecl.AsExportDeclaration().ModuleSpecifier == nil {
			return r.isDeclarationVisible(exportDecl.Parent)
		}
		return false

	default:
		return false
	}
}

func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFile) {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	if r.declarationFileLinks.Get(file.AsNode()).aliasesMarked {
		return
	}
	r.declarationFileLinks.Get(file.AsNode()).aliasesMarked = true
	// TODO: Does this even *have* to be an upfront walk? If it's not possible for a
	// import a = a.b.c statement to chain into exposing a statement in a sibling scope,
	// it could at least be pushed into scope entry -  then it wouldn't need to be recursive.
	file.AsNode().ForEachChild(r.aliasMarkingVisitor)
}

func isCommonJSModuleExports(node *ast.Node) bool {
	if ast.IsBinaryExpression(node) && ast.IsExpressionStatement(node.Parent) && ast.IsSourceFile(node.Parent.Parent) &&
		node.Parent.Parent.AsSourceFile().CommonJSModuleIndicator != nil {
		switch ast.GetAssignmentDeclarationKind(node) {
		case ast.JSDeclarationKindModuleExports, ast.JSDeclarationKindExportsProperty:
			return true
		}
	}
	return false
}

func (r *EmitResolver) aliasMarkingVisitorWorker(node *ast.Node) bool {
	switch node.Kind {
	case ast.KindBinaryExpression:
		if isCommonJSModuleExports(node) && ast.IsIdentifier(node.AsBinaryExpression().Right) {
			r.markLinkedAliases(node.AsBinaryExpression().Right)
		}
	case ast.KindExportAssignment:
		if node.Expression().Kind == ast.KindIdentifier {
			r.markLinkedAliases(node.Expression())
		}
	case ast.KindExportSpecifier:
		r.markLinkedAliases(node.PropertyNameOrName())
	}
	return node.ForEachChild(r.aliasMarkingVisitor)
}

// Sets the isVisible link on statements the Identifier or ExportName node points at
// Follows chains of import d = a.b.c
func (r *EmitResolver) markLinkedAliases(node *ast.Node) {
	var exportSymbol *ast.Symbol
	if node.Kind != ast.KindStringLiteral && node.Parent != nil && (ast.IsExportAssignment(node.Parent) || isCommonJSModuleExports(node.Parent)) {
		exportSymbol = r.checker.resolveName(node, node.Text(), ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias /*nameNotFoundMessage*/, nil /*isUse*/, false, false)
	} else if node.Parent.Kind == ast.KindExportSpecifier {
		exportSymbol = r.checker.getTargetOfExportSpecifier(node.Parent, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias, false)
	}

	visited := make(map[ast.SymbolId]struct{}, 2) // guard against circular imports
	for exportSymbol != nil {
		_, seen := visited[ast.GetSymbolId(exportSymbol)]
		if seen {
			break
		}
		visited[ast.GetSymbolId(exportSymbol)] = struct{}{}

		var nextSymbol *ast.Symbol
		for _, declaration := range exportSymbol.Declarations {
			r.declarationLinks.Get(declaration).isVisible = core.TSTrue

			if ast.IsInternalModuleImportEqualsDeclaration(declaration) {
				// Add the referenced top container visible
				internalModuleReference := declaration.AsImportEqualsDeclaration().ModuleReference
				firstIdentifier := ast.GetFirstIdentifier(internalModuleReference)
				importSymbol := r.checker.resolveName(declaration, firstIdentifier.Text(), ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias /*nameNotFoundMessage*/, nil /*isUse*/, false, false)
				nextSymbol = importSymbol
			}
		}

		exportSymbol = nextSymbol
	}
}

func getMeaningOfEntityNameReference(entityName *ast.Node) ast.SymbolFlags {
	// get symbol of the first identifier of the entityName
	if entityName.Parent.Kind == ast.KindTypeQuery ||
		entityName.Parent.Kind == ast.KindExpressionWithTypeArguments && !ast.IsPartOfTypeNode(entityName.Parent) ||
		entityName.Parent.Kind == ast.KindComputedPropertyName ||
		entityName.Parent.Kind == ast.KindTypePredicate && entityName.Parent.AsTypePredicateNode().ParameterName == entityName ||
		entityName.Parent.Kind == ast.KindBinaryExpression {
		// Typeof value
		return ast.SymbolFlagsValue | ast.SymbolFlagsExportValue
	}
	if entityName.Kind == ast.KindQualifiedName || entityName.Kind == ast.KindPropertyAccessExpression ||
		entityName.Parent.Kind == ast.KindImportEqualsDeclaration ||
		(entityName.Parent.Kind == ast.KindQualifiedName && entityName.Parent.AsQualifiedName().Left == entityName) ||
		(entityName.Parent.Kind == ast.KindPropertyAccessExpression && entityName.Parent.Expression() == entityName) ||
		(entityName.Parent.Kind == ast.KindElementAccessExpression && entityName.Parent.Expression() == entityName) {
		// Left identifier from type reference or TypeAlias
		// Entity name of the import declaration
		return ast.SymbolFlagsNamespace
	}
	// Type Reference or TypeAlias entity = Identifier
	return ast.SymbolFlagsType
}

func (r *EmitResolver) IsEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node) printer.SymbolAccessibilityResult {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.isEntityNameVisible(entityName, enclosingDeclaration, true)
}

func (r *EmitResolver) isEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node, shouldComputeAliasToMakeVisible bool) printer.SymbolAccessibilityResult {
	// node = r.emitContext.ParseNode(entityName)
	if !ast.IsParseTreeNode(entityName) {
		return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityNotAccessible}
	}

	meaning := getMeaningOfEntityNameReference(entityName)
	firstIdentifier := ast.GetFirstIdentifier(entityName)

	symbol := r.checker.resolveName(enclosingDeclaration, firstIdentifier.Text(), meaning, nil, false, false)

	if symbol != nil && symbol.Flags&ast.SymbolFlagsTypeParameter != 0 && meaning&ast.SymbolFlagsType != 0 {
		return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible}
	}

	if symbol == nil && ast.IsThisIdentifier(firstIdentifier) {
		sym := r.checker.getSymbolOfDeclaration(r.checker.getThisContainer(firstIdentifier, false, false))
		if r.isSymbolAccessible(sym, enclosingDeclaration, meaning, false).Accessibility == printer.SymbolAccessibilityAccessible {
			return printer.SymbolAccessibilityResult{Accessibility: printer.SymbolAccessibilityAccessible}
		}
	}

	if symbol == nil {
		return printer.SymbolAccessibilityResult{
			Accessibility:   printer.SymbolAccessibilityNotResolved,
			ErrorSymbolName: firstIdentifier.Text(),
			ErrorNode:       firstIdentifier,
		}
	}

	visible := r.hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible)
	if visible != nil {
		return *visible
	}

	return printer.SymbolAccessibilityResult{
		Accessibility:   printer.SymbolAccessibilityNotAccessible,
		ErrorSymbolName: firstIdentifier.Text(),
		ErrorNode:       firstIdentifier,
	}
}

func noopAddVisibleAlias(declaration *ast.Node, aliasingStatement *ast.Node) {}

func (r *EmitResolver) hasVisibleDeclarations(symbol *ast.Symbol, shouldComputeAliasToMakeVisible bool) *printer.SymbolAccessibilityResult {
	var aliasesToMakeVisibleSet map[ast.NodeId]*ast.Node

	var addVisibleAlias func(declaration *ast.Node, aliasingStatement *ast.Node)
	if shouldComputeAliasToMakeVisible {
		addVisibleAlias = func(declaration *ast.Node, aliasingStatement *ast.Node) {
			r.declarationLinks.Get(declaration).isVisible = core.TSTrue
			if aliasesToMakeVisibleSet == nil {
				aliasesToMakeVisibleSet = make(map[ast.NodeId]*ast.Node)
			}
			aliasesToMakeVisibleSet[ast.GetNodeId(declaration)] = aliasingStatement
		}
	} else {
		addVisibleAlias = noopAddVisibleAlias
	}

	for _, declaration := range symbol.Declarations {
		if ast.IsIdentifier(declaration) {
			continue
		}
		if !r.isDeclarationVisible(declaration) {
			// Mark the unexported alias as visible if its parent is visible
			// because these kind of aliases can be used to name types in declaration file
			anyImportSyntax := getAnyImportSyntax(declaration)
			if anyImportSyntax != nil &&
				!ast.HasSyntacticModifier(anyImportSyntax, ast.ModifierFlagsExport) && // import clause without export
				r.isDeclarationVisible(anyImportSyntax.Parent) {
				addVisibleAlias(declaration, anyImportSyntax)
				continue
			}
			if ast.IsVariableDeclaration(declaration) && ast.IsVariableStatement(declaration.Parent.Parent) &&
				!ast.HasSyntacticModifier(declaration.Parent.Parent, ast.ModifierFlagsExport) && // unexported variable statement
				r.isDeclarationVisible(declaration.Parent.Parent.Parent) {
				addVisibleAlias(declaration, declaration.Parent.Parent)
				continue
			}
			if ast.IsLateVisibilityPaintedStatement(declaration) && // unexported top-level statement
				!ast.HasSyntacticModifier(declaration, ast.ModifierFlagsExport) &&
				r.isDeclarationVisible(declaration.Parent) {
				addVisibleAlias(declaration, declaration)
				continue
			}
			if ast.IsBindingElement(declaration) {
				if symbol.Flags&ast.SymbolFlagsAlias != 0 && ast.IsInJSFile(declaration) && declaration.Parent != nil && declaration.Parent.Parent != nil && // exported import-like top-level JS require statement
					ast.IsVariableDeclaration(declaration.Parent.Parent) &&
					declaration.Parent.Parent.Parent.Parent != nil && ast.IsVariableStatement(declaration.Parent.Parent.Parent.Parent) &&
					!ast.HasSyntacticModifier(declaration.Parent.Parent.Parent.Parent, ast.ModifierFlagsExport) &&
					declaration.Parent.Parent.Parent.Parent.Parent != nil && // check if the thing containing the variable statement is visible (ie, the file)
					r.isDeclarationVisible(declaration.Parent.Parent.Parent.Parent.Parent) {
					addVisibleAlias(declaration, declaration.Parent.Parent.Parent.Parent)
					continue
				}
				if symbol.Flags&ast.SymbolFlagsBlockScopedVariable != 0 {
					rootDeclaration := ast.WalkUpBindingElementsAndPatterns(declaration)
					if ast.IsParameterDeclaration(rootDeclaration) {
						return nil
					}
					variableStatement := rootDeclaration.Parent.Parent
					if !ast.IsVariableStatement(variableStatement) {
						return nil
					}
					if ast.HasSyntacticModifier(variableStatement, ast.ModifierFlagsExport) {
						continue // no alias to add, already exported
					}
					if !r.isDeclarationVisible(variableStatement.Parent) {
						return nil // not visible
					}
					addVisibleAlias(declaration, variableStatement)
					continue
				}
			}

			// Declaration is not visible
			return nil
		}
	}

	return &printer.SymbolAccessibilityResult{
		Accessibility:        printer.SymbolAccessibilityAccessible,
		AliasesToMakeVisible: slices.Collect(maps.Values(aliasesToMakeVisibleSet)),
	}
}

func (r *EmitResolver) IsImplementationOfOverload(node *ast.SignatureDeclaration) bool {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(node) {
		return false
	}
	if ast.NodeIsPresent(node.Body()) {
		if ast.IsGetAccessorDeclaration(node) || ast.IsSetAccessorDeclaration(node) {
			return false // Get or set accessors can never be overload implementations, but can have up to 2 signatures
		}
		r.checkerMu.Lock()
		defer r.checkerMu.Unlock()
		symbol := r.checker.getSymbolOfDeclaration(node)
		signaturesOfSymbol := r.checker.getSignaturesOfSymbol(symbol)
		// If this function body corresponds to function with multiple signature, it is implementation of overload
		// e.g.: function foo(a: string): string;
		//       function foo(a: number): number;
		//       function foo(a: any) { // This is implementation of the overloads
		//           return a;
		//       }
		if len(signaturesOfSymbol) > 1 {
			return true
		}
		// If there is single signature for the symbol, it is overload if that signature isn't coming from the node
		// e.g.: function foo(a: string): string;
		//       function foo(a: any) { // This is implementation of the overloads
		//           return a;
		//       }
		if len(signaturesOfSymbol) == 1 {
			declaration := signaturesOfSymbol[0].declaration
			if declaration != node && declaration.Flags&ast.NodeFlagsJSDoc == 0 {
				return true
			}
		}
	}
	return false
}

func (r *EmitResolver) IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(decl.AsNode()) {
		return false
	}
	file := ast.GetSourceFileOfNode(decl.AsNode())
	if file.Symbol == nil {
		// script file
		return false
	}
	importTarget := r.GetExternalModuleFileFromDeclaration(decl.AsNode())
	if importTarget == nil {
		return false
	}
	if importTarget == file {
		return false
	}
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	exports := r.checker.getExportsOfModule(file.Symbol)
	for s := range maps.Values(exports) {
		merged := r.checker.getMergedSymbol(s)
		if merged != s {
			if len(merged.Declarations) > 0 {
				for _, d := range merged.Declarations {
					declFile := ast.GetSourceFileOfNode(d)
					if declFile == importTarget {
						return true
					}
				}
			}
		}
	}
	return false
}

func (r *EmitResolver) IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool {
	if !ast.IsPropertyAccessExpression(node) ||
		!ast.IsIdentifier(node.Name()) ||
		!ast.IsPropertyAccessExpression(node.Expression()) && !ast.IsIdentifier(node.Expression()) {
		return false
	}
	if node.Expression().Kind == ast.KindIdentifier {
		if node.Expression().Text() != "Symbol" {
			return false
		}
		r.checkerMu.Lock()
		defer r.checkerMu.Unlock()
		// Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol
		return r.checker.getResolvedSymbol(node.Expression()) == r.checker.getGlobalSymbol("Symbol", ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*diagnostic*/)
	}
	if node.Expression().Expression().Kind != ast.KindIdentifier || node.Expression().Expression().Text() != "globalThis" || node.Expression().Name().Text() != "Symbol" {
		return false
	}
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	// Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis`
	return r.checker.getResolvedSymbol(node.Expression().Expression()) == r.checker.globalThisSymbol
}

func (r *EmitResolver) RequiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
	if !ast.IsParseTreeNode(declaration) {
		return false
	}
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration)
}

func (r *EmitResolver) RequiresAddingImplicitUndefinedUnsafe(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
	if !ast.IsParseTreeNode(declaration) {
		return false
	}
	// NO LOCKING - only should be called in contexts that already have a checker lock
	return r.requiresAddingImplicitUndefined(declaration, symbol, enclosingDeclaration)
}

func (r *EmitResolver) requiresAddingImplicitUndefined(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(declaration) {
		return false
	}
	switch declaration.Kind {
	case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindJSDocPropertyTag:
		if symbol == nil {
			symbol = r.checker.getSymbolOfDeclaration(declaration)
		}
		t := r.checker.getTypeOfSymbol(symbol)
		r.checker.mappedSymbolLinks.Has(symbol)
		return (symbol.Flags&ast.SymbolFlagsProperty != 0) && (symbol.Flags&ast.SymbolFlagsOptional != 0) && isOptionalDeclaration(declaration) && r.checker.ReverseMappedSymbolLinks.Has(symbol) && r.checker.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil && containsNonMissingUndefinedType(r.checker, t)
	case ast.KindParameter, ast.KindJSDocParameterTag:
		return r.requiresAddingImplicitUndefinedWorker(declaration, enclosingDeclaration)
	default:
		panic("Node cannot possibly require adding undefined")
	}
}

func (r *EmitResolver) requiresAddingImplicitUndefinedWorker(parameter *ast.Node, enclosingDeclaration *ast.Node) bool {
	return (r.isRequiredInitializedParameter(parameter, enclosingDeclaration) || r.isOptionalUninitializedParameterProperty(parameter)) && !r.declaredParameterTypeContainsUndefined(parameter)
}

func (r *EmitResolver) declaredParameterTypeContainsUndefined(parameter *ast.Node) bool {
	// typeNode := getNonlocalEffectiveTypeAnnotationNode(parameter); // !!! JSDoc Support
	typeNode := parameter.Type()
	if typeNode == nil {
		return false
	}
	t := r.checker.getTypeFromTypeNode(typeNode)
	// allow error type here to avoid confusing errors that the annotation has to contain undefined when it does in cases like this:
	//
	// export function fn(x?: Unresolved | undefined): void {}
	return r.checker.isErrorType(t) || r.checker.containsUndefinedType(t)
}

func (r *EmitResolver) isOptionalUninitializedParameterProperty(parameter *ast.Node) bool {
	return r.checker.strictNullChecks &&
		r.isOptionalParameter(parameter) &&
		( /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil) && // !!! TODO: JSDoc support
		ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier)
}

func (r *EmitResolver) isRequiredInitializedParameter(parameter *ast.Node, enclosingDeclaration *ast.Node) bool {
	if !r.checker.strictNullChecks || r.isOptionalParameter(parameter) || /*isJSDocParameterTag(parameter) ||*/ parameter.Initializer() == nil { // !!! TODO: JSDoc Support
		return false
	}
	if ast.HasSyntacticModifier(parameter, ast.ModifierFlagsParameterPropertyModifier) {
		return enclosingDeclaration != nil && ast.IsFunctionLikeDeclaration(enclosingDeclaration)
	}
	return true
}

func (r *EmitResolver) isOptionalParameter(node *ast.Node) bool {
	return r.checker.isOptionalParameter(node)
}

func (r *EmitResolver) IsLiteralConstDeclaration(node *ast.Node) bool {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(node) {
		return false
	}
	if isDeclarationReadonly(node) || ast.IsVariableDeclaration(node) && ast.IsVarConst(node) {
		r.checkerMu.Lock()
		defer r.checkerMu.Unlock()
		return isFreshLiteralType(r.checker.getTypeOfSymbol(r.checker.getSymbolOfDeclaration(node)))
	}
	return false
}

func (r *EmitResolver) IsExpandoFunctionDeclarationUnsafe(node *ast.Node) bool {
	// node = r.emitContext.ParseNode(node)
	if !ast.IsParseTreeNode(node) {
		return false
	}
	// this is substantially different from strada, but so is expando property checking
	props := r.GetPropertiesOfContainerFunction(node)
	for _, p := range props {
		if ast.IsExpandoPropertyDeclaration(p.ValueDeclaration) {
			return true
		}
	}
	return false
}

func (r *EmitResolver) IsExpandoFunctionDeclaration(node *ast.Node) bool {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.IsExpandoFunctionDeclarationUnsafe(node)
}

func (r *EmitResolver) isSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) printer.SymbolAccessibilityResult {
	return r.checker.IsSymbolAccessible(symbol, enclosingDeclaration, meaning, shouldComputeAliasToMarkVisible)
}

func (r *EmitResolver) IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) printer.SymbolAccessibilityResult {
	// TODO: Split into locking and non-locking API methods - only current usage is the symbol tracker, which is non-locking,
	// as all tracker calls happen within a CreateX call below, which already holds a lock
	// r.checkerMu.Lock()
	// defer r.checkerMu.Unlock()
	return r.isSymbolAccessible(symbol, enclosingDeclaration, meaning, shouldComputeAliasToMarkVisible)
}

func isConstEnumOrConstEnumOnlyModule(s *ast.Symbol) bool {
	return isConstEnumSymbol(s) || s.Flags&ast.SymbolFlagsConstEnumOnlyModule != 0
}

func (r *EmitResolver) IsReferencedAliasDeclaration(node *ast.Node) bool {
	c := r.checker
	if !c.canCollectSymbolAliasAccessibilityData || !ast.IsParseTreeNode(node) {
		return true
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	if ast.IsAliasSymbolDeclaration(node) {
		if symbol := c.getSymbolOfDeclaration(node); symbol != nil {
			aliasLinks := c.aliasSymbolLinks.Get(symbol)
			if aliasLinks.referenced {
				return true
			}
			target := aliasLinks.aliasTarget
			if target != nil && node.ModifierFlags()&ast.ModifierFlagsExport != 0 &&
				c.getSymbolFlags(target)&ast.SymbolFlagsValue != 0 &&
				(c.compilerOptions.ShouldPreserveConstEnums() || !isConstEnumOrConstEnumOnlyModule(target)) {
				return true
			}
		}
	}
	return false
}

func (r *EmitResolver) IsValueAliasDeclaration(node *ast.Node) bool {
	c := r.checker
	if !c.canCollectSymbolAliasAccessibilityData || !ast.IsParseTreeNode(node) {
		return true
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.isValueAliasDeclarationWorker(node)
}

func (r *EmitResolver) isValueAliasDeclarationWorker(node *ast.Node) bool {
	c := r.checker

	switch node.Kind {
	case ast.KindImportEqualsDeclaration:
		return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), false /*excludeTypeOnlyValues*/)
	case ast.KindImportClause,
		ast.KindNamespaceImport,
		ast.KindImportSpecifier,
		ast.KindExportSpecifier:
		symbol := c.getSymbolOfDeclaration(node)
		return symbol != nil && r.isAliasResolvedToValue(symbol, true /*excludeTypeOnlyValues*/)
	case ast.KindExportDeclaration:
		exportClause := node.AsExportDeclaration().ExportClause
		return exportClause != nil && (ast.IsNamespaceExport(exportClause) ||
			core.Some(exportClause.Elements(), r.isValueAliasDeclaration))
	case ast.KindExportAssignment:
		if node.Expression() != nil && node.Expression().Kind == ast.KindIdentifier {
			return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), true /*excludeTypeOnlyValues*/)
		}
		return true
	case ast.KindBinaryExpression:
		if isCommonJSModuleExports(node) && ast.IsIdentifier(node.AsBinaryExpression().Right) {
			return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), true /*excludeTypeOnlyValues*/)
		}
	}
	return false
}

func (r *EmitResolver) isAliasResolvedToValue(symbol *ast.Symbol, excludeTypeOnlyValues bool) bool {
	c := r.checker
	if symbol == nil {
		return false
	}
	if symbol.ValueDeclaration != nil {
		if container := ast.GetSourceFileOfNode(symbol.ValueDeclaration); container != nil {
			fileSymbol := c.getSymbolOfDeclaration(container.AsNode())
			// Ensures cjs export assignment is setup, since this symbol may point at, and merge with, the file itself.
			// If we don't, the merge may not have yet occurred, and the flags check below will be missing flags that
			// are added as a result of the merge.
			c.resolveExternalModuleSymbol(fileSymbol, false /*dontResolveAlias*/)
		}
	}
	target := c.getExportSymbolOfValueSymbolIfExported(c.resolveAlias(symbol))
	if target == c.unknownSymbol {
		return !excludeTypeOnlyValues || c.getTypeOnlyAliasDeclaration(symbol) == nil
	}
	// const enums and modules that contain only const enums are not considered values from the emit perspective
	// unless 'preserveConstEnums' option is set to true
	return c.getSymbolFlagsEx(symbol, excludeTypeOnlyValues, true /*excludeLocalMeanings*/)&ast.SymbolFlagsValue != 0 &&
		(c.compilerOptions.ShouldPreserveConstEnums() ||
			!isConstEnumOrConstEnumOnlyModule(target))
}

func (r *EmitResolver) IsTopLevelValueImportEqualsWithEntityName(node *ast.Node) bool {
	c := r.checker
	if !c.canCollectSymbolAliasAccessibilityData {
		return true
	}
	if !ast.IsParseTreeNode(node) || node.Kind != ast.KindImportEqualsDeclaration || node.Parent.Kind != ast.KindSourceFile {
		return false
	}
	if ast.IsImportEqualsDeclaration(node) &&
		(ast.NodeIsMissing(node.AsImportEqualsDeclaration().ModuleReference) || node.AsImportEqualsDeclaration().ModuleReference.Kind == ast.KindExternalModuleReference) {
		return false
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.isAliasResolvedToValue(c.getSymbolOfDeclaration(node), false /*excludeTypeOnlyValues*/)
}

func (r *EmitResolver) MarkLinkedReferencesRecursively(file *ast.SourceFile) {
	if !ast.IsParseTreeNode(file.AsNode()) {
		return
	}
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	if file != nil {
		var visit ast.Visitor
		visit = func(n *ast.Node) bool {
			if ast.IsImportEqualsDeclaration(n) && n.ModifierFlags()&ast.ModifierFlagsExport == 0 {
				return false // These are deferred and marked in a chain when referenced
			}
			if ast.IsImportDeclaration(n) {
				return false // likewise, these are ultimately what get marked by calls on other nodes - we want to skip them
			}
			r.checker.markLinkedReferences(n, ReferenceHintUnspecified, nil /*propSymbol*/, nil /*parentType*/)
			n.ForEachChild(visit)
			return false
		}
		file.ForEachChild(visit)
	}
}

func (r *EmitResolver) GetExternalModuleFileFromDeclaration(declaration *ast.Node) *ast.SourceFile {
	if !ast.IsParseTreeNode(declaration) {
		return nil
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.checker.getExternalModuleFileFromDeclaration(declaration)
}

func (r *EmitResolver) getReferenceResolver() binder.ReferenceResolver {
	if r.referenceResolver == nil {
		r.referenceResolver = binder.NewReferenceResolver(r.checker.compilerOptions, binder.ReferenceResolverHooks{
			ResolveName:                            r.checker.resolveName,
			GetResolvedSymbol:                      r.checker.getResolvedSymbolOrNil,
			GetMergedSymbol:                        r.checker.getMergedSymbol,
			GetParentOfSymbol:                      r.checker.getParentOfSymbol,
			GetSymbolOfDeclaration:                 r.checker.getSymbolOfDeclaration,
			GetTypeOnlyAliasDeclaration:            r.checker.getTypeOnlyAliasDeclarationEx,
			GetExportSymbolOfValueSymbolIfExported: r.checker.getExportSymbolOfValueSymbolIfExported,
			GetElementAccessExpressionName:         r.checker.tryGetElementAccessExpressionName,
		})
	}
	return r.referenceResolver
}

func (r *EmitResolver) GetReferencedExportContainer(node *ast.IdentifierNode, prefixLocals bool) *ast.Node /*SourceFile|ModuleDeclaration|EnumDeclaration*/ {
	if !ast.IsParseTreeNode(node) {
		return nil
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.getReferenceResolver().GetReferencedExportContainer(node, prefixLocals)
}

func (r *EmitResolver) SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	r.jsxLinks.Get(node).importRef = ref
}

func (r *EmitResolver) GetReferencedImportDeclaration(node *ast.IdentifierNode) *ast.Declaration {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	if !ast.IsParseTreeNode(node) {
		return r.jsxLinks.Get(node).importRef
	}

	symbol := r.checker.getReferencedValueOrAliasSymbol(node)
	if ast.IsNonLocalAlias(symbol, ast.SymbolFlagsValue) && r.checker.getTypeOnlyAliasDeclarationEx(symbol, ast.SymbolFlagsValue) == nil {
		return r.checker.getDeclarationOfAliasSymbol(symbol)
	}
	return nil
}

func (r *EmitResolver) GetReferencedValueDeclaration(node *ast.IdentifierNode) *ast.Declaration {
	if !ast.IsParseTreeNode(node) {
		return nil
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.getReferenceResolver().GetReferencedValueDeclaration(node)
}

func (r *EmitResolver) GetReferencedValueDeclarationUnsafe(node *ast.IdentifierNode) *ast.Declaration {
	return r.getReferenceResolver().GetReferencedValueDeclaration(node)
}

func (r *EmitResolver) GetReferencedValueDeclarations(node *ast.IdentifierNode) []*ast.Declaration {
	if !ast.IsParseTreeNode(node) {
		return nil
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.getReferenceResolver().GetReferencedValueDeclarations(node)
}

// IsNameResolvable returns `true` if the given `name` resolves to any symbol at `location`
func (r *EmitResolver) IsNameResolvable(location *ast.Node, name string) bool {
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	symbol := r.checker.resolveName(location, name, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, nil /*nameNotFoundMessage*/, false /*isUse*/, false /*excludeGlobals*/)
	return symbol != nil
}

func (r *EmitResolver) GetElementAccessExpressionName(expression *ast.ElementAccessExpression) string {
	if !ast.IsParseTreeNode(expression.AsNode()) {
		return ""
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.getReferenceResolver().GetElementAccessExpressionName(expression)
}

func (r *EmitResolver) GetReferencedMemberValueDeclaration(node *ast.Node) *ast.Declaration {
	if !ast.IsParseTreeNode(node) {
		return nil
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	return r.getReferenceResolver().GetReferencedMemberValueDeclaration(node)
}

// TODO: the emit resolver being responsible for some amount of node construction is a very leaky abstraction,
// and requires giving it access to a lot of context it's otherwise not required to have, which also further complicates the API
// and likely reduces performance. There's probably some refactoring that could be done here to simplify this.

func (r *EmitResolver) CreateReturnTypeOfSignatureDeclaration(emitContext *printer.EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	original := emitContext.ParseNode(signatureDeclaration)
	if original == nil {
		return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword)
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
	return requestNodeBuilder.SerializeReturnTypeForSignature(original, enclosingDeclaration, flags, internalFlags, tracker)
}

func (r *EmitResolver) CreateTypeParametersOfSignatureDeclaration(emitContext *printer.EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
	original := emitContext.ParseNode(signatureDeclaration)
	if original == nil {
		return nil
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
	return requestNodeBuilder.SerializeTypeParametersForSignature(original, enclosingDeclaration, flags, internalFlags, tracker)
}

func (r *EmitResolver) CreateTypeOfDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	original := emitContext.ParseNode(declaration)
	if original == nil {
		return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword)
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
	// // Get type of the symbol if this is the valid symbol otherwise get type at location
	symbol := r.checker.getSymbolOfDeclaration(declaration)
	return requestNodeBuilder.SerializeTypeForDeclaration(declaration, symbol, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker)
}

func (r *EmitResolver) CreateLiteralConstValue(emitContext *printer.EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node {
	node = emitContext.ParseNode(node)
	r.checkerMu.Lock()
	t := r.checker.getTypeOfSymbol(r.checker.getSymbolOfDeclaration(node))
	r.checkerMu.Unlock()
	if t == nil {
		return nil // TODO: How!? Maybe this should be a panic. All symbols should have a type.
	}

	var enumResult *ast.Node
	if t.flags&TypeFlagsEnumLike != 0 {
		r.checkerMu.Lock()
		defer r.checkerMu.Unlock()
		requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
		enumResult = requestNodeBuilder.SymbolToExpression(t.symbol, ast.SymbolFlagsValue, node, nodebuilder.FlagsNone, nodebuilder.InternalFlagsNone, tracker)
		// What about regularTrueType/regularFalseType - since those aren't fresh, we never make initializers from them
		// TODO: handle those if this function is ever used for more than initializers in declaration emit
	} else if t == r.checker.trueType {
		enumResult = emitContext.Factory.NewKeywordExpression(ast.KindTrueKeyword)
	} else if t == r.checker.falseType {
		enumResult = emitContext.Factory.NewKeywordExpression(ast.KindFalseKeyword)
	}
	if enumResult != nil {
		return enumResult
	}
	if t.flags&TypeFlagsLiteral == 0 {
		return nil // non-literal type
	}
	switch value := t.AsLiteralType().value.(type) {
	case string:
		return emitContext.Factory.NewStringLiteral(value, ast.TokenFlagsNone)
	case jsnum.Number:
		if value.IsInf() {
			if value > 0 {
				return emitContext.Factory.NewIdentifier("Infinity")
			}
			return emitContext.Factory.NewPrefixUnaryExpression(ast.KindMinusToken, emitContext.Factory.NewIdentifier("Infinity"))
		}
		if value.IsNaN() {
			return emitContext.Factory.NewIdentifier("NaN")
		}
		if value.Abs() != value {
			// negative
			return emitContext.Factory.NewPrefixUnaryExpression(
				ast.KindMinusToken,
				emitContext.Factory.NewNumericLiteral(value.String()[1:], ast.TokenFlagsNone),
			)
		}
		return emitContext.Factory.NewNumericLiteral(value.String(), ast.TokenFlagsNone)
	case jsnum.PseudoBigInt:
		return emitContext.Factory.NewBigIntLiteral(pseudoBigIntToString(value)+"n", ast.TokenFlagsNone)
	case bool:
		kind := ast.KindFalseKeyword
		if value {
			kind = ast.KindTrueKeyword
		}
		return emitContext.Factory.NewKeywordExpression(kind)
	}
	panic("unhandled literal const value kind")
}

func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	expression = emitContext.ParseNode(expression)
	if expression == nil {
		return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword)
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
	return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker)
}

func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
	container = emitContext.ParseNode(container)
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	sym := container.Symbol()
	staticInfos := r.checker.getIndexInfosOfType(r.checker.getTypeOfSymbol(sym))
	instanceIndexSymbol := r.checker.getIndexSymbol(sym)
	var instanceInfos []*IndexInfo
	if instanceIndexSymbol != nil {
		siblingSymbols := slices.Collect(maps.Values(r.checker.getMembersOfSymbol(sym)))
		instanceInfos = r.checker.getIndexInfosOfIndexSymbol(instanceIndexSymbol, siblingSymbols)
	}

	requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context

	var result []*ast.Node
	for i, infoList := range [][]*IndexInfo{staticInfos, instanceInfos} {
		isStatic := true
		if i > 0 {
			isStatic = false
		}
		if len(infoList) == 0 {
			continue
		}
		for _, info := range infoList {
			if info.declaration != nil {
				continue
			}
			if info == r.checker.anyBaseTypeIndexInfo {
				continue // inherited, but looks like a late-bound signature because it has no declarations
			}
			if len(info.components) != 0 {
				// !!! TODO: Complete late-bound index info support - getObjectLiteralIndexInfo does not yet add late bound components to index signatures
				allComponentComputedNamesSerializable := enclosingDeclaration != nil && core.Every(info.components, func(c *ast.Node) bool {
					return c.Name() != nil &&
						ast.IsComputedPropertyName(c.Name()) &&
						ast.IsEntityNameExpression(c.Name().Expression()) &&
						r.isEntityNameVisible(c.Name().Expression(), enclosingDeclaration, false).Accessibility == printer.SymbolAccessibilityAccessible
				})
				if allComponentComputedNamesSerializable {
					for _, c := range info.components {
						if r.checker.hasLateBindableName(c) {
							// skip late bound props that contribute to the index signature - they'll be preserved via other means
							continue
						}

						firstIdentifier := ast.GetFirstIdentifier(c.Name().Expression())
						name := r.checker.resolveName(firstIdentifier, firstIdentifier.Text(), ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*nameNotFoundMessage*/, true /*isUse*/, false /*excludeGlobals*/)
						if name != nil {
							tracker.TrackSymbol(name, enclosingDeclaration, ast.SymbolFlagsValue)
						}

						mods := core.IfElse(isStatic, []*ast.Node{emitContext.Factory.NewModifier(ast.KindStaticKeyword)}, nil)
						if info.isReadonly {
							mods = append(mods, emitContext.Factory.NewModifier(ast.KindReadonlyKeyword))
						}

						decl := emitContext.Factory.NewPropertyDeclaration(
							core.IfElse(mods != nil, emitContext.Factory.NewModifierList(mods), nil),
							c.Name(),
							c.QuestionToken(),
							requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(c.Symbol()), enclosingDeclaration, flags, internalFlags, tracker),
							nil,
						)
						result = append(result, decl)
					}
					continue
				}
			}
			node := requestNodeBuilder.IndexInfoToIndexSignatureDeclaration(info, enclosingDeclaration, flags, internalFlags, tracker)
			if node != nil && isStatic {
				modNodes := []*ast.Node{emitContext.Factory.NewModifier(ast.KindStaticKeyword)}
				modNodes = append(modNodes, node.ModifierNodes()...)
				mods := emitContext.Factory.NewModifierList(modNodes)
				node = emitContext.Factory.UpdateIndexSignatureDeclaration(
					node.AsIndexSignatureDeclaration(),
					mods,
					node.ParameterList(),
					node.Type(),
				)
			}
			if node != nil {
				result = append(result, node)
			}
		}
	}
	return result
}

func (r *EmitResolver) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags {
	// node = emitContext.ParseNode(node)
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.checker.GetEffectiveDeclarationFlags(node, flags)
}

func (r *EmitResolver) GetResolutionModeOverride(node *ast.Node) core.ResolutionMode {
	// node = emitContext.ParseNode(node)
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.checker.GetResolutionModeOverride(node.AsImportAttributes(), false)
}

func (r *EmitResolver) GetConstantValue(node *ast.Node) any {
	// node = emitContext.ParseNode(node)
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()
	return r.checker.GetConstantValue(node)
}

func (r *EmitResolver) GetTypeReferenceSerializationKind(typeName *ast.Node, location *ast.Node) printer.TypeReferenceSerializationKind {
	// typeName = emitContext.ParseNode(typeName)
	// location = emitContext.ParseNode(location)
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	if typeName == nil || location == nil {
		return printer.TypeReferenceSerializationKindUnknown
	}

	// Resolve the symbol as a value to ensure the type can be reached at runtime during emit.
	isTypeOnly := false
	if ast.IsQualifiedName(typeName) {
		rootValueSymbol := r.checker.resolveEntityName(ast.GetFirstIdentifier(typeName), ast.SymbolFlagsValue, true, true, location)

		if rootValueSymbol != nil && len(rootValueSymbol.Declarations) > 0 {
			isTypeOnly = core.Every(rootValueSymbol.Declarations, ast.IsTypeOnlyImportOrExportDeclaration)
		}
	}
	valueSymbol := r.checker.resolveEntityName(typeName, ast.SymbolFlagsValue, true, true, location)
	resolvedValueSymbol := valueSymbol
	if valueSymbol != nil && valueSymbol.Flags&ast.SymbolFlagsAlias != 0 {
		resolvedValueSymbol = r.checker.resolveAlias(valueSymbol)
	}

	isTypeOnly = isTypeOnly || (valueSymbol != nil && r.checker.getTypeOnlyAliasDeclarationEx(valueSymbol, ast.SymbolFlagsValue) != nil)

	// Resolve the symbol as a type so that we can provide a more useful hint for the type serializer.
	typeSymbol := r.checker.resolveEntityName(typeName, ast.SymbolFlagsType, true, true, location)
	resolvedTypeSymbol := typeSymbol
	if typeSymbol != nil && typeSymbol.Flags&ast.SymbolFlagsAlias != 0 {
		resolvedTypeSymbol = r.checker.resolveAlias(typeSymbol)
	}
	// In case the value symbol can't be resolved (e.g. because of missing declarations), use type symbol for reachability check.
	isTypeOnly = isTypeOnly || (typeSymbol != nil && r.checker.getTypeOnlyAliasDeclarationEx(typeSymbol, ast.SymbolFlagsType) != nil)

	if resolvedValueSymbol != nil && resolvedValueSymbol == resolvedTypeSymbol {
		globalPromiseSymbol := r.checker.getGlobalPromiseConstructorSymbol()
		if globalPromiseSymbol != nil && resolvedValueSymbol == globalPromiseSymbol {
			return printer.TypeReferenceSerializationKindPromise
		}

		constructorType := r.checker.getTypeOfSymbol(resolvedValueSymbol)
		if constructorType != nil && r.checker.isConstructorType(constructorType) {
			if isTypeOnly {
				return printer.TypeReferenceSerializationKindTypeWithCallSignature
			}
			return printer.TypeReferenceSerializationKindTypeWithConstructSignatureAndValue
		}
	}

	// We might not be able to resolve type symbol so use unknown type in that case (eg error case)
	if resolvedTypeSymbol == nil {
		if isTypeOnly {
			return printer.TypeReferenceSerializationKindObjectType
		}
		return printer.TypeReferenceSerializationKindUnknown
	}

	type_ := r.checker.getDeclaredTypeOfSymbol(resolvedTypeSymbol)
	if r.checker.isErrorType(type_) {
		if isTypeOnly {
			return printer.TypeReferenceSerializationKindObjectType
		}
		return printer.TypeReferenceSerializationKindUnknown
	}

	if type_.flags&TypeFlagsAnyOrUnknown != 0 {
		return printer.TypeReferenceSerializationKindObjectType
	} else if r.checker.isTypeAssignableToKind(type_, TypeFlagsVoid|TypeFlagsNullable|TypeFlagsNever) {
		return printer.TypeReferenceSerializationKindVoidNullableOrNeverType
	} else if r.checker.isTypeAssignableToKind(type_, TypeFlagsBooleanLike) {
		return printer.TypeReferenceSerializationKindBooleanType
	} else if r.checker.isTypeAssignableToKind(type_, TypeFlagsNumberLike) {
		return printer.TypeReferenceSerializationKindNumberLikeType
	} else if r.checker.isTypeAssignableToKind(type_, TypeFlagsBigIntLike) {
		return printer.TypeReferenceSerializationKindBigIntLikeType
	} else if r.checker.isTypeAssignableToKind(type_, TypeFlagsStringLike) {
		return printer.TypeReferenceSerializationKindStringLikeType
	} else if isTupleType(type_) {
		return printer.TypeReferenceSerializationKindArrayLikeType
	} else if r.checker.isTypeAssignableToKind(type_, TypeFlagsESSymbolLike) {
		return printer.TypeReferenceSerializationKindESSymbolType
	} else if r.checker.isFunctionType(type_) {
		return printer.TypeReferenceSerializationKindTypeWithCallSignature
	} else if r.checker.isArrayType(type_) {
		return printer.TypeReferenceSerializationKindArrayLikeType
	} else {
		return printer.TypeReferenceSerializationKindObjectType
	}
}

func (r *EmitResolver) GetPropertiesOfContainerFunction(node *ast.Node) []*ast.Symbol {
	// This is explicitly _not locked_ because it is only called via error reporters invoked via node builder calls
	// to the symbol tracker already within locked contexts.
	// r.checkerMu.Lock()
	// defer r.checkerMu.Unlock()
	if node == nil {
		return []*ast.Symbol{}
	}
	s := r.checker.getSymbolOfDeclaration(node)
	if s == nil {
		return []*ast.Symbol{}
	}
	return r.checker.getPropertiesOfType(r.checker.getTypeOfSymbol(s))
}

func (r *EmitResolver) TryJSTypeNodeToTypeNode(emitContext *printer.EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	typeNode = emitContext.ParseNode(typeNode)
	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context
	return requestNodeBuilder.TryJSTypeNodeToTypeNode(typeNode, enclosingDeclaration, flags, internalFlags, tracker)
}

// IsThisPropertyAssignmentDeclarationRedundant reports whether a JS `this.<name> = ...` expando
// assignment should be omitted from declaration emit because the member it would synthesize is
// already provided by an `extends` base type. This mirrors the skip condition in the checker's
// serializePropertySymbol: an inherited member is redundant when it is identical to the assigned
// one (same readonly-ness, optionality and type). Inherited accessors and methods are always
// treated as redundant here, since accessors merge oddly with value assignments (and run via the
// accessor at runtime), and `this`-expando props carry the ReplaceableByMethod contract, so a
// rebind such as `this.method = this.method.bind(this)` must not override the base method.
//
// Only `extends` base types are considered. Members coming from `implements` clauses are not
// inherited, so the class must redeclare them, and they are always emitted.
func (r *EmitResolver) IsThisPropertyAssignmentDeclarationRedundant(node *ast.Node) bool {
	if node == nil {
		return false
	}

	r.checkerMu.Lock()
	defer r.checkerMu.Unlock()

	s := r.checker.getSymbolOfDeclaration(node)
	if s == nil || s.Parent == nil {
		return false
	}
	parentType := r.checker.getDeclaredTypeOfSymbol(s.Parent)
	if parentType == nil {
		return false
	}
	for _, base := range r.checker.getBaseTypes(parentType) {
		baseProp := r.checker.getPropertyOfType(base, s.Name)
		if baseProp == nil {
			continue
		}
		if baseProp.Flags&(ast.SymbolFlagsAccessor|ast.SymbolFlagsMethod|ast.SymbolFlagsFunction) != 0 {
			return true
		}
		if r.checker.isReadonlySymbol(baseProp) == r.checker.isReadonlySymbol(s) &&
			(s.Flags&ast.SymbolFlagsOptional) == (baseProp.Flags&ast.SymbolFlagsOptional) &&
			r.checker.isTypeIdenticalTo(r.checker.getTypeOfSymbol(s), r.checker.getTypeOfSymbol(baseProp)) {
			return true
		}
	}
	return false
}
