package ls

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

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/astnav"
	"github.com/microsoft/typescript-go/internal/checker"
	"github.com/microsoft/typescript-go/internal/collections"
	"github.com/microsoft/typescript-go/internal/compiler"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/debug"
	"github.com/microsoft/typescript-go/internal/jsnum"
	"github.com/microsoft/typescript-go/internal/ls/lsconv"
	"github.com/microsoft/typescript-go/internal/ls/lsutil"
	"github.com/microsoft/typescript-go/internal/lsp/lsproto"
	"github.com/microsoft/typescript-go/internal/scanner"
	"github.com/microsoft/typescript-go/internal/stringutil"
	"github.com/microsoft/typescript-go/internal/tspath"
)

var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`)

func IsInString(sourceFile *ast.SourceFile, position int, previousToken *ast.Node) bool {
	if previousToken != nil && ast.IsStringTextContainingNode(previousToken) {
		start := astnav.GetStartOfNode(previousToken, sourceFile, false /*includeJSDoc*/)
		end := previousToken.End()

		// To be "in" one of these literals, the position has to be:
		//   1. entirely within the token text.
		//   2. at the end position of an unterminated token.
		//   3. at the end of a regular expression (due to trailing flags like '/foo/g').
		if start < position && position < end {
			return true
		}

		if position == end {
			return ast.IsUnterminatedLiteral(previousToken)
		}
	}
	return false
}

func isModuleSpecifierLike(node *ast.Node) bool {
	if !ast.IsStringLiteralLike(node) {
		return false
	}

	if ast.IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) || ast.IsImportCall(node.Parent) {
		return node.Parent.Arguments()[0] == node
	}

	return node.Parent.Kind == ast.KindExternalModuleReference ||
		node.Parent.Kind == ast.KindImportDeclaration ||
		node.Parent.Kind == ast.KindJSImportDeclaration
}

func getNonModuleSymbolOfMergedModuleSymbol(symbol *ast.Symbol) *ast.Symbol {
	if len(symbol.Declarations) == 0 || (symbol.Flags&(ast.SymbolFlagsModule|ast.SymbolFlagsTransient)) == 0 {
		return nil
	}

	if decl := core.Find(symbol.Declarations, func(d *ast.Node) bool { return !ast.IsSourceFile(d) && !ast.IsModuleDeclaration(d) }); decl != nil {
		return decl.Symbol()
	}
	return nil
}

func getLocalSymbolForExportSpecifier(referenceLocation *ast.Node, referenceSymbol *ast.Symbol, exportSpecifier *ast.ExportSpecifier, ch *checker.Checker) *ast.Symbol {
	if isExportSpecifierAlias(referenceLocation, exportSpecifier) {
		if symbol := ch.GetExportSpecifierLocalTargetSymbol(exportSpecifier.AsNode()); symbol != nil {
			return symbol
		}
	}
	return referenceSymbol
}

func isExportSpecifierAlias(referenceLocation *ast.Node, exportSpecifier *ast.ExportSpecifier) bool {
	debug.Assert(exportSpecifier.PropertyName == referenceLocation || exportSpecifier.Name() == referenceLocation, "referenceLocation is not export specifier name or property name")
	propertyName := exportSpecifier.PropertyName
	if propertyName != nil {
		// Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol.
		return propertyName == referenceLocation
	} else {
		// `export { foo } from "foo"` is a re-export.
		// `export { foo };` is not a re-export, it creates an alias for the local variable `foo`.
		return exportSpecifier.Parent.Parent.ModuleSpecifier() == nil
	}
}

func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange {
	return getRangeOfEnclosingComment(file, position, astnav.FindPrecedingToken(file, position), tokenAtPosition)
}

func positionBelongsToNode(candidate *ast.Node, position int, file *ast.SourceFile) bool {
	return lsutil.PositionBelongsToNode(candidate, position, file)
}

type PossibleTypeArgumentInfo struct {
	called         *ast.IdentifierNode
	nTypeArguments int
}

// Get info for an expression like `f <` that may be the start of type arguments.
func getPossibleTypeArgumentsInfo(tokenIn *ast.Node, sourceFile *ast.SourceFile) *PossibleTypeArgumentInfo {
	// This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character,
	// then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required
	if strings.LastIndexByte(sourceFile.Text(), '<') == -1 {
		return nil
	}

	token := tokenIn
	// This function determines if the node could be a type argument position
	// When editing, it is common to have an incomplete type argument list (e.g. missing ">"),
	// so the tree can have any shape depending on the tokens before the current node.
	// Instead, scanning for an identifier followed by a "<" before current node
	// will typically give us better results than inspecting the tree.
	// Note that we also balance out the already provided type arguments, arrays, object literals while doing so.
	remainingLessThanTokens := 0
	nTypeArguments := 0
	for token != nil {
		switch token.Kind {
		case ast.KindLessThanToken:
			// Found the beginning of the generic argument expression
			token = astnav.FindPrecedingToken(sourceFile, token.Pos())
			if token != nil && token.Kind == ast.KindQuestionDotToken {
				token = astnav.FindPrecedingToken(sourceFile, token.Pos())
			}
			if token == nil || !ast.IsIdentifier(token) {
				return nil
			}
			if remainingLessThanTokens == 0 {
				if ast.IsDeclarationName(token) {
					return nil
				}
				return &PossibleTypeArgumentInfo{
					called:         token,
					nTypeArguments: nTypeArguments,
				}
			}
			remainingLessThanTokens--
		case ast.KindGreaterThanGreaterThanGreaterThanToken:
			remainingLessThanTokens += 3
		case ast.KindGreaterThanGreaterThanToken:
			remainingLessThanTokens += 2
		case ast.KindGreaterThanToken:
			remainingLessThanTokens++
		case ast.KindCloseBraceToken:
			// This can be object type, skip until we find the matching open brace token
			// Skip until the matching open brace token
			token = findPrecedingMatchingToken(token, ast.KindOpenBraceToken, sourceFile)
			if token == nil {
				return nil
			}
		case ast.KindCloseParenToken:
			// This can be object type, skip until we find the matching open brace token
			// Skip until the matching open brace token
			token = findPrecedingMatchingToken(token, ast.KindOpenParenToken, sourceFile)
			if token == nil {
				return nil
			}
		case ast.KindCloseBracketToken:
			// This can be object type, skip until we find the matching open brace token
			// Skip until the matching open brace token
			token = findPrecedingMatchingToken(token, ast.KindOpenBracketToken, sourceFile)
			if token == nil {
				return nil
			}
		case ast.KindCommaToken:
			// Valid tokens in a type name. Skip.
			nTypeArguments++
		case ast.KindEqualsGreaterThanToken, ast.KindIdentifier, ast.KindStringLiteral, ast.KindNumericLiteral,
			ast.KindBigIntLiteral, ast.KindTrueKeyword, ast.KindFalseKeyword, ast.KindTypeOfKeyword, ast.KindExtendsKeyword,
			ast.KindKeyOfKeyword, ast.KindDotToken, ast.KindBarToken, ast.KindQuestionToken, ast.KindColonToken:
			// do nothing
		default:
			if !ast.IsTypeNode(token) {
				// Invalid token in type
				return nil
			}
		}
		token = astnav.FindPrecedingToken(sourceFile, token.Pos())
	}
	return nil
}

func isNameOfModuleDeclaration(node *ast.Node) bool {
	if node.Parent.Kind != ast.KindModuleDeclaration {
		return false
	}
	return node.Parent.Name() == node
}

func isExpressionOfExternalModuleImportEqualsDeclaration(node *ast.Node) bool {
	return ast.IsExternalModuleImportEqualsDeclaration(node.Parent.Parent) && ast.GetExternalModuleImportEqualsDeclarationExpression(node.Parent.Parent) == node
}

func isNamespaceReference(node *ast.Node) bool {
	return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node)
}

func isQualifiedNameNamespaceReference(node *ast.Node) bool {
	root := node
	isLastClause := true
	if root.Parent.Kind == ast.KindQualifiedName {
		for root.Parent != nil && root.Parent.Kind == ast.KindQualifiedName {
			root = root.Parent
		}

		isLastClause = root.AsQualifiedName().Right == node
	}

	return root.Parent.Kind == ast.KindTypeReference && !isLastClause
}

func isPropertyAccessNamespaceReference(node *ast.Node) bool {
	root := node
	isLastClause := true
	if root.Parent.Kind == ast.KindPropertyAccessExpression {
		for root.Parent != nil && root.Parent.Kind == ast.KindPropertyAccessExpression {
			root = root.Parent
		}

		isLastClause = root.Name() == node
	}

	if !isLastClause && root.Parent.Kind == ast.KindExpressionWithTypeArguments && root.Parent.Parent.Kind == ast.KindHeritageClause {
		decl := root.Parent.Parent.Parent
		return (decl.Kind == ast.KindClassDeclaration && root.Parent.Parent.AsHeritageClause().Token == ast.KindImplementsKeyword) ||
			(decl.Kind == ast.KindInterfaceDeclaration && root.Parent.Parent.AsHeritageClause().Token == ast.KindExtendsKeyword)
	}

	return false
}

func isThis(node *ast.Node) bool {
	switch node.Kind {
	case ast.KindThisKeyword:
		// case ast.KindThisType: TODO: GH#9267
		return true
	case ast.KindIdentifier:
		// 'this' as a parameter
		return node.Text() == "this" && node.Parent.Kind == ast.KindParameter
	default:
		return false
	}
}

func isTypeReference(node *ast.Node) bool {
	if ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) {
		node = node.Parent
	}

	switch node.Kind {
	case ast.KindThisKeyword:
		return !ast.IsExpressionNode(node)
	case ast.KindThisType:
		return true
	}

	switch node.Parent.Kind {
	case ast.KindTypeReference:
		return true
	case ast.KindImportType:
		return !node.Parent.AsImportTypeNode().IsTypeOf
	case ast.KindExpressionWithTypeArguments:
		return ast.IsPartOfTypeNode(node.Parent)
	}

	return false
}

func isInRightSideOfInternalImportEqualsDeclaration(node *ast.Node) bool {
	if node.Parent == nil {
		return false
	}
	for node.Parent.Kind == ast.KindQualifiedName {
		node = node.Parent
	}

	return ast.IsInternalModuleImportEqualsDeclaration(node.Parent) && node.Parent.AsImportEqualsDeclaration().ModuleReference == node
}

func (l *LanguageService) createLspRangeFromNode(node *ast.Node, file *ast.SourceFile) lsproto.Range {
	return l.createLspRangeFromBounds(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End(), file)
}

func createRangeFromNode(node *ast.Node, file *ast.SourceFile) core.TextRange {
	return core.NewTextRange(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End())
}

func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.SourceFile) lsproto.Range {
	return l.converters.ToLSPRange(file, core.NewTextRange(start, end))
}

func (l *LanguageService) createLspRangeFromRange(textRange core.TextRange, script lsconv.Script) lsproto.Range {
	return l.converters.ToLSPRange(script, textRange)
}

func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile) lsproto.Position {
	return l.converters.PositionToLineAndCharacter(file, core.TextPos(position))
}

func quote(file *ast.SourceFile, preferences lsutil.UserPreferences, text string) string {
	// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
	quotePreference := lsutil.GetQuotePreference(file, preferences)
	quoted, _ := core.StringifyJson(text, "" /*prefix*/, "" /*indent*/)
	if quotePreference == lsutil.QuotePreferenceSingle {
		quoted = quoteReplacer.Replace(stringutil.StripQuotes(quoted))
	}
	return quoted
}

var typeKeywords *collections.Set[ast.Kind] = collections.NewSetFromItems(
	ast.KindAnyKeyword,
	ast.KindAssertsKeyword,
	ast.KindBigIntKeyword,
	ast.KindBooleanKeyword,
	ast.KindFalseKeyword,
	ast.KindInferKeyword,
	ast.KindKeyOfKeyword,
	ast.KindNeverKeyword,
	ast.KindNullKeyword,
	ast.KindNumberKeyword,
	ast.KindObjectKeyword,
	ast.KindReadonlyKeyword,
	ast.KindStringKeyword,
	ast.KindSymbolKeyword,
	ast.KindTypeOfKeyword,
	ast.KindTrueKeyword,
	ast.KindVoidKeyword,
	ast.KindUndefinedKeyword,
	ast.KindUniqueKeyword,
	ast.KindUnknownKeyword,
)

func isTypeKeyword(kind ast.Kind) bool {
	return typeKeywords.Has(kind)
}

func isSeparator(node *ast.Node, candidate *ast.Node) bool {
	return candidate != nil && node.Parent != nil && (candidate.Kind == ast.KindCommaToken || (candidate.Kind == ast.KindSemicolonToken && node.Parent.Kind == ast.KindObjectLiteralExpression))
}

func isLiteralNameOfPropertyDeclarationOrIndexAccess(node *ast.Node) bool {
	// utilities
	switch node.Parent.Kind {
	case ast.KindPropertyDeclaration,
		ast.KindPropertySignature,
		ast.KindPropertyAssignment,
		ast.KindEnumMember,
		ast.KindMethodDeclaration,
		ast.KindMethodSignature,
		ast.KindGetAccessor,
		ast.KindSetAccessor,
		ast.KindModuleDeclaration:
		return ast.GetNameOfDeclaration(node.Parent) == node
	case ast.KindElementAccessExpression:
		return node.Parent.AsElementAccessExpression().ArgumentExpression == node
	case ast.KindComputedPropertyName:
		return true
	case ast.KindLiteralType:
		return node.Parent.Parent.Kind == ast.KindIndexedAccessType
	default:
		return false
	}
}

func isObjectBindingElementWithoutPropertyName(bindingElement *ast.Node) bool {
	return bindingElement.Kind == ast.KindBindingElement &&
		bindingElement.Parent.Kind == ast.KindObjectBindingPattern &&
		bindingElement.Name().Kind == ast.KindIdentifier &&
		bindingElement.PropertyName() == nil
}

func isRightSideOfPropertyAccess(node *ast.Node) bool {
	return node.Parent != nil && node.Parent.Kind == ast.KindPropertyAccessExpression && node.Parent.Name() == node
}

func isStaticSymbol(symbol *ast.Symbol) bool {
	if symbol.ValueDeclaration == nil {
		return false
	}
	modifierFlags := symbol.ValueDeclaration.ModifierFlags()
	return modifierFlags&ast.ModifierFlagsStatic != 0
}

func isImplementation(node *ast.Node) bool {
	if node.Flags&ast.NodeFlagsAmbient != 0 {
		return !(node.Kind == ast.KindInterfaceDeclaration || node.Kind == ast.KindTypeAliasDeclaration)
	}
	if ast.IsVariableLike(node) {
		return ast.HasInitializer(node)
	}
	if ast.IsFunctionLikeDeclaration(node) {
		return node.Body() != nil
	}
	return ast.IsClassLike(node) || ast.IsModuleOrEnumDeclaration(node)
}

func isImplementationExpression(node *ast.Node) bool {
	switch node.Kind {
	case ast.KindParenthesizedExpression:
		return isImplementationExpression(node.Expression())
	case ast.KindArrowFunction, ast.KindFunctionExpression, ast.KindObjectLiteralExpression, ast.KindClassExpression, ast.KindArrayLiteralExpression:
		return true
	default:
		return false
	}
}

func isReadonlyTypeOperator(node *ast.Node) bool {
	return node.Kind == ast.KindReadonlyKeyword && node.Parent.Kind == ast.KindTypeOperator && node.Parent.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword
}

func isJumpStatementTarget(node *ast.Node) bool {
	return node.Kind == ast.KindIdentifier && ast.IsBreakOrContinueStatement(node.Parent) && node.Parent.Label() == node
}

func isLabelOfLabeledStatement(node *ast.Node) bool {
	return node.Kind == ast.KindIdentifier && node.Parent.Kind == ast.KindLabeledStatement && node.Parent.Label() == node
}

func findReferenceInPosition(refs []*ast.FileReference, pos int) *ast.FileReference {
	return core.Find(refs, func(ref *ast.FileReference) bool { return ref.TextRange.ContainsInclusive(pos) })
}

func getContainingNodeIfInHeritageClause(node *ast.Node) *ast.Node {
	if node.Kind == ast.KindIdentifier || node.Kind == ast.KindPropertyAccessExpression {
		return getContainingNodeIfInHeritageClause(node.Parent)
	}
	if node.Kind == ast.KindExpressionWithTypeArguments && (ast.IsClassLike(node.Parent.Parent) || node.Parent.Parent.Kind == ast.KindInterfaceDeclaration) {
		return node.Parent.Parent
	}
	return nil
}

func getContainerNode(node *ast.Node) *ast.Node {
	for parent := node.Parent; parent != nil; parent = parent.Parent {
		switch parent.Kind {
		case ast.KindSourceFile, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindFunctionDeclaration, ast.KindFunctionExpression,
			ast.KindGetAccessor, ast.KindSetAccessor, ast.KindClassDeclaration, ast.KindInterfaceDeclaration, ast.KindEnumDeclaration, ast.KindModuleDeclaration:
			return parent
		}
	}
	return nil
}

func getAdjustedLocation(node *ast.Node, forRename bool, sourceFile *ast.SourceFile) *ast.Node {
	// todo: check if this function needs to be changed for jsdoc updates

	parent := node.Parent
	// /**/<modifier> [|name|] ...
	// /**/<modifier> <class|interface|type|enum|module|namespace|function|get|set> [|name|] ...
	// /**/<class|interface|type|enum|module|namespace|function|get|set> [|name|] ...
	// /**/import [|name|] = ...
	//
	// NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled
	// specially by `getSymbolAtLocation`.
	isModifier := func(node *ast.Node) bool {
		if ast.IsModifier(node) && (forRename || node.Kind != ast.KindDefaultKeyword) {
			return ast.CanHaveModifiers(parent) && slices.Contains(parent.ModifierNodes(), node)
		}
		switch node.Kind {
		case ast.KindClassKeyword:
			return ast.IsClassDeclaration(parent) || ast.IsClassExpression(node)
		case ast.KindFunctionKeyword:
			return ast.IsFunctionDeclaration(parent) || ast.IsFunctionExpression(node)
		case ast.KindInterfaceKeyword:
			return ast.IsInterfaceDeclaration(parent)
		case ast.KindEnumKeyword:
			return ast.IsEnumDeclaration(parent)
		case ast.KindTypeKeyword:
			return ast.IsTypeAliasDeclaration(parent)
		case ast.KindNamespaceKeyword, ast.KindModuleKeyword:
			return ast.IsModuleDeclaration(parent)
		case ast.KindImportKeyword:
			return ast.IsImportEqualsDeclaration(parent)
		case ast.KindGetKeyword:
			return ast.IsGetAccessorDeclaration(parent)
		case ast.KindSetKeyword:
			return ast.IsSetAccessorDeclaration(parent)
		}
		return false
	}
	if isModifier(node) {
		if sourceFile == nil {
			sourceFile = ast.GetSourceFileOfNode(node)
		}
		if location := getAdjustedLocationForDeclaration(parent, forRename, sourceFile); location != nil {
			return location
		}
	}

	// /**/<var|let|const> [|name|] ...
	if (node.Kind == ast.KindVarKeyword || node.Kind == ast.KindConstKeyword || node.Kind == ast.KindLetKeyword) &&
		ast.IsVariableDeclarationList(parent) && len(parent.AsVariableDeclarationList().Declarations.Nodes) == 1 {
		declaration := parent.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
		if ast.IsIdentifier(declaration.Name()) {
			return declaration.Name()
		}
	}

	if node.Kind == ast.KindTypeKeyword {
		// import /**/type [|name|] from ...;
		// import /**/type { [|name|] } from ...;
		// import /**/type { propertyName as [|name|] } from ...;
		// import /**/type ... from "[|module|]";
		if ast.IsImportClause(parent) && parent.IsTypeOnly() {
			if location := getAdjustedLocationForImportDeclaration(parent.Parent.AsImportDeclaration(), forRename); location != nil {
				return location
			}
		}
		// export /**/type { [|name|] } from ...;
		// export /**/type { propertyName as [|name|] } from ...;
		// export /**/type * from "[|module|]";
		// export /**/type * as ... from "[|module|]";
		if ast.IsExportDeclaration(parent) && parent.IsTypeOnly() {
			if location := getAdjustedLocationForExportDeclaration(parent.AsExportDeclaration(), forRename); location != nil {
				return location
			}
		}
	}

	// import { propertyName /**/as [|name|] } ...
	// import * /**/as [|name|] ...
	// export { propertyName /**/as [|name|] } ...
	// export * /**/as [|name|] ...
	if node.Kind == ast.KindAsKeyword {
		if parent.Kind == ast.KindImportSpecifier && parent.PropertyName() != nil ||
			parent.Kind == ast.KindExportSpecifier && parent.PropertyName() != nil ||
			parent.Kind == ast.KindNamespaceImport ||
			parent.Kind == ast.KindNamespaceExport {
			return parent.Name()
		}
		if parent.Kind == ast.KindExportDeclaration {
			if exportClause := parent.AsExportDeclaration().ExportClause; exportClause != nil && exportClause.Kind == ast.KindNamespaceExport {
				return exportClause.Name()
			}
		}
	}

	// /**/import [|name|] from ...;
	// /**/import { [|name|] } from ...;
	// /**/import { propertyName as [|name|] } from ...;
	// /**/import ... from "[|module|]";
	// /**/import "[|module|]";
	if node.Kind == ast.KindImportKeyword && parent.Kind == ast.KindImportDeclaration {
		if location := getAdjustedLocationForImportDeclaration(parent.AsImportDeclaration(), forRename); location != nil {
			return location
		}
	}

	if node.Kind == ast.KindExportKeyword {
		// /**/export { [|name|] } ...;
		// /**/export { propertyName as [|name|] } ...;
		// /**/export * from "[|module|]";
		// /**/export * as ... from "[|module|]";
		if parent.Kind == ast.KindExportDeclaration {
			if location := getAdjustedLocationForExportDeclaration(parent.AsExportDeclaration(), forRename); location != nil {
				return location
			}
		}
		// NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`.
		// /**/export default [|name|];
		// /**/export = [|name|];
		if parent.Kind == ast.KindExportAssignment {
			return ast.SkipOuterExpressions(parent.Expression(), ast.OEKAll)
		}
	}
	// import name = /**/require("[|module|]");
	if node.Kind == ast.KindRequireKeyword && parent.Kind == ast.KindExternalModuleReference {
		return parent.Expression()
	}
	// import ... /**/from "[|module|]";
	// export ... /**/from "[|module|]";
	if node.Kind == ast.KindFromKeyword {
		if (parent.Kind == ast.KindImportDeclaration || parent.Kind == ast.KindExportDeclaration) && parent.ModuleSpecifier() != nil {
			return parent.ModuleSpecifier()
		}
	}
	// class ... /**/extends [|name|] ...
	// class ... /**/implements [|name|] ...
	// class ... /**/implements name1, name2 ...
	// interface ... /**/extends [|name|] ...
	// interface ... /**/extends name1, name2 ...
	if (node.Kind == ast.KindExtendsKeyword || node.Kind == ast.KindImplementsKeyword) && parent.Kind == ast.KindHeritageClause && parent.AsHeritageClause().Token == node.Kind {
		getAdjustedLocationForHeritageClause := func(node *ast.HeritageClause) *ast.Node {
			// /**/extends [|name|]
			// /**/implements [|name|]
			if len(node.Types.Nodes) == 1 {
				return node.Types.Nodes[0].Expression()
			}

			// fall through `getAdjustedLocation`
			//    /**/extends name1, name2 ...
			//    /**/implements name1, name2 ...
			return nil
		}

		if location := getAdjustedLocationForHeritageClause(parent.AsHeritageClause()); location != nil {
			return location
		}
	}
	if node.Kind == ast.KindExtendsKeyword {
		// ... <T /**/extends [|U|]> ...
		if parent.Kind == ast.KindTypeParameter {
			if constraint := parent.AsTypeParameterDeclaration().Constraint; constraint != nil && constraint.Kind == ast.KindTypeReference {
				return constraint.AsTypeReferenceNode().TypeName
			}
		}
		// ... T /**/extends [|U|] ? ...
		if parent.Kind == ast.KindConditionalType {
			if extendsType := parent.AsConditionalTypeNode().ExtendsType; extendsType != nil && extendsType.Kind == ast.KindTypeReference {
				return extendsType.AsTypeReferenceNode().TypeName
			}
		}
	}
	// ... T extends /**/infer [|U|] ? ...
	if node.Kind == ast.KindInferKeyword && parent.Kind == ast.KindInferType {
		return parent.AsInferTypeNode().TypeParameter.Name()
	}
	// { [ [|K|] /**/in keyof T]: ... }
	if node.Kind == ast.KindInKeyword && parent.Kind == ast.KindTypeParameter && parent.Parent.Kind == ast.KindMappedType {
		return parent.Name()
	}
	// /**/keyof [|T|]
	if node.Kind == ast.KindKeyOfKeyword && parent.Kind == ast.KindTypeOperator && parent.AsTypeOperatorNode().Operator == ast.KindKeyOfKeyword {
		if parentType := parent.Type(); parentType != nil && parentType.Kind == ast.KindTypeReference {
			return parentType.AsTypeReferenceNode().TypeName
		}
	}
	// /**/readonly [|name|][]
	if node.Kind == ast.KindReadonlyKeyword && parent.Kind == ast.KindTypeOperator && parent.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword {
		if parentType := parent.Type(); parentType != nil && parentType.Kind == ast.KindArrayType && parentType.AsArrayTypeNode().ElementType.Kind == ast.KindTypeReference {
			return parentType.AsArrayTypeNode().ElementType.AsTypeReferenceNode().TypeName
		}
	}

	if !forRename {
		// /**/new [|name|]
		// /**/void [|name|]
		// /**/void obj.[|name|]
		// /**/typeof [|name|]
		// /**/typeof obj.[|name|]
		// /**/await [|name|]
		// /**/await obj.[|name|]
		// /**/yield [|name|]
		// /**/yield obj.[|name|]
		// /**/delete obj.[|name|]
		if node.Kind == ast.KindNewKeyword && parent.Kind == ast.KindNewExpression ||
			node.Kind == ast.KindVoidKeyword && parent.Kind == ast.KindVoidExpression ||
			node.Kind == ast.KindTypeOfKeyword && parent.Kind == ast.KindTypeOfExpression ||
			node.Kind == ast.KindAwaitKeyword && parent.Kind == ast.KindAwaitExpression ||
			node.Kind == ast.KindYieldKeyword && parent.Kind == ast.KindYieldExpression ||
			node.Kind == ast.KindDeleteKeyword && parent.Kind == ast.KindDeleteExpression {
			if expr := parent.Expression(); expr != nil {
				return ast.SkipOuterExpressions(expr, ast.OEKAll)
			}
		}

		// left /**/in [|name|]
		// left /**/instanceof [|name|]
		if (node.Kind == ast.KindInKeyword || node.Kind == ast.KindInstanceOfKeyword) && parent.Kind == ast.KindBinaryExpression && parent.AsBinaryExpression().OperatorToken == node {
			return ast.SkipOuterExpressions(parent.AsBinaryExpression().Right, ast.OEKAll)
		}

		// left /**/as [|name|]
		if node.Kind == ast.KindAsKeyword && parent.Kind == ast.KindAsExpression {
			if asExprType := parent.Type(); asExprType != nil && asExprType.Kind == ast.KindTypeReference {
				return asExprType.AsTypeReferenceNode().TypeName
			}
		}

		// for (... /**/in [|name|])
		// for (... /**/of [|name|])
		if node.Kind == ast.KindInKeyword && parent.Kind == ast.KindForInStatement ||
			node.Kind == ast.KindOfKeyword && parent.Kind == ast.KindForOfStatement {
			return ast.SkipOuterExpressions(parent.Expression(), ast.OEKAll)
		}
	}

	return node
}

func getAdjustedLocationForDeclaration(node *ast.Node, forRename bool, sourceFile *ast.SourceFile) *ast.Node {
	if node.Name() != nil {
		return node.Name()
	}
	if forRename {
		return nil
	}
	switch node.Kind {
	case ast.KindClassDeclaration, ast.KindFunctionDeclaration:
		// for class and function declarations, use the `default` modifier
		// when the declaration is unnamed.
		return core.Find(node.ModifierNodes(), func(*ast.Node) bool { return node.Kind == ast.KindDefaultKeyword })
	case ast.KindClassExpression:
		// for class expressions, use the `class` keyword when the class is unnamed
		return astnav.FindChildOfKind(node, ast.KindClassKeyword, sourceFile)
	case ast.KindFunctionExpression:
		// for function expressions, use the `function` keyword when the function is unnamed
		return astnav.FindChildOfKind(node, ast.KindFunctionKeyword, sourceFile)
	case ast.KindConstructor:
		return node
	}
	return nil
}

func getAdjustedLocationForImportDeclaration(node *ast.ImportDeclaration, forRename bool) *ast.Node {
	if node.ImportClause != nil {
		if name := node.ImportClause.Name(); name != nil {
			if node.ImportClause.AsImportClause().NamedBindings != nil {
				// do not adjust if we have both a name and named bindings
				return nil
			}
			// /**/import [|name|] from ...;
			// import /**/type [|name|] from ...;
			return node.ImportClause.Name()
		}

		// /**/import { [|name|] } from ...;
		// /**/import { propertyName as [|name|] } from ...;
		// /**/import * as [|name|] from ...;
		// import /**/type { [|name|] } from ...;
		// import /**/type { propertyName as [|name|] } from ...;
		// import /**/type * as [|name|] from ...;
		if namedBindings := node.ImportClause.AsImportClause().NamedBindings; namedBindings != nil {
			switch namedBindings.Kind {
			case ast.KindNamedImports:
				// do nothing if there is more than one binding
				elements := namedBindings.Elements()
				if len(elements) != 1 {
					return nil
				}
				return elements[0].Name()

			case ast.KindNamespaceImport:
				return namedBindings.Name()

			}
		}
	}
	if !forRename {
		// /**/import "[|module|]";
		// /**/import ... from "[|module|]";
		// import /**/type ... from "[|module|]";
		return node.ModuleSpecifier
	}
	return nil
}

func getAdjustedLocationForExportDeclaration(node *ast.ExportDeclaration, forRename bool) *ast.Node {
	if node.ExportClause != nil {
		// /**/export { [|name|] } ...
		// /**/export { propertyName as [|name|] } ...
		// /**/export * as [|name|] ...
		// export /**/type { [|name|] } from ...
		// export /**/type { propertyName as [|name|] } from ...
		// export /**/type * as [|name|] ...
		switch node.ExportClause.Kind {
		case ast.KindNamedExports:
			// do nothing if there is more than one binding
			elements := node.ExportClause.Elements()
			if len(elements) != 1 {
				return nil
			}
			return elements[0].Name()
		case ast.KindNamespaceExport:
			return node.ExportClause.Name()
		}
	}
	if !forRename {
		// /**/export * from "[|module|]";
		// export /**/type * from "[|module|]";
		return node.ModuleSpecifier
	}
	return nil
}

func symbolFlagsHaveMeaning(flags ast.SymbolFlags, meaning ast.SemanticMeaning) bool {
	if meaning == ast.SemanticMeaningAll {
		return true
	}
	if meaning&ast.SemanticMeaningValue != 0 {
		return flags&ast.SymbolFlagsValue != 0
	}
	if meaning&ast.SemanticMeaningType != 0 {
		return flags&ast.SymbolFlagsType != 0
	}
	if meaning&ast.SemanticMeaningNamespace != 0 {
		return flags&ast.SymbolFlagsNamespace != 0
	}
	return false
}

func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
	// todo: check if this function needs to be changed for jsdoc updates
	node = getAdjustedLocation(ast.GetReparsedNodeForNode(node), false /*forRename*/, nil)
	parent := node.Parent
	switch {
	case ast.IsSourceFile(node):
		return ast.SemanticMeaningValue
	case ast.NodeKindIs(parent, ast.KindExportAssignment, ast.KindExportSpecifier, ast.KindExternalModuleReference, ast.KindImportSpecifier, ast.KindImportClause) || parent.Kind == ast.KindImportEqualsDeclaration && node == parent.Name():
		return ast.SemanticMeaningAll
	case isInRightSideOfInternalImportEqualsDeclaration(node):
		//     import a = |b|; // Namespace
		//     import a = |b.c|; // Value, type, namespace
		//     import a = |b.c|.d; // Namespace
		name := node
		if node.Kind != ast.KindQualifiedName {
			name = core.IfElse(node.Parent.Kind == ast.KindQualifiedName && node.Parent.AsQualifiedName().Right == node, node.Parent, nil)
		}
		if name != nil && name.Parent.Kind == ast.KindImportEqualsDeclaration {
			return ast.SemanticMeaningAll
		}
		return ast.SemanticMeaningNamespace
	case ast.IsDeclarationName(node):
		return getMeaningFromDeclaration(parent)
	case ast.IsEntityName(node) && ast.IsJSDocNameReferenceContext(node):
		return ast.SemanticMeaningAll
	case isTypeReference(node):
		return ast.SemanticMeaningType
	case isNamespaceReference(node):
		return ast.SemanticMeaningNamespace
	case ast.IsTypeParameterDeclaration(parent):
		return ast.SemanticMeaningType
	case ast.IsLiteralTypeNode(parent):
		// This might be T["name"], which is actually referencing a property and not a type. So allow both meanings.
		return ast.SemanticMeaningType | ast.SemanticMeaningValue
	default:
		return ast.SemanticMeaningValue
	}
}

func getMeaningFromDeclaration(node *ast.Node) ast.SemanticMeaning {
	switch node.Kind {
	case ast.KindVariableDeclaration, ast.KindParameter, ast.KindBindingElement,
		ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment,
		ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor,
		ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindCatchClause, ast.KindJsxAttribute:
		return ast.SemanticMeaningValue

	case ast.KindTypeParameter, ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindTypeLiteral:
		return ast.SemanticMeaningType

	case ast.KindEnumMember, ast.KindClassDeclaration:
		return ast.SemanticMeaningValue | ast.SemanticMeaningType

	case ast.KindModuleDeclaration:
		if ast.IsAmbientModule(node) {
			return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue
		} else if ast.GetModuleInstanceState(node) == ast.ModuleInstanceStateInstantiated {
			return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue
		} else {
			return ast.SemanticMeaningNamespace
		}

	case ast.KindEnumDeclaration, ast.KindNamedImports, ast.KindImportSpecifier, ast.KindImportEqualsDeclaration, ast.KindImportDeclaration,
		ast.KindJSImportDeclaration, ast.KindExportAssignment, ast.KindExportDeclaration:
		return ast.SemanticMeaningAll

	// An external module can be a Value
	case ast.KindSourceFile:
		return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue
	}

	return ast.SemanticMeaningAll
}

func getIntersectingMeaningFromDeclarations(node *ast.Node, symbol *ast.Symbol, defaultMeaning ast.SemanticMeaning) ast.SemanticMeaning {
	if node == nil {
		return defaultMeaning
	}

	meaning := getMeaningFromLocation(node)
	declarations := symbol.Declarations
	if len(declarations) == 0 {
		return meaning
	}

	lastIterationMeaning := meaning

	// !!! TODO check if the port is correct and the for loop is needed
	iteration := func(m ast.SemanticMeaning) ast.SemanticMeaning {
		for _, declaration := range declarations {
			declarationMeaning := getMeaningFromDeclaration(declaration)

			if declarationMeaning&m != 0 {
				m |= declarationMeaning
			}
		}
		return m
	}
	meaning = iteration(meaning)

	for meaning != lastIterationMeaning {
		// The result is order-sensitive, for instance if initialMeaning == Namespace, and declarations = [class, instantiated module]
		// we need to consider both as the initialMeaning intersects with the module in the namespace space, and the module
		// intersects with the class in the value space.
		// To achieve that we will keep iterating until the result stabilizes.

		// Remember the last meaning
		lastIterationMeaning = meaning
		meaning = iteration(meaning)
	}

	return meaning
}

// Returns the node in an `extends` or `implements` clause of a class or interface.
func getAllSuperTypeNodes(node *ast.Node) []*ast.TypeNode {
	if ast.IsInterfaceDeclaration(node) {
		return ast.GetHeritageElements(node, ast.KindExtendsKeyword)
	}
	if ast.IsClassLike(node) {
		return append(
			core.SingleElementSlice(ast.GetClassExtendsHeritageElement(node)),
			ast.GetImplementsTypeNodes(node)...,
		)
	}
	return nil
}

func getParentSymbolsOfPropertyAccess(location *ast.Node, symbol *ast.Symbol, ch *checker.Checker) []*ast.Symbol {
	if !isRightSideOfPropertyAccess(location) {
		return nil
	}
	lhsType := ch.GetTypeAtLocation(location.Parent.Expression())
	if lhsType == nil {
		return nil
	}
	var possibleSymbols []*checker.Type
	if lhsType.Flags()&checker.TypeFlagsUnionOrIntersection != 0 {
		possibleSymbols = lhsType.Types()
	} else if lhsType.Symbol() != symbol.Parent {
		possibleSymbols = []*checker.Type{lhsType}
	}
	return core.MapNonNil(possibleSymbols, func(t *checker.Type) *ast.Symbol {
		if t.Symbol() != nil && t.Symbol().Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0 {
			return t.Symbol()
		}
		return nil
	})
}

// Find symbol of the given property-name and add the symbol to the given result array
// @param symbol a symbol to start searching for the given propertyName
// @param propertyName a name of property to search for
// @param cb a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol.
//
//	The value of previousIterationSymbol is undefined when the function is first called.
func getPropertySymbolsFromBaseTypes(symbol *ast.Symbol, propertyName string, checker *checker.Checker, cb func(base *ast.Symbol) *ast.Symbol) *ast.Symbol {
	var seen collections.Set[*ast.Symbol]
	var recur func(*ast.Symbol) *ast.Symbol
	recur = func(symbol *ast.Symbol) *ast.Symbol {
		// Use `addToSeen` to ensure we don't infinitely recurse in this situation:
		//      interface C extends C {
		//          /*findRef*/propName: string;
		//      }
		if symbol.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) == 0 || !seen.AddIfAbsent(symbol) {
			return nil
		}
		for _, declaration := range symbol.Declarations {
			for _, typeReference := range getAllSuperTypeNodes(declaration) {
				if propertyType := checker.GetTypeAtLocation(typeReference); propertyType != nil && propertyType.Symbol() != nil {
					// Visit the typeReference as well to see if it directly or indirectly uses that property
					if propertySymbol := checker.GetPropertyOfType(propertyType, propertyName); propertySymbol != nil {
						for _, rootSymbol := range checker.GetRootSymbols(propertySymbol) {
							if result := cb(rootSymbol); result != nil {
								return result
							}
						}
					}
					if result := recur(propertyType.Symbol()); result != nil {
						return result
					}
				}
			}
		}
		return nil
	}
	return recur(symbol)
}

func getPropertySymbolFromBindingElement(checker *checker.Checker, bindingElement *ast.Node) *ast.Symbol {
	if typeOfPattern := checker.GetTypeAtLocation(bindingElement.Parent); typeOfPattern != nil {
		return checker.GetPropertyOfType(typeOfPattern, bindingElement.Name().Text())
	}
	return nil
}

func getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
	bindingElement := ast.GetDeclarationOfKind(symbol, ast.KindBindingElement)
	if bindingElement != nil && isObjectBindingElementWithoutPropertyName(bindingElement) {
		return getPropertySymbolFromBindingElement(checker, bindingElement)
	}
	return nil
}

func getTargetLabel(referenceNode *ast.Node, labelName string) *ast.Node {
	// todo: rewrite as `ast.FindAncestor`
	for referenceNode != nil {
		if referenceNode.Kind == ast.KindLabeledStatement && referenceNode.Label().Text() == labelName {
			return referenceNode.Label()
		}
		referenceNode = referenceNode.Parent
	}
	return nil
}

func skipConstraint(t *checker.Type, typeChecker *checker.Checker) *checker.Type {
	if t.IsTypeParameter() {
		c := typeChecker.GetBaseConstraintOfType(t)
		if c != nil {
			return c
		}
	}
	return t
}

type caseClauseTrackerState struct {
	existingStrings collections.Set[string]
	existingNumbers collections.Set[jsnum.Number]
	existingBigInts collections.Set[jsnum.PseudoBigInt]
}

// string | jsnum.Number
type trackerAddValue = any

// string | jsnum.Number | jsnum.PseudoBigInt
type trackerHasValue = any

type caseClauseTracker interface {
	addValue(value trackerAddValue)
	hasValue(value trackerHasValue) bool
}

func (c *caseClauseTrackerState) addValue(value trackerAddValue) {
	switch v := value.(type) {
	case string:
		c.existingStrings.Add(v)
	case jsnum.Number:
		c.existingNumbers.Add(v)
	default:
		panic(fmt.Sprintf("Unsupported type: %T", v))
	}
}

func (c *caseClauseTrackerState) hasValue(value trackerHasValue) bool {
	switch v := value.(type) {
	case string:
		return c.existingStrings.Has(v)
	case jsnum.Number:
		return c.existingNumbers.Has(v)
	case jsnum.PseudoBigInt:
		return c.existingBigInts.Has(v)
	default:
		panic(fmt.Sprintf("Unsupported type: %T", v))
	}
}

func newCaseClauseTracker(typeChecker *checker.Checker, clauses []*ast.CaseOrDefaultClauseNode) caseClauseTracker {
	c := &caseClauseTrackerState{
		existingStrings: collections.Set[string]{},
		existingNumbers: collections.Set[jsnum.Number]{},
		existingBigInts: collections.Set[jsnum.PseudoBigInt]{},
	}
	for _, clause := range clauses {
		if !ast.IsDefaultClause(clause) {
			expression := ast.SkipParentheses(clause.Expression())
			if ast.IsLiteralExpression(expression) {
				switch expression.Kind {
				case ast.KindNoSubstitutionTemplateLiteral, ast.KindStringLiteral:
					c.existingStrings.Add(expression.Text())
				case ast.KindNumericLiteral:
					c.existingNumbers.Add(jsnum.FromString(expression.Text()))
				case ast.KindBigIntLiteral:
					c.existingBigInts.Add(jsnum.ParseValidBigInt(expression.Text()))
				}
			} else {
				symbol := typeChecker.GetSymbolAtLocation(clause.Expression())
				if symbol != nil && symbol.ValueDeclaration != nil && ast.IsEnumMember(symbol.ValueDeclaration) {
					enumValue := typeChecker.GetConstantValue(symbol.ValueDeclaration)
					if enumValue != nil {
						c.addValue(enumValue)
					}
				}
			}
		}
	}
	return c
}

func RangeContainsRange(r1 core.TextRange, r2 core.TextRange) bool {
	return startEndContainsRange(r1.Pos(), r1.End(), r2)
}

func startEndContainsRange(start int, end int, textRange core.TextRange) bool {
	return start <= textRange.Pos() && end >= textRange.End()
}

func getPossibleGenericSignatures(called *ast.Expression, typeArgumentCount int, c *checker.Checker) []*checker.Signature {
	typeAtLocation := c.GetTypeAtLocation(called)
	if ast.IsOptionalChain(called.Parent) {
		typeAtLocation = removeOptionality(typeAtLocation, ast.IsOptionalChainRoot(called.Parent), true /*isOptionalChain*/, c)
	}
	var signatures []*checker.Signature
	if ast.IsNewExpression(called.Parent) {
		signatures = c.GetSignaturesOfType(typeAtLocation, checker.SignatureKindConstruct)
	} else {
		signatures = c.GetSignaturesOfType(typeAtLocation, checker.SignatureKindCall)
	}
	return core.Filter(signatures, func(s *checker.Signature) bool {
		return s.TypeParameters() != nil && len(s.TypeParameters()) >= typeArgumentCount
	})
}

func removeOptionality(t *checker.Type, isOptionalExpression bool, isOptionalChain bool, c *checker.Checker) *checker.Type {
	if isOptionalExpression {
		return c.GetNonNullableType(t)
	} else if isOptionalChain {
		return c.GetNonOptionalType(t)
	}
	return t
}

func isNoSubstitutionTemplateLiteral(node *ast.Node) bool {
	return node.Kind == ast.KindNoSubstitutionTemplateLiteral
}

func isTaggedTemplateExpression(node *ast.Node) bool {
	return node.Kind == ast.KindTaggedTemplateExpression
}

func isInsideTemplateLiteral(node *ast.Node, position int, sourceFile *ast.SourceFile) bool {
	return ast.IsTemplateLiteralKind(node.Kind) && (scanner.GetTokenPosOfNode(node, sourceFile, false) < position && position < node.End() || (ast.IsUnterminatedLiteral(node) && position == node.End()))
}

// Pseudo-literals
func isTemplateHead(node *ast.Node) bool {
	return node.Kind == ast.KindTemplateHead
}

func isTemplateTail(node *ast.Node) bool {
	return node.Kind == ast.KindTemplateTail
}

func findPrecedingMatchingToken(token *ast.Node, matchingTokenKind ast.Kind, sourceFile *ast.SourceFile) *ast.Node {
	closeTokenText := scanner.TokenToString(token.Kind)
	matchingTokenText := scanner.TokenToString(matchingTokenKind)
	// Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides
	// a good, fast approximation without too much extra work in the cases where it fails.
	bestGuessIndex := strings.LastIndex(sourceFile.Text(), matchingTokenText)
	if bestGuessIndex == -1 {
		return nil // if the token text doesn't appear in the file, there can't be a match - super fast bail
	}
	// we can only use the textual result directly if we didn't have to count any close tokens within the range
	if strings.LastIndex(sourceFile.Text(), closeTokenText) < bestGuessIndex {
		nodeAtGuess := astnav.FindPrecedingToken(sourceFile, bestGuessIndex+1)
		if nodeAtGuess != nil && nodeAtGuess.Kind == matchingTokenKind {
			return nodeAtGuess
		}
	}
	tokenKind := token.Kind
	remainingMatchingTokens := 0
	for {
		preceding := astnav.FindPrecedingToken(sourceFile, token.Pos())
		if preceding == nil {
			return nil
		}
		token = preceding
		switch token.Kind {
		case matchingTokenKind:
			if remainingMatchingTokens == 0 {
				return token
			}
			remainingMatchingTokens--
		case tokenKind:
			remainingMatchingTokens++
		}
	}
}

func findContainingList(node *ast.Node, file *ast.SourceFile) *ast.NodeList {
	// The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will
	// be parented by the container of the SyntaxList, not the SyntaxList itself.
	var list *ast.NodeList
	visitNode := func(n *ast.Node, visitor *ast.NodeVisitor) *ast.Node {
		return n
	}
	visitNodes := func(nodes *ast.NodeList, visitor *ast.NodeVisitor) *ast.NodeList {
		if nodes != nil && RangeContainsRange(nodes.Loc, node.Loc) {
			list = nodes
		}
		return nodes
	}
	astnav.VisitEachChildAndJSDoc(node.Parent, file, visitNode, visitNodes)
	return list
}

func getLeadingCommentRangesOfNode(node *ast.Node, file *ast.SourceFile) iter.Seq[ast.CommentRange] {
	if node.Kind == ast.KindJsxText {
		return nil
	}
	return scanner.GetLeadingCommentRanges(&ast.NodeFactory{}, file.Text(), node.Pos())
}

// Equivalent to Strada's `node.getChildren()` for non-JSDoc nodes.
func getChildrenFromNonJSDocNode(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
	var childNodes []*ast.Node
	node.ForEachChild(func(child *ast.Node) bool {
		childNodes = append(childNodes, child)
		return false
	})

	// If the node has no children, don't scan for tokens.
	// This prevents creating tokens for leaf nodes' own text.
	if len(childNodes) == 0 {
		return nil
	}

	var children []*ast.Node
	pos := node.Pos()
	for _, child := range childNodes {
		scanner := scanner.GetScannerForSourceFile(sourceFile, pos)
		for pos < child.Pos() {
			token := scanner.Token()
			tokenFullStart := scanner.TokenFullStart()
			tokenEnd := scanner.TokenEnd()
			children = append(children, sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, node, scanner.TokenFlags()))
			pos = tokenEnd
			scanner.Scan()
		}
		children = append(children, child)
		pos = child.End()
	}
	scanner := scanner.GetScannerForSourceFile(sourceFile, pos)
	for pos < node.End() {
		token := scanner.Token()
		tokenFullStart := scanner.TokenFullStart()
		tokenEnd := scanner.TokenEnd()
		children = append(children, sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, node, scanner.TokenFlags()))
		pos = tokenEnd
		scanner.Scan()
	}
	return children
}

// Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
func getContainingObjectLiteralElement(node *ast.Node) *ast.Node {
	element := getContainingObjectLiteralElementWorker(node)
	if element != nil && (ast.IsObjectLiteralExpression(element.Parent) || ast.IsJsxAttributes(element.Parent)) {
		return element
	}
	return nil
}

func getContainingObjectLiteralElementWorker(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral, ast.KindNumericLiteral:
		if node.Parent.Kind == ast.KindComputedPropertyName {
			if isObjectLiteralOrJsxElement(node.Parent.Parent) {
				return node.Parent.Parent
			}
			return nil
		}
		fallthrough
	case ast.KindIdentifier, ast.KindJsxNamespacedName:
		if isObjectLiteralOrJsxElement(node.Parent) && (node.Parent.Parent.Kind == ast.KindObjectLiteralExpression || node.Parent.Parent.Kind == ast.KindJsxAttributes) && node.Parent.Name() == node {
			return node.Parent
		}
	}
	return nil
}

func isObjectLiteralOrJsxElement(node *ast.Node) bool {
	return ast.IsObjectLiteralElement(node) || ast.IsJsxAttribute(node) || ast.IsJsxSpreadAttribute(node)
}

// Return a function that returns true if the given node has not been seen
func nodeSeenTracker() func(*ast.Node) bool {
	var seen collections.Set[*ast.Node]
	return func(node *ast.Node) bool {
		return seen.AddIfAbsent(node)
	}
}

// FindAllReferences.toContextSpan
func toContextRange(textRange *core.TextRange, contextFile *ast.SourceFile, context *ast.Node) *core.TextRange {
	if context == nil {
		return textRange
	}
	// !!! isContextWithStartAndEndNode
	contextRange := getRangeOfNode(context, contextFile, nil /*endNode*/)
	if contextRange.Pos() != textRange.Pos() || contextRange.End() != textRange.End() {
		return &contextRange
	}
	return nil
}

func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *compiler.Program) *refInfo {
	if referencePath := findReferenceInPosition(sourceFile.ReferencedFiles, position); referencePath != nil {
		if file := program.GetSourceFileFromReference(sourceFile, referencePath); file != nil {
			return &refInfo{reference: referencePath, fileName: file.FileName(), file: file, unverified: false}
		}
		return nil
	}

	if typeReferenceDirective := findReferenceInPosition(sourceFile.TypeReferenceDirectives, position); typeReferenceDirective != nil {
		if reference := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile); reference != nil {
			if file := program.GetSourceFile(reference.ResolvedFileName); file != nil {
				return &refInfo{reference: typeReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
			}
		}
		return nil
	}

	if libReferenceDirective := findReferenceInPosition(sourceFile.LibReferenceDirectives, position); libReferenceDirective != nil {
		if file := program.GetLibFileFromReference(libReferenceDirective); file != nil {
			return &refInfo{reference: libReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
		}
		return nil
	}

	if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 {
		return nil
	}

	node := astnav.GetTouchingToken(sourceFile, position)
	if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) {
		return nil
	}

	if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil {
		verifiedFileName := resolution.ResolvedFileName
		fileName := resolution.ResolvedFileName
		if fileName == "" {
			fileName = tspath.ResolvePath(tspath.GetDirectoryPath(sourceFile.FileName()), node.Text())
		}
		return &refInfo{
			file:       program.GetSourceFile(fileName),
			fileName:   fileName,
			reference:  nil,
			unverified: verifiedFileName != "",
		}
	}

	return nil
}

func getContextualTypeFromParent(node *ast.Expression, typeChecker *checker.Checker, contextFlags checker.ContextFlags) *checker.Type {
	parent := ast.WalkUpParenthesizedExpressions(node.Parent)
	switch parent.Kind {
	case ast.KindNewExpression:
		return typeChecker.GetContextualType(parent, contextFlags)
	case ast.KindBinaryExpression:
		if isEqualityOperatorKind(parent.AsBinaryExpression().OperatorToken.Kind) {
			return typeChecker.GetTypeAtLocation(
				core.IfElse(node == parent.AsBinaryExpression().Right, parent.AsBinaryExpression().Left, parent.AsBinaryExpression().Right),
			)
		}
		return typeChecker.GetContextualType(node, contextFlags)
	case ast.KindCaseClause:
		return getSwitchedType(parent, typeChecker)
	default:
		return typeChecker.GetContextualType(node, contextFlags)
	}
}

func getContextualTypeFromParentOrAncestorTypeNode(node *ast.Expression, typeChecker *checker.Checker) *checker.Type {
	if node.Flags&ast.NodeFlagsJSDoc != 0 && node.Flags&ast.NodeFlagsJavaScriptFile == 0 {
		return nil
	}

	contextualType := getContextualTypeFromParent(node, typeChecker, checker.ContextFlagsNone)
	if contextualType != nil {
		return contextualType
	}

	if ancestorTypeNode := getAncestorTypeNode(node); ancestorTypeNode != nil {
		return typeChecker.GetTypeAtLocation(ancestorTypeNode)
	}

	return nil
}

func getAncestorTypeNode(node *ast.Node) *ast.Node {
	var lastTypeNode *ast.TypeNode
	ast.FindAncestor(node, func(n *ast.Node) bool {
		if ast.IsTypeNode(n) {
			lastTypeNode = n
		}
		return !ast.IsQualifiedName(n.Parent) && !ast.IsTypeNode(n.Parent) && !ast.IsTypeElement(n.Parent)
	})
	return lastTypeNode
}

func isSourceFileWithGlobalExports(node *ast.Node) bool {
	return node != nil && ast.IsSourceFile(node) && node.AsSourceFile().GlobalExports != nil
}
