package estransforms

import (
	"iter"
	"slices"

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

// classFacts tracks various facts about a class being transformed.
type classFacts int

const (
	classFactsNone                                       classFacts = 0
	classFactsClassWasDecorated                          classFacts = 1 << 0
	classFactsNeedsClassConstructorReference             classFacts = 1 << 1
	classFactsNeedsClassSuperReference                   classFacts = 1 << 2
	classFactsNeedsSubstitutionForThisInClassStaticField classFacts = 1 << 3
	classFactsWillHoistInitializersToConstructor         classFacts = 1 << 4
)

// privateIdentifierKind represents the kind of private identifier declaration.
// privateIdentifierInfo stores information about a private identifier during transformation.
type privateIdentifierInfo struct {
	kind printer.PrivateIdentifierKind
	// brandCheckIdentifier can contain:
	//  - For instance field: The WeakMap that will be the storage for the field.
	//  - For instance methods or accessors: The WeakSet that will be used for brand checking.
	//  - For static members: The constructor that will be used for brand checking.
	brandCheckIdentifier *ast.IdentifierNode
	// isStatic stores if the identifier is static or not.
	isStatic bool
	// isValid stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor)
	// or duplicate identifiers are considered invalid.
	isValid bool
	// variableName contains the variable that will serve as the storage for a static field.
	variableName *ast.IdentifierNode
	// methodName is the identifier for a variable that will contain the private method implementation.
	methodName *ast.IdentifierNode
	// getterName is the identifier for a variable that will contain the private get accessor implementation, if any.
	getterName *ast.IdentifierNode
	// setterName is the identifier for a variable that will contain the private set accessor implementation, if any.
	setterName *ast.IdentifierNode
}

// privateEnvironmentData stores class-scoped environment data for private identifiers.
type privateEnvironmentData struct {
	// className is used for prefixing generated variable names.
	className *ast.IdentifierNode
	// weakSetName is used for brand check on private methods.
	weakSetName *ast.IdentifierNode
}

// privateEnvironment stores a map of private identifier names to their transform info.
// Like Strada, it uses two separate maps: one for non-generated identifiers (keyed by text)
// and one for generated identifiers (keyed by original AST node). This prevents collisions
// when different auto-accessors produce generated backing field names with the same text.
type privateEnvironment struct {
	data                 privateEnvironmentData
	members              map[string]*privateIdentifierInfo
	generatedIdentifiers map[*ast.Node]*privateIdentifierInfo
}

// classLexicalEnvironment stores information about the lexical environment of a class.
type classLexicalEnvironment struct {
	facts classFacts
	// classConstructor is used for brand checks on static members, and `this` references in static initializers.
	classConstructor *ast.IdentifierNode
	classThis        *ast.IdentifierNode
	// superClassReference is used for `super` references in static initializers.
	superClassReference *ast.IdentifierNode
}

// classLexicalEnv is a linked list of class lexical environments.
type classLexicalEnv struct {
	previous   *classLexicalEnv
	data       *classLexicalEnvironment
	privateEnv *privateEnvironment
}

type classFieldsTransformer struct {
	transformers.Transformer
	compilerOptions *core.CompilerOptions
	resolver        binder.ReferenceResolver

	// Computed configuration flags
	shouldTransformInitializersUsingSet               bool
	shouldTransformInitializersUsingDefine            bool
	shouldTransformInitializers                       bool
	shouldTransformPrivateElementsOrClassStaticBlocks bool
	shouldTransformAutoAccessors                      bool
	shouldTransformThisInStaticInitializers           bool
	shouldTransformSuperInStaticInitializers          bool
	shouldTransformPrivateStaticElementsInFile        bool
	legacyDecorators                                  bool

	// pendingExpressions tracks what computed name expressions originating from elided names
	// must be inlined at the next execution site, in document order.
	pendingExpressions []*ast.Expression
	// pendingStatements tracks what computed name expression statements and static property
	// initializers must be emitted at the next execution site, in document order (for decorated classes).
	pendingStatements     []*ast.Statement
	lexicalEnvironment    *classLexicalEnv
	currentClassContainer *ast.ClassLikeDeclaration
	currentClassElement   *ast.ClassElement
	// classAliases maps class declarations to alias identifiers for substituting class name
	// references in static initializers. Replaces Strada's onSubstituteNode/trySubstituteClassAlias.
	classAliases               map[*ast.Node]*ast.IdentifierNode
	enclosingClassDeclarations collections.Set[*ast.Node]
	inIterationStatement       bool
	// insideComputedPropertyName replaces Strada's onEmitNode for ComputedPropertyName, which
	// switches to the outer lexical environment. Used by visitThisExpression() to apply
	// the outer environment's substitution without requiring currentClassElement to be static.
	insideComputedPropertyName bool
	parentNode                 *ast.Node
	currentNode                *ast.Node

	// Visitors
	modifierVisitor                *ast.NodeVisitor
	discardedValueVisitor          *ast.NodeVisitor
	heritageClauseVisitor          *ast.NodeVisitor
	assignmentTargetVisitor        *ast.NodeVisitor
	classElementVisitor            *ast.NodeVisitor
	accessorFieldResultVisitor     *ast.NodeVisitor
	arrayAssignmentElementVisitor  *ast.NodeVisitor
	objectAssignmentElementVisitor *ast.NodeVisitor
	substitutionVisitor            *ast.NodeVisitor

	// Pre-bound callbacks to avoid repeated closure allocation.
	isAnonymousClassNeedingAssignedName func(*anonymousFunctionDefinition) bool
}

func newClassFieldsTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
	languageVersion := opts.CompilerOptions.GetEmitScriptTarget()
	useDefineForClassFields := opts.CompilerOptions.GetUseDefineForClassFields()

	// When targeting ESNext+ with useDefineForClassFields (the default), there are no class
	// field transformations to perform and no prior transform sets EFTransformPrivateStaticElements,
	// so every node would be returned unchanged. Skip entirely.
	if languageVersion >= core.ScriptTargetESNext && useDefineForClassFields {
		return nil
	}

	tx := &classFieldsTransformer{
		compilerOptions:  opts.CompilerOptions,
		resolver:         opts.Resolver,
		legacyDecorators: opts.CompilerOptions.ExperimentalDecorators.IsTrue(),
	}

	// Always transform field initializers using Set semantics when `useDefineForClassFields: false`.
	tx.shouldTransformInitializersUsingSet = !useDefineForClassFields

	// Transform field initializers using Define semantics when `useDefineForClassFields: true` and target < ES2022.
	tx.shouldTransformInitializersUsingDefine = useDefineForClassFields && languageVersion < core.ScriptTargetES2022

	tx.shouldTransformInitializers = tx.shouldTransformInitializersUsingSet || tx.shouldTransformInitializersUsingDefine

	// We need to transform private members and class static blocks when target < ES2022.
	tx.shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < core.ScriptTargetES2022

	// We need to transform `accessor` fields when target < ESNext.
	// We may need to transform `accessor` fields when `useDefineForClassFields: false`
	tx.shouldTransformAutoAccessors = languageVersion < core.ScriptTargetESNext

	// We need to transform `this` in a static initializer into a reference to the class
	// when target < ES2022 since the assignment will be moved outside of the class body.
	tx.shouldTransformThisInStaticInitializers = languageVersion < core.ScriptTargetES2022

	// Since target is always >= ES2015, this is always the same as
	// shouldTransformThisInStaticInitializers.
	tx.shouldTransformSuperInStaticInitializers = tx.shouldTransformThisInStaticInitializers

	result := tx.NewTransformer(tx.visit, opts.Context)
	tx.modifierVisitor = tx.EmitContext().NewNodeVisitor(tx.visitModifier)
	tx.discardedValueVisitor = tx.EmitContext().NewNodeVisitor(tx.visitDiscardedValue)
	tx.heritageClauseVisitor = tx.EmitContext().NewNodeVisitor(tx.visitHeritageClause)
	tx.assignmentTargetVisitor = tx.EmitContext().NewNodeVisitor(tx.visitAssignmentTarget)
	tx.classElementVisitor = tx.EmitContext().NewNodeVisitor(tx.visitClassElement)
	tx.accessorFieldResultVisitor = tx.EmitContext().NewNodeVisitor(tx.visitAccessorFieldResult)
	tx.arrayAssignmentElementVisitor = tx.EmitContext().NewNodeVisitor(tx.visitArrayAssignmentElement)
	tx.objectAssignmentElementVisitor = tx.EmitContext().NewNodeVisitor(tx.visitObjectAssignmentElement)
	tx.substitutionVisitor = tx.EmitContext().NewNodeVisitor(tx.visitForSubstitution)
	tx.isAnonymousClassNeedingAssignedName = tx.isAnonymousClassNeedingAssignedNameWorker

	return result
}

// requiresBlockScopedVar returns true when private field temp variables should be
// declared as block-scoped (let) rather than function-scoped (var). This occurs when
// a class expression is directly inside a loop body.
// Replaces Strada's resolver.hasNodeCheckFlag(node, NodeCheckFlags.BlockScopedBindingInLoop).
func (tx *classFieldsTransformer) requiresBlockScopedVar() bool {
	return tx.inIterationStatement && tx.currentClassContainer != nil && ast.IsClassExpression(tx.currentClassContainer)
}

// classExpressionNeedsBlockScopedTemp returns true when the class expression's temp variable
// must be block-scoped. This is more specific than requiresBlockScopedVar: the class temp only
// needs to be block-scoped when the class expression has a non-static property with a computed
// property name inside a loop (matching the checker's BlockScopedBindingInLoop on the class node).
func (tx *classFieldsTransformer) classExpressionNeedsBlockScopedTemp() bool {
	if !tx.requiresBlockScopedVar() {
		return false
	}
	for _, member := range tx.currentClassContainer.Members() {
		if ast.IsPropertyDeclaration(member) && !ast.HasStaticModifier(member) &&
			member.Name() != nil && ast.IsComputedPropertyName(member.Name()) {
			return true
		}
	}
	return false
}

func (tx *classFieldsTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node {
	if node.IsDeclarationFile {
		return node.AsNode()
	}
	tx.lexicalEnvironment = nil
	tx.shouldTransformPrivateStaticElementsInFile = tx.EmitContext().EmitFlags(node.AsNode())&printer.EFTransformPrivateStaticElements != 0
	tx.classAliases = make(map[*ast.Node]*ast.IdentifierNode)
	tx.enclosingClassDeclarations.Clear()
	visited := tx.Visitor().VisitEachChild(node.AsNode())
	tx.EmitContext().AddEmitHelper(visited, tx.EmitContext().ReadEmitHelpers()...)
	tx.classAliases = nil
	tx.enclosingClassDeclarations.Clear()
	return visited
}

func (tx *classFieldsTransformer) visitModifier(node *ast.Node) *ast.Node {
	if node.Kind == ast.KindAccessorKeyword {
		if tx.shouldTransformAutoAccessorsInCurrentClass() {
			return nil
		}
		return node
	}
	if ast.IsModifier(node) {
		return node
	}
	return nil
}

func (tx *classFieldsTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) {
	grandparentNode = tx.parentNode
	tx.parentNode = tx.currentNode
	tx.currentNode = node
	return grandparentNode
}

func (tx *classFieldsTransformer) popNode(grandparentNode *ast.Node) {
	tx.currentNode = tx.parentNode
	tx.parentNode = grandparentNode
}

// visitForSubstitution visits nodes solely for class alias substitution in subtrees
// that don't contain class field or lexical this/super transforms. It substitutes
// identifiers that reference class declarations with their aliases, while skipping
// the .Name() of PropertyAccessExpressions since Strada's onSubstituteNode only
// fires for EmitHint.Expression, which excludes property access names.
func (tx *classFieldsTransformer) visitForSubstitution(node *ast.Node) *ast.Node {
	if node.Kind == ast.KindIdentifier {
		return tx.visitIdentifier(node.AsIdentifier())
	}
	if node.Kind == ast.KindPropertyAccessExpression && ast.IsIdentifier(node.AsPropertyAccessExpression().Name()) {
		return tx.visitPropertyAccessExpressionForSubstitution(node.AsPropertyAccessExpression())
	}
	return tx.substitutionVisitor.VisitEachChild(node)
}

// visit is the main visitor.
func (tx *classFieldsTransformer) visit(node *ast.Node) *ast.Node {
	grandparentNode := tx.pushNode(node)
	defer tx.popNode(grandparentNode)

	if node.SubtreeFacts()&(ast.SubtreeContainsClassFields|ast.SubtreeContainsLexicalThisOrSuper) == 0 {
		if tx.currentClassContainer != nil && len(tx.classAliases) > 0 {
			// Continue visiting for alias substitution even in non-class-field subtrees.
			return tx.visitForSubstitution(node)
		}
		return node
	}

	switch node.Kind {
	case ast.KindSourceFile:
		return tx.visitSourceFile(node.AsSourceFile())
	case ast.KindClassDeclaration:
		return tx.visitClassDeclaration(node.AsClassDeclaration())
	case ast.KindClassExpression:
		return tx.visitClassExpression(node.AsClassExpression())
	case ast.KindClassStaticBlockDeclaration, ast.KindPropertyDeclaration:
		panic("Use `classElementVisitor` instead.")
	case ast.KindPropertyAssignment:
		return tx.visitPropertyAssignment(node.AsPropertyAssignment())
	case ast.KindVariableStatement:
		return tx.visitVariableStatement(node.AsVariableStatement())
	case ast.KindVariableDeclaration:
		return tx.visitVariableDeclaration(node.AsVariableDeclaration())
	case ast.KindParameter:
		return tx.visitParameterDeclaration(node.AsParameterDeclaration())
	case ast.KindBindingElement:
		return tx.visitBindingElement(node.AsBindingElement())
	case ast.KindExportAssignment:
		return tx.visitExportAssignment(node.AsExportAssignment())
	case ast.KindPrivateIdentifier:
		return tx.visitPrivateIdentifier(node)
	case ast.KindPropertyAccessExpression:
		return tx.visitPropertyAccessExpression(node.AsPropertyAccessExpression())
	case ast.KindElementAccessExpression:
		return tx.visitElementAccessExpression(node.AsElementAccessExpression())
	case ast.KindPrefixUnaryExpression, ast.KindPostfixUnaryExpression:
		return tx.visitPreOrPostfixUnaryExpression(node, false /*discarded*/)
	case ast.KindBinaryExpression:
		return tx.visitBinaryExpression(node.AsBinaryExpression(), false /*discarded*/)
	case ast.KindParenthesizedExpression:
		return tx.visitParenthesizedExpression(node.AsParenthesizedExpression(), false /*discarded*/)
	case ast.KindCallExpression:
		return tx.visitCallExpression(node.AsCallExpression())
	case ast.KindExpressionStatement:
		return tx.visitExpressionStatement(node.AsExpressionStatement())
	case ast.KindTaggedTemplateExpression:
		return tx.visitTaggedTemplateExpression(node.AsTaggedTemplateExpression())
	case ast.KindForStatement:
		return tx.visitForStatement(node.AsForStatement())
	case ast.KindForInStatement, ast.KindForOfStatement, ast.KindDoStatement, ast.KindWhileStatement:
		return tx.setInIterationStatementAnd(true, (*classFieldsTransformer).visitEachChildOfNode, node)
	case ast.KindThisKeyword:
		return tx.visitThisExpression(node)
	case ast.KindFunctionDeclaration, ast.KindFunctionExpression:
		return tx.setInIterationStatementAnd(false, (*classFieldsTransformer).visitFunctionExpressionOrDeclaration, node)
	case ast.KindConstructor, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
		return tx.setInIterationStatementAnd(false, (*classFieldsTransformer).setClassElementAndVisitEachChild, node)
	default:
		return tx.Visitor().VisitEachChild(node)
	}
}

// visitDiscardedValue visits a node in an expression whose result is discarded.
func (tx *classFieldsTransformer) visitDiscardedValue(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindPrefixUnaryExpression, ast.KindPostfixUnaryExpression:
		return tx.visitPreOrPostfixUnaryExpression(node, true /*discarded*/)
	case ast.KindBinaryExpression:
		return tx.visitBinaryExpression(node.AsBinaryExpression(), true /*discarded*/)
	case ast.KindParenthesizedExpression:
		return tx.visitParenthesizedExpression(node.AsParenthesizedExpression(), true /*discarded*/)
	default:
		return tx.visit(node)
	}
}

// visitHeritageClause visits a node in a HeritageClause.
func (tx *classFieldsTransformer) visitHeritageClause(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindHeritageClause:
		return tx.heritageClauseVisitor.VisitEachChild(node)
	case ast.KindExpressionWithTypeArguments:
		return tx.visitExpressionWithTypeArgumentsInHeritageClause(node.AsExpressionWithTypeArguments())
	default:
		return tx.visit(node)
	}
}

// visitAssignmentTarget visits the assignment target of a destructuring assignment.
func (tx *classFieldsTransformer) visitAssignmentTarget(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression:
		return tx.visitAssignmentPattern(node)
	default:
		return tx.visit(node)
	}
}

func (tx *classFieldsTransformer) visitDestructuringAssignmentTarget(node *ast.Node) *ast.Node {
	if ast.IsObjectLiteralExpression(node) || ast.IsArrayLiteralExpression(node) {
		return tx.visitAssignmentPattern(node)
	}
	if ast.IsPropertyAccessExpression(node) && ast.IsPrivateIdentifier(node.AsPropertyAccessExpression().Name()) {
		return tx.wrapPrivateIdentifierForDestructuringTarget(node)
	}
	if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
		ast.IsSuperProperty(node) &&
		isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
		data := tx.lexicalEnvironment.data
		if data.facts&classFactsClassWasDecorated != 0 {
			return tx.visitInvalidSuperProperty(node)
		}
		if data.classConstructor != nil && data.superClassReference != nil {
			var name *ast.Expression
			if ast.IsElementAccessExpression(node) {
				name = tx.Visitor().VisitNode(node.AsElementAccessExpression().ArgumentExpression)
			} else if ast.IsPropertyAccessExpression(node) && ast.IsIdentifier(node.AsPropertyAccessExpression().Name()) {
				name = tx.Factory().NewStringLiteralFromNode(node.AsPropertyAccessExpression().Name())
			}
			if name != nil {
				temp := tx.Factory().NewTempVariable()
				setExpr := tx.Factory().NewReflectSetCall(
					data.superClassReference,
					name,
					temp,
					data.classConstructor,
				)
				return tx.Factory().NewAssignmentTargetWrapper(temp, setExpr)
			}
		}
	}
	return tx.Visitor().VisitEachChild(node)
}

// visitClassElement visits a member of a class.
func (tx *classFieldsTransformer) visitClassElement(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindConstructor:
		return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitConstructorDeclaration, node)
	case ast.KindGetAccessor, ast.KindSetAccessor, ast.KindMethodDeclaration:
		return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitMethodOrAccessorDeclaration, node)
	case ast.KindPropertyDeclaration:
		return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitPropertyDeclaration, node)
	case ast.KindClassStaticBlockDeclaration:
		return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitClassStaticBlockDeclaration, node)
	case ast.KindComputedPropertyName:
		return tx.visitComputedPropertyName(node.AsComputedPropertyName())
	case ast.KindSemicolonClassElement:
		return node
	default:
		if ast.IsModifierLike(node) {
			return tx.visitModifier(node)
		}
		return tx.visit(node)
	}
}

// visitPropertyName visits a property name of a class member.
func (tx *classFieldsTransformer) visitPropertyName(name *ast.PropertyName) *ast.PropertyName {
	if ast.IsComputedPropertyName(name) {
		return tx.visitComputedPropertyName(name.AsComputedPropertyName())
	}
	return tx.Visitor().VisitNode(name)
}

// visitAccessorFieldResult visits the results of an auto-accessor field transformation in a second pass.
func (tx *classFieldsTransformer) visitAccessorFieldResult(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindPropertyDeclaration:
		return tx.transformFieldInitializer(node.AsPropertyDeclaration())
	case ast.KindGetAccessor, ast.KindSetAccessor:
		return tx.visitClassElement(node)
	default:
		debug.FailBadSyntaxKind(node, "Expected node to either be a PropertyDeclaration, GetAccessorDeclaration, or SetAccessorDeclaration")
		return nil
	}
}

// visitIdentifier replaces Strada's onSubstituteNode/trySubstituteClassAlias. Instead of
// substituting at emit time using NodeCheckFlags.ConstructorReference, we resolve the
// identifier to its declaration and check if that declaration has a registered alias.
func (tx *classFieldsTransformer) visitIdentifier(node *ast.Identifier) *ast.Node {
	declaration := tx.resolver.GetReferencedValueDeclaration(tx.EmitContext().MostOriginal(node.AsNode()))
	if declaration != nil {
		if alias, ok := tx.classAliases[declaration]; ok && tx.enclosingClassDeclarations.Has(declaration) {
			clone := alias.Clone(tx.Factory())
			tx.EmitContext().SetSourceMapRange(clone, node.Loc)
			tx.EmitContext().SetCommentRange(clone, node.Loc)
			return clone
		}
	}
	return node.AsNode()
}

// visitPrivateIdentifier handles an undeclared private name. Replace it with an empty
// identifier to indicate a problem with the code.
// Note: private identifiers in statement position (e.g., `#;`) are intercepted earlier
// by visitExpressionStatement, which preserves them so the runtime throws a SyntaxError.
func (tx *classFieldsTransformer) visitPrivateIdentifier(node *ast.Node) *ast.Node {
	if !tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		return node
	}
	if tx.parentNode != nil && ast.IsStatement(tx.parentNode) {
		return node
	}
	result := tx.Factory().NewIdentifier("")
	tx.EmitContext().SetOriginal(result, node)
	return result
}

// transformPrivateIdentifierInInExpression visits `#id in expr`.
func (tx *classFieldsTransformer) transformPrivateIdentifierInInExpression(node *ast.BinaryExpression) *ast.Node {
	info := tx.accessPrivateIdentifier(node.Left)
	if info != nil {
		receiver := tx.Visitor().VisitNode(node.Right)
		result := tx.Factory().NewClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver)
		tx.EmitContext().SetOriginal(result, node.AsNode())
		return result
	}
	// Private name has not been declared. Subsequent transformers will handle this error
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitPropertyAssignment(node *ast.PropertyAssignment) *ast.Node {
	// 13.2.5.5 RS: PropertyDefinitionEvaluation
	//   PropertyAssignment : PropertyName `:` AssignmentExpression
	//     ...
	//     5. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and _isProtoSetter_ is *false*, then
	//        a. Let _popValue_ be ? NamedEvaluation of |AssignmentExpression| with argument _propKey_.
	//     ...

	if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/).AsPropertyAssignment()
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitVariableStatement(node *ast.VariableStatement) *ast.Node {
	savedPendingStatements := tx.pendingStatements
	tx.pendingStatements = nil

	visitedNode := tx.Visitor().VisitEachChild(node.AsNode())

	if len(tx.pendingStatements) > 0 {
		result := make([]*ast.Node, 0, 1+len(tx.pendingStatements))
		result = append(result, visitedNode)
		result = append(result, tx.pendingStatements...)
		tx.pendingStatements = savedPendingStatements
		return tx.Factory().NewSyntaxList(result)
	}

	tx.pendingStatements = savedPendingStatements
	return visitedNode
}

func (tx *classFieldsTransformer) visitVariableDeclaration(node *ast.VariableDeclaration) *ast.Node {
	// 14.3.1.2 RS: Evaluation
	//   LexicalBinding : BindingIdentifier Initializer
	//     ...
	//     3. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//        a. Let _value_ be ? NamedEvaluation of |Initializer| with argument _bindingId_.
	//     ...
	//
	// 14.3.2.1 RS: Evaluation
	//   VariableDeclaration : BindingIdentifier Initializer
	//     ...
	//     3. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//        a. Let _value_ be ? NamedEvaluation of |Initializer| with argument _bindingId_.
	//     ...

	if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsVariableDeclaration()
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitParameterDeclaration(node *ast.ParameterDeclaration) *ast.Node {
	// 8.6.3 RS: IteratorBindingInitialization
	//   SingleNameBinding : BindingIdentifier Initializer?
	//     ...
	//     5. If |Initializer| is present and _v_ is *undefined*, then
	//        a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//           i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_.
	//     ...
	//
	// 14.3.3.3 RS: KeyedBindingInitialization
	//   SingleNameBinding : BindingIdentifier Initializer?
	//     ...
	//     4. If |Initializer| is present and _v_ is *undefined*, then
	//        a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//           i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_.
	//     ...

	if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsParameterDeclaration()
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitBindingElement(node *ast.BindingElement) *ast.Node {
	// 8.6.3 RS: IteratorBindingInitialization
	//   SingleNameBinding : BindingIdentifier Initializer?
	//     ...
	//     5. If |Initializer| is present and _v_ is *undefined*, then
	//        a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//           i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_.
	//     ...
	//
	// 14.3.3.3 RS: KeyedBindingInitialization
	//   SingleNameBinding : BindingIdentifier Initializer?
	//     ...
	//     4. If |Initializer| is present and _v_ is *undefined*, then
	//        a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//           i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_.
	//     ...

	if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsBindingElement()
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitExportAssignment(node *ast.ExportAssignment) *ast.Node {
	// 16.2.3.7 RS: Evaluation
	//   ExportDeclaration : `export` `default` AssignmentExpression `;`
	//     1. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true*, then
	//        a. Let _value_ be ? NamedEvaluation of |AssignmentExpression| with argument `"default"`.
	//     ...

	// NOTE: Since emit for `export =` translates to `module.exports = ...`, the assigned name of the class
	// is `""`.

	if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		assignedName := ""
		if !node.IsExportEquals {
			assignedName = "default"
		}
		node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), true /*ignoreEmptyStringLiteral*/, assignedName).AsExportAssignment()
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) injectPendingExpressions(expression *ast.Expression) *ast.Expression {
	if len(tx.pendingExpressions) > 0 {
		if ast.IsParenthesizedExpression(expression) {
			tx.pendingExpressions = append(tx.pendingExpressions, expression.Expression())
			expression = tx.Factory().UpdateParenthesizedExpression(
				expression.AsParenthesizedExpression(),
				tx.Factory().InlineExpressions(tx.pendingExpressions),
			)
		} else {
			exprs := append(tx.pendingExpressions, expression)
			expression = tx.Factory().InlineExpressions(exprs)
		}
		tx.pendingExpressions = nil
	}
	return expression
}

func (tx *classFieldsTransformer) visitComputedPropertyName(node *ast.ComputedPropertyName) *ast.Node {
	// Computed property names are evaluated in the enclosing scope, not the current class.
	// Replaces Strada's onEmitNode for ComputedPropertyName which switches to
	// lexicalEnvironment?.previous. We do this explicitly during transformation.
	savedLexicalEnvironment := tx.lexicalEnvironment
	savedInsideComputedPropertyName := tx.insideComputedPropertyName
	tx.insideComputedPropertyName = true
	if tx.lexicalEnvironment != nil && tx.lexicalEnvironment.previous != nil {
		tx.lexicalEnvironment = tx.lexicalEnvironment.previous
	}
	expression := tx.Visitor().VisitNode(node.Expression)
	tx.lexicalEnvironment = savedLexicalEnvironment
	tx.insideComputedPropertyName = savedInsideComputedPropertyName
	return tx.Factory().UpdateComputedPropertyName(node, tx.injectPendingExpressions(expression))
}

func (tx *classFieldsTransformer) visitConstructorDeclaration(node *ast.Node) *ast.Node {
	if tx.currentClassContainer != nil {
		return tx.transformConstructor(node.AsConstructorDeclaration(), tx.currentClassContainer)
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) shouldTransformClassElementToWeakMap(node *ast.Node) bool {
	if tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		return true
	}
	return tx.shouldAlwaysTransformPrivateStaticElements(node)
}

func (tx *classFieldsTransformer) shouldAlwaysTransformPrivateStaticElements(node *ast.Node) bool {
	return ast.HasStaticModifier(node) && tx.EmitContext().EmitFlags(node)&printer.EFTransformPrivateStaticElements != 0
}

// nodeHasTransformPrivateStaticElementsFlag checks the emit flag on a class node (not a member).
// Unlike shouldAlwaysTransformPrivateStaticElements, this does not check HasStaticModifier,
// since class nodes themselves don't have a static modifier.
func (tx *classFieldsTransformer) nodeHasTransformPrivateStaticElementsFlag(node *ast.Node) bool {
	return tx.EmitContext().EmitFlags(node)&printer.EFTransformPrivateStaticElements != 0
}

func (tx *classFieldsTransformer) visitMethodOrAccessorDeclaration(node *ast.Node) *ast.Node {
	debug.Assert(!ast.HasDecorators(node))

	if !ast.IsPrivateIdentifierClassElementDeclaration(node) || !tx.shouldTransformClassElementToWeakMap(node) {
		return tx.classElementVisitor.VisitEachChild(node)
	}

	// leave invalid code untransformed
	info := tx.accessPrivateIdentifier(node.Name())
	debug.Assert(info != nil, "Undeclared private name for property declaration.")
	if !info.isValid {
		return node
	}

	functionName := tx.getHoistedFunctionName(node)
	if functionName != nil {
		modifiers := tx.extractNonStaticNonAccessorModifiers(node)
		tx.EmitContext().StartVariableEnvironment()
		saved := tx.inIterationStatement
		tx.inIterationStatement = false
		body := tx.EmitContext().VisitFunctionBody(node.Body(), tx.Visitor())
		params := tx.Visitor().VisitNodes(node.ParameterList())
		tx.inIterationStatement = saved

		funcExpr := tx.Factory().NewFunctionExpression(modifiers, node.BodyData().AsteriskToken, functionName, nil, params, nil, nil, body)
		assignment := tx.Factory().NewAssignmentExpression(functionName, funcExpr)
		tx.addPendingExpressions(assignment)
	}

	// remove method declaration from class
	return nil
}

func (tx *classFieldsTransformer) extractNonStaticNonAccessorModifiers(node *ast.Node) *ast.ModifierList {
	return transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^(ast.ModifierFlagsStatic | ast.ModifierFlagsAccessor))
}

func (tx *classFieldsTransformer) setCurrentClassElementAnd(classElement *ast.ClassElement, visitor func(tx *classFieldsTransformer, node *ast.Node) *ast.Node, node *ast.Node) *ast.Node {
	if classElement != tx.currentClassElement {
		saved := tx.currentClassElement
		tx.currentClassElement = classElement
		result := visitor(tx, node)
		tx.currentClassElement = saved
		return result
	}
	return visitor(tx, node)
}

// visitEachChildOfNode just calls Visitor.VisitEachChild, but is necessary to avoid repeated closure allocations when passing as a callback.
func (tx *classFieldsTransformer) visitEachChildOfNode(node *ast.Node) *ast.Node {
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) setInIterationStatementAnd(inIteration bool, visitor func(tx *classFieldsTransformer, node *ast.Node) *ast.Node, node *ast.Node) *ast.Node {
	if tx.inIterationStatement != inIteration {
		saved := tx.inIterationStatement
		tx.inIterationStatement = inIteration
		result := visitor(tx, node)
		tx.inIterationStatement = saved
		return result
	}
	return visitor(tx, node)
}

func (tx *classFieldsTransformer) clearClassElementAndVisitEachChild(node *ast.Node) *ast.Node {
	return tx.setCurrentClassElementAnd(nil, (*classFieldsTransformer).visitEachChildOfNode, node)
}

// visitFunctionExpressionOrDeclaration handles lexical environment scoping for function
// expressions and declarations, mirroring Strada's onEmitNode behavior.
//
// In Strada, onEmitNode checks whether a FunctionExpression has been registered in
// lexicalEnvironmentMap (via its original node). If found, the lexical environment is
// restored; otherwise it is cleared (since regular functions create a new `this` scope).
//
// Since Corsa performs substitution eagerly (no emit-time hooks), we replicate this by
// preserving currentClassElement for function expressions whose original node is a class
// member of the current class. This allows visitThisExpression to correctly substitute
// `this` -> `_classThis` inside synthesized functions (e.g., ES decorator descriptor
// methods for static private auto-accessors).
func (tx *classFieldsTransformer) visitFunctionExpressionOrDeclaration(node *ast.Node) *ast.Node {
	if tx.currentClassElement != nil {
		original := tx.EmitContext().MostOriginal(node)
		if original != node && tx.currentClassContainer != nil {
			for _, member := range tx.currentClassContainer.Members() {
				if tx.EmitContext().MostOriginal(member) == original && ast.IsStatic(member) {
					// The function expression originates from a static class member (e.g., a
					// descriptor method synthesized by the ES decorator transformer for a
					// static private auto-accessor). Preserve the current class element so
					// that visitThisExpression can substitute `this` with `_classThis`.
					// Non-static members must NOT preserve the class element because `this`
					// inside their descriptor functions should remain dynamic.
					return tx.visitEachChildOfNode(node)
				}
			}
		}
	}
	return tx.setCurrentClassElementAnd(nil, (*classFieldsTransformer).visitEachChildOfNode, node)
}

func (tx *classFieldsTransformer) setClassElementAndVisitEachChild(node *ast.Node) *ast.Node {
	return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitEachChildOfNode, node)
}

func (tx *classFieldsTransformer) getHoistedFunctionName(node *ast.Node) *ast.IdentifierNode {
	debug.Assert(node.Name() != nil && ast.IsPrivateIdentifier(node.Name()))
	info := tx.accessPrivateIdentifier(node.Name())
	debug.Assert(info != nil, "Undeclared private name for property declaration.")
	if info.kind == printer.PrivateIdentifierKindMethod {
		return info.methodName
	}
	if info.kind == printer.PrivateIdentifierKindAccessor {
		if ast.IsGetAccessorDeclaration(node) {
			return info.getterName
		}
		if ast.IsSetAccessorDeclaration(node) {
			return info.setterName
		}
	}
	return nil
}

func (tx *classFieldsTransformer) tryGetClassThis() *ast.Expression {
	if classThis := tx.tryGetClassThisNoContainer(); classThis != nil {
		return classThis
	}
	if tx.currentClassContainer != nil {
		return tx.currentClassContainer.Name()
	}
	return nil
}

func (tx *classFieldsTransformer) tryGetClassThisNoContainer() *ast.Expression {
	lex := tx.getClassLexicalEnvironment()
	if lex.classThis != nil {
		return lex.classThis
	}
	if lex.classConstructor != nil {
		return lex.classConstructor
	}
	return nil
}

// transformAutoAccessor transforms an auto-accessor property:
//
//	accessor x = 1;
//
// into:
//
//	#x = 1;
//	get x() { return this.#x; }
//	set x(value) { this.#x = value; }
func (tx *classFieldsTransformer) transformAutoAccessor(node *ast.PropertyDeclaration) *ast.Node {
	commentRange := tx.EmitContext().CommentRange(node.AsNode())
	sourceMapRange := tx.EmitContext().SourceMapRange(node.AsNode())

	// Since we're creating two declarations where there was previously one, cache
	// the expression for any computed property names.
	name := node.Name()
	getterName := name
	setterName := name
	if ast.IsComputedPropertyName(name) && !transformers.IsSimpleInlineableExpression(name.Expression()) {
		cacheAssignment := findComputedPropertyNameCacheAssignment(tx.EmitContext(), name)
		if cacheAssignment != nil {
			getterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), tx.Visitor().VisitNode(name.Expression()))
			setterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), cacheAssignment.Left)
		} else {
			temp := tx.Factory().NewTempVariable()
			tx.EmitContext().SetSourceMapRange(temp, name.Expression().Loc)
			tx.EmitContext().AddVariableDeclaration(temp)
			expression := tx.Visitor().VisitNode(name.Expression())
			assignment := tx.Factory().NewAssignmentExpression(temp, expression)
			tx.EmitContext().SetSourceMapRange(assignment, name.Expression().Loc)
			getterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), assignment)
			setterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), temp)
		}
	}

	modifiers := tx.modifierVisitor.VisitModifiers(node.Modifiers())
	backingField := createAccessorPropertyBackingField(tx.Factory(), node, modifiers, node.Initializer)
	tx.EmitContext().SetOriginal(backingField, node.AsNode())
	tx.EmitContext().AddEmitFlags(backingField, printer.EFNoComments)
	tx.EmitContext().SetSourceMapRange(backingField, sourceMapRange)

	var receiver *ast.Expression
	if ast.IsStatic(node.AsNode()) {
		receiver = tx.tryGetClassThis()
		if receiver == nil {
			receiver = tx.Factory().NewThisExpression()
		}
	} else {
		receiver = tx.Factory().NewThisExpression()
	}

	getter := tx.createAccessorPropertyGetRedirector(node, modifiers, getterName, receiver)
	tx.EmitContext().SetOriginal(getter, node.AsNode())
	tx.EmitContext().SetCommentRange(getter, commentRange)
	tx.EmitContext().SetSourceMapRange(getter, sourceMapRange)

	// create a fresh copy of the modifiers so that we don't duplicate comments
	var setterModifiers *ast.ModifierList
	if modifiers != nil {
		setterModifiers = tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifiers.ModifierFlags, tx.Factory().NewModifier))
	}
	setter := tx.createAccessorPropertySetRedirector(node, setterModifiers, setterName, receiver)
	tx.EmitContext().SetOriginal(setter, node.AsNode())
	tx.EmitContext().AddEmitFlags(setter, printer.EFNoComments)
	tx.EmitContext().SetSourceMapRange(setter, sourceMapRange)

	// Visit the results in a second pass
	visited, _ := tx.accessorFieldResultVisitor.VisitSlice([]*ast.Node{backingField, getter, setter})
	return tx.Factory().NewSyntaxList(visited)
}

func (tx *classFieldsTransformer) transformPrivateFieldInitializer(node *ast.PropertyDeclaration) *ast.Node {
	if tx.shouldTransformClassElementToWeakMap(node.AsNode()) {
		// If we are transforming private elements into WeakMap/WeakSet, we should elide the node.
		info := tx.accessPrivateIdentifier(node.Name())
		debug.Assert(info != nil, "Undeclared private name for property declaration.")

		// Leave invalid code untransformed
		if !info.isValid {
			return node.AsNode()
		}

		// If we encounter a valid private static field and we're not transforming
		// class static blocks, convert to a static block initializer.
		if info.isStatic && !tx.shouldTransformPrivateElementsOrClassStaticBlocks {
			// TODO: fix
			statement := tx.transformPropertyOrClassStaticBlock(node.AsNode(), tx.Factory().NewThisExpression())
			if statement != nil {
				return tx.Factory().NewClassStaticBlockDeclaration(
					nil, /*modifiers*/
					tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{statement}), true /*multiLine*/),
				)
			}
		}

		return nil
	}

	if tx.shouldTransformInitializersUsingSet && !ast.HasStaticModifier(node.AsNode()) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil &&
		tx.lexicalEnvironment.data.facts&classFactsWillHoistInitializersToConstructor != 0 {
		return tx.Factory().UpdatePropertyDeclaration(
			node,
			tx.Visitor().VisitModifiers(node.Modifiers()),
			node.Name(),
			nil, /*postfixToken*/
			nil, /*typeNode*/
			nil, /*initializer*/
		)
	}

	if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsPropertyDeclaration()
	}

	return tx.Factory().UpdatePropertyDeclaration(
		node,
		tx.modifierVisitor.VisitModifiers(node.Modifiers()),
		tx.visitPropertyName(node.Name()),
		nil, /*postfixToken*/
		nil, /*typeNode*/
		tx.Visitor().VisitNode(node.Initializer),
	)
}

func (tx *classFieldsTransformer) transformPublicFieldInitializer(node *ast.PropertyDeclaration) *ast.Node {
	if tx.shouldTransformInitializers && !ast.IsAutoAccessorPropertyDeclaration(node.AsNode()) {
		// Elide the property declaration; the initializer will be moved to the constructor.
		// For computed property names, we still need to emit the expression.
		expr := tx.getPropertyNameExpressionIfNeeded(node.Name(), node.Initializer != nil || tx.compilerOptions.GetUseDefineForClassFields())
		if expr != nil {
			for e := range flattenCommaList(expr) {
				tx.addPendingExpressions(e)
			}
		}

		// When target >= ES2022 (i.e., !shouldTransformPrivateElementsOrClassStaticBlocks) and we
		// still need to transform initializers (useDefineForClassFields: false), static property
		// initializers must be converted into `static { this.x = ...; }` blocks so that `this`
		// refers to the class constructor inside the static block.
		if ast.IsStatic(node.AsNode()) && !tx.shouldTransformPrivateElementsOrClassStaticBlocks {
			initializerStatement := tx.transformPropertyOrClassStaticBlock(node.AsNode(), tx.Factory().NewThisExpression())
			if initializerStatement != nil {
				staticBlock := tx.Factory().NewClassStaticBlockDeclaration(
					nil, /*modifiers*/
					tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{initializerStatement}), false),
				)

				tx.EmitContext().SetOriginal(staticBlock, node.AsNode())
				tx.EmitContext().SetCommentRange(staticBlock, node.Loc)

				tx.EmitContext().AddEmitFlags(initializerStatement, printer.EFNoComments)
				return staticBlock
			}
		}

		return nil
	}

	return tx.Factory().UpdatePropertyDeclaration(
		node,
		tx.modifierVisitor.VisitModifiers(node.Modifiers()),
		tx.visitPropertyName(node.Name()),
		nil, /*postfixToken*/
		nil, /*typeNode*/
		tx.Visitor().VisitNode(node.Initializer),
	)
}

func (tx *classFieldsTransformer) transformFieldInitializer(node *ast.PropertyDeclaration) *ast.Node {
	debug.Assert(!ast.HasDecorators(node.AsNode()), "Decorators should already have been transformed and elided.")
	if ast.IsPrivateIdentifierClassElementDeclaration(node.AsNode()) {
		return tx.transformPrivateFieldInitializer(node)
	}
	return tx.transformPublicFieldInitializer(node)
}

func (tx *classFieldsTransformer) shouldTransformAutoAccessorsInCurrentClass() bool {
	if tx.shouldTransformAutoAccessors {
		return true
	}
	// When targeting ESNext with useDefineForClassFields: false, auto-accessors are only
	// transformed if the current class will hoist initializers to the constructor.
	return tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil &&
		tx.lexicalEnvironment.data.facts&classFactsWillHoistInitializersToConstructor != 0
}

func (tx *classFieldsTransformer) visitPropertyDeclaration(node *ast.Node) *ast.Node {
	// If this is an auto-accessor, we defer to `transformAutoAccessor`. That function
	// will in turn call `transformFieldInitializer` as needed.
	propDecl := node.AsPropertyDeclaration()
	if ast.IsAutoAccessorPropertyDeclaration(node) && (tx.shouldTransformAutoAccessorsInCurrentClass() ||
		ast.HasStaticModifier(node) && tx.shouldAlwaysTransformPrivateStaticElements(node)) {
		return tx.transformAutoAccessor(propDecl)
	}
	return tx.transformFieldInitializer(propDecl)
}

func (tx *classFieldsTransformer) createPrivateIdentifierAccess(info *privateIdentifierInfo, receiver *ast.Expression) *ast.Expression {
	receiver = tx.Visitor().VisitNode(receiver)
	return tx.createPrivateIdentifierAccessHelper(info, receiver)
}

func (tx *classFieldsTransformer) createPrivateIdentifierAccessHelper(info *privateIdentifierInfo, receiver *ast.Expression) *ast.Expression {
	tx.EmitContext().SetCommentRange(receiver, core.NewTextRange(-1, receiver.End()))

	switch info.kind {
	case printer.PrivateIdentifierKindAccessor:
		return tx.Factory().NewClassPrivateFieldGetHelper(
			receiver,
			info.brandCheckIdentifier,
			info.kind,
			info.getterName,
		)
	case printer.PrivateIdentifierKindMethod:
		return tx.Factory().NewClassPrivateFieldGetHelper(
			receiver,
			info.brandCheckIdentifier,
			info.kind,
			info.methodName,
		)
	case printer.PrivateIdentifierKindField:
		var f *ast.IdentifierNode
		if info.isStatic {
			f = info.variableName
		}
		return tx.Factory().NewClassPrivateFieldGetHelper(
			receiver,
			info.brandCheckIdentifier,
			info.kind,
			f,
		)
	case printer.PrivateIdentifierKindUntransformed:
		debug.Fail("Access helpers should not be created for untransformed private elements")
		return nil
	}
	debug.AssertNever(info, "Unknown private element type")
	return nil
}

func (tx *classFieldsTransformer) visitPropertyAccessExpression(node *ast.PropertyAccessExpression) *ast.Node {
	if ast.IsPrivateIdentifier(node.Name()) {
		info := tx.accessPrivateIdentifier(node.Name())
		if info != nil {
			result := tx.createPrivateIdentifierAccess(info, node.Expression)
			tx.EmitContext().SetOriginal(result, node.AsNode())
			result.Loc = node.Loc
			return result
		}
	}
	if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
		ast.IsSuperProperty(node.AsNode()) && ast.IsIdentifier(node.Name()) &&
		isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
		data := tx.lexicalEnvironment.data
		if data.facts&classFactsClassWasDecorated != 0 {
			return tx.visitInvalidSuperProperty(node.AsNode())
		}
		if data.classConstructor != nil && data.superClassReference != nil {
			// converts `super.x` into `Reflect.get(_baseTemp, "x", _classTemp)`
			superProperty := tx.Factory().NewReflectGetCall(
				data.superClassReference,
				tx.Factory().NewStringLiteralFromNode(node.Name()),
				data.classConstructor,
			)
			tx.EmitContext().SetOriginal(superProperty, node.Expression)
			superProperty.Loc = node.Expression.Loc
			return superProperty
		}
	}
	// Visit only the expression, not the name (when it's a regular identifier), to prevent
	// substitution of property names. Strada's onSubstituteNode only fires for
	// EmitHint.Expression, which excludes the .name of PropertyAccessExpression.
	// Private identifier names are still visited through VisitEachChild so they can be
	// transformed by visitPrivateIdentifier.
	if ast.IsIdentifier(node.Name()) {
		return tx.visitPropertyAccessExpressionForSubstitution(node)
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

// visitPropertyAccessExpressionForSubstitution visits only the expression of a PropertyAccessExpression,
// leaving the name unchanged. This prevents the name from being treated as a standalone identifier
// reference and incorrectly substituted with a class alias.
func (tx *classFieldsTransformer) visitPropertyAccessExpressionForSubstitution(node *ast.PropertyAccessExpression) *ast.Node {
	expression := tx.Visitor().VisitNode(node.Expression)
	if expression != node.Expression {
		return tx.Factory().UpdatePropertyAccessExpression(node, expression, node.QuestionDotToken, node.Name(), node.Flags)
	}
	return node.AsNode()
}

func (tx *classFieldsTransformer) visitElementAccessExpression(node *ast.ElementAccessExpression) *ast.Node {
	if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
		ast.IsSuperProperty(node.AsNode()) &&
		isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
		data := tx.lexicalEnvironment.data
		if data.facts&classFactsClassWasDecorated != 0 {
			return tx.visitInvalidSuperProperty(node.AsNode())
		}
		if data.classConstructor != nil && data.superClassReference != nil {
			// converts `super[x]` into `Reflect.get(_baseTemp, x, _classTemp)`
			superProperty := tx.Factory().NewReflectGetCall(
				data.superClassReference,
				tx.Visitor().VisitNode(node.ArgumentExpression),
				data.classConstructor,
			)
			tx.EmitContext().SetOriginal(superProperty, node.Expression)
			superProperty.Loc = node.Expression.Loc
			return superProperty
		}
	}
	return tx.Visitor().VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitPreOrPostfixUnaryExpression(node *ast.Node, discarded bool) *ast.Node {
	var operator ast.Kind
	var operand *ast.Node
	if ast.IsPrefixUnaryExpression(node) {
		operator = node.AsPrefixUnaryExpression().Operator
		operand = node.AsPrefixUnaryExpression().Operand
	} else {
		operator = node.AsPostfixUnaryExpression().Operator
		operand = node.AsPostfixUnaryExpression().Operand
	}

	if operator == ast.KindPlusPlusToken || operator == ast.KindMinusMinusToken {
		operandSkipped := ast.SkipParentheses(operand)

		// Private identifier property access
		if ast.IsPropertyAccessExpression(operandSkipped) && ast.IsPrivateIdentifier(operandSkipped.Name()) {
			info := tx.accessPrivateIdentifier(operandSkipped.Name())
			if info != nil {
				receiver := tx.Visitor().VisitNode(operandSkipped.Expression())
				readExpression, initializeExpression := tx.createCopiableReceiverExpr(receiver)

				expression := tx.createPrivateIdentifierAccessHelper(info, readExpression)
				var temp *ast.IdentifierNode
				if !ast.IsPrefixUnaryExpression(node) && !discarded {
					temp = tx.Factory().NewTempVariable()
					tx.EmitContext().AddVariableDeclaration(temp)
				}
				expression = expandPreOrPostfixIncrementOrDecrementExpression(tx.Factory(), tx.EmitContext(), node, expression, temp)
				assignReceiver := readExpression
				if initializeExpression != nil {
					assignReceiver = initializeExpression
				}
				expression = tx.createPrivateIdentifierAssignment(info, assignReceiver, expression, ast.KindEqualsToken)
				tx.EmitContext().SetOriginal(expression, node)
				expression.Loc = node.Loc
				if temp != nil {
					expression = tx.Factory().NewCommaExpression(expression, temp)
					expression.Loc = node.Loc
				}
				return expression
			}
		} else if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
			ast.IsSuperProperty(operandSkipped) &&
			isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
			tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
			// converts `++super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = ++_a), _classTemp), _b)`
			// converts `++super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = ++_b), _classTemp), _c)`
			// converts `--super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = --_a), _classTemp), _b)`
			// converts `--super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = --_b), _classTemp), _c)`
			// converts `super.a++` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a++), _classTemp), _b)`
			// converts `super[f()]++` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b++), _classTemp), _c)`
			// converts `super.a--` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a--), _classTemp), _b)`
			// converts `super[f()]--` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b--), _classTemp), _c)`
			data := tx.lexicalEnvironment.data
			if data.facts&classFactsClassWasDecorated != 0 {
				visitedExpr := tx.visitInvalidSuperProperty(operandSkipped)
				if ast.IsPrefixUnaryExpression(node) {
					return tx.Factory().UpdatePrefixUnaryExpression(node.AsPrefixUnaryExpression(), node.AsPrefixUnaryExpression().Operator, visitedExpr)
				}
				return tx.Factory().UpdatePostfixUnaryExpression(node.AsPostfixUnaryExpression(), visitedExpr, node.AsPostfixUnaryExpression().Operator)
			}
			if data.classConstructor != nil && data.superClassReference != nil {
				var setterName *ast.Expression
				var getterName *ast.Expression
				if ast.IsPropertyAccessExpression(operandSkipped) {
					if ast.IsIdentifier(operandSkipped.Name()) {
						getterName = tx.Factory().NewStringLiteralFromNode(operandSkipped.Name())
						setterName = getterName
					}
				} else if ast.IsElementAccessExpression(operandSkipped) {
					if transformers.IsSimpleInlineableExpression(operandSkipped.AsElementAccessExpression().ArgumentExpression) {
						getterName = operandSkipped.AsElementAccessExpression().ArgumentExpression
						setterName = getterName
					} else {
						getterName = tx.Factory().NewTempVariable()
						tx.EmitContext().AddVariableDeclaration(getterName)
						setterName = tx.Factory().NewAssignmentExpression(getterName, tx.Visitor().VisitNode(operandSkipped.AsElementAccessExpression().ArgumentExpression))
					}
				}
				if setterName != nil && getterName != nil {
					expression := tx.Factory().NewReflectGetCall(data.superClassReference, getterName, data.classConstructor)
					expression.Loc = operandSkipped.Loc

					var temp *ast.IdentifierNode
					if !discarded {
						temp = tx.Factory().NewTempVariable()
						tx.EmitContext().AddVariableDeclaration(temp)
					}
					expression = expandPreOrPostfixIncrementOrDecrementExpression(tx.Factory(), tx.EmitContext(), node, expression, temp)
					expression = tx.Factory().NewReflectSetCall(data.superClassReference, setterName, expression, data.classConstructor)
					tx.EmitContext().SetOriginal(expression, node)
					expression.Loc = node.Loc
					if temp != nil {
						expression = tx.Factory().NewCommaExpression(expression, temp)
						expression.Loc = node.Loc
					}
					return expression
				}
			}
		}
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitForStatement(node *ast.ForStatement) *ast.Node {
	initializer := tx.discardedValueVisitor.VisitNode(node.Initializer)
	condition := tx.Visitor().VisitNode(node.Condition)
	incrementor := tx.discardedValueVisitor.VisitNode(node.Incrementor)
	saved := tx.inIterationStatement
	tx.inIterationStatement = true
	body := tx.EmitContext().VisitIterationBody(node.Statement, tx.Visitor())
	tx.inIterationStatement = saved
	return tx.Factory().UpdateForStatement(node, initializer, condition, incrementor, body)
}

func (tx *classFieldsTransformer) visitExpressionStatement(node *ast.ExpressionStatement) *ast.Node {
	// Preserve private identifiers that appear directly as the expression of an
	// ExpressionStatement (e.g., `#;`). This is error-recovery output from the parser
	// for invalid syntax. Keeping it ensures the runtime throws a SyntaxError rather
	// than silently succeeding with an empty statement.
	if ast.IsPrivateIdentifier(node.Expression) && tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		return node.AsNode()
	}
	return tx.Factory().UpdateExpressionStatement(
		node,
		tx.discardedValueVisitor.VisitNode(node.Expression),
	)
}

func (tx *classFieldsTransformer) createCopiableReceiverExpr(receiver *ast.Expression) (readExpression *ast.Expression, initializeExpression *ast.Expression) {
	clone := receiver
	if !ast.NodeIsSynthesized(receiver) {
		clone = receiver.Clone(tx.Factory())
	}
	if transformers.IsSimpleInlineableExpression(receiver) {
		return clone, nil
	}
	readExpression = tx.Factory().NewTempVariable()
	tx.EmitContext().AddVariableDeclaration(readExpression)
	initializeExpression = tx.Factory().NewAssignmentExpression(readExpression, clone)
	return readExpression, initializeExpression
}

func (tx *classFieldsTransformer) visitCallExpression(node *ast.CallExpression) *ast.Node {
	if ast.IsPropertyAccessExpression(node.Expression) && ast.IsPrivateIdentifier(node.Expression.AsPropertyAccessExpression().Name()) &&
		tx.accessPrivateIdentifier(node.Expression.AsPropertyAccessExpression().Name()) != nil {
		// obj.#x()

		// Transform call expressions of private names to properly bind the `this` parameter.
		thisArg, target := tx.createCallBinding(node.Expression)
		visitedTarget := tx.Visitor().VisitNode(target)
		visitedThisArg := tx.Visitor().VisitNode(thisArg)
		visitedArgs := tx.Visitor().VisitNodes(node.Arguments)
		allArgs := make([]*ast.Node, 0, 1+len(visitedArgs.Nodes))
		allArgs = append(allArgs, visitedThisArg)
		allArgs = append(allArgs, visitedArgs.Nodes...)
		if node.Flags&ast.NodeFlagsOptionalChain != 0 {
			return tx.Factory().UpdateCallExpression(
				node,
				tx.Factory().NewPropertyAccessExpression(visitedTarget, node.QuestionDotToken, tx.Factory().NewIdentifier("call"), ast.NodeFlagsOptionalChain),
				nil, /*questionDotToken*/
				nil, /*typeArguments*/
				tx.Factory().NewNodeList(allArgs),
				node.Flags,
			)
		}
		return tx.Factory().UpdateCallExpression(
			node,
			tx.Factory().NewPropertyAccessExpression(visitedTarget, nil, tx.Factory().NewIdentifier("call"), ast.NodeFlagsNone),
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Factory().NewNodeList(allArgs),
			node.Flags,
		)
	}

	if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
		ast.IsSuperProperty(node.Expression) &&
		isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil &&
		tx.lexicalEnvironment.data.classConstructor != nil {
		// super.x()
		// super[x]()

		// converts `super.f(...)` into `Reflect.get(_baseTemp, "f", _classTemp).call(_classTemp, ...)`
		invocation := tx.Factory().NewFunctionCallCall(
			tx.Visitor().VisitNode(node.Expression),
			tx.lexicalEnvironment.data.classConstructor,
			tx.Visitor().VisitNodes(node.Arguments).Nodes,
		)
		tx.EmitContext().SetOriginal(invocation, node.AsNode())
		invocation.Loc = node.Loc
		return invocation
	}

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

func (tx *classFieldsTransformer) visitTaggedTemplateExpression(node *ast.TaggedTemplateExpression) *ast.Node {
	if ast.IsPropertyAccessExpression(node.Tag) && ast.IsPrivateIdentifier(node.Tag.AsPropertyAccessExpression().Name()) &&
		tx.accessPrivateIdentifier(node.Tag.AsPropertyAccessExpression().Name()) != nil {
		// Bind the `this` correctly for tagged template literals when the tag is a private identifier property access.
		thisArg, target := tx.createCallBinding(node.Tag)
		bindExpr := tx.Factory().NewCallExpression(
			tx.Factory().NewPropertyAccessExpression(tx.Visitor().VisitNode(target), nil, tx.Factory().NewIdentifier("bind"), ast.NodeFlagsNone),
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Factory().NewNodeList([]*ast.Node{tx.Visitor().VisitNode(thisArg)}),
			ast.NodeFlagsNone,
		)
		return tx.Factory().UpdateTaggedTemplateExpression(
			node,
			bindExpr,
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Visitor().VisitNode(node.Template),
			node.Flags,
		)
	}

	if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
		ast.IsSuperProperty(node.Tag) &&
		isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil &&
		tx.lexicalEnvironment.data.classConstructor != nil {
		// converts `` super.f`x` `` into `` Reflect.get(_baseTemp, "f", _classTemp).bind(_classTemp)`x` ``
		invocation := tx.Factory().NewFunctionBindCall(
			tx.Visitor().VisitNode(node.Tag),
			tx.lexicalEnvironment.data.classConstructor,
			nil,
		)
		tx.EmitContext().SetOriginal(invocation, node.AsNode())
		invocation.Loc = node.Loc
		return tx.Factory().UpdateTaggedTemplateExpression(
			node,
			invocation,
			nil, /*questionDotToken*/
			nil, /*typeArguments*/
			tx.Visitor().VisitNode(node.Template),
			node.Flags,
		)
	}

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

func (tx *classFieldsTransformer) transformClassStaticBlockDeclaration(node *ast.Node) *ast.Expression {
	if tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		if isClassThisAssignmentBlock(tx.EmitContext(), node) {
			result := tx.Visitor().VisitNode(node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Nodes[0].Expression())
			// If the generated `_classThis` assignment is a noop (i.e., `_classThis = _classThis`), we can
			// eliminate the expression
			if ast.IsAssignmentExpression(result, true /*excludeCompoundAssignment*/) {
				binary := result.AsBinaryExpression()
				if binary.Left == binary.Right {
					return nil
				}
			}
			return result
		}

		if isClassNamedEvaluationHelperBlock(tx.EmitContext(), node) {
			return tx.Visitor().VisitNode(node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Nodes[0].Expression())
		}

		tx.EmitContext().StartVariableEnvironment()
		statements := tx.setCurrentClassElementAndVisitStatements(node, node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Nodes)
		statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements)

		iife := tx.Factory().NewImmediatelyInvokedArrowFunction(statements)
		arrowFunction := ast.SkipParentheses(iife.Expression())
		tx.EmitContext().SetOriginal(arrowFunction, node)
		tx.EmitContext().AddEmitFlags(arrowFunction, printer.EFNoLexicalArguments)
		// Preserve the statement list source range so the printer can emit detached comments
		// (e.g., `// do` inside an otherwise empty static block)
		arrowFunction.AsArrowFunction().Body.AsBlock().Statements.Loc = node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Loc
		tx.EmitContext().SetOriginal(iife, node)
		tx.EmitContext().AssignSourceMapRange(iife, node)
		tx.EmitContext().AddEmitFlags(arrowFunction, printer.EFNoLexicalThis)
		return iife
	}
	return nil
}

func (tx *classFieldsTransformer) setCurrentClassElementAndVisitStatements(classElement *ast.Node, statements []*ast.Statement) []*ast.Statement {
	savedCurrentClassElement := tx.currentClassElement
	tx.currentClassElement = classElement
	result, _ := tx.Visitor().VisitSlice(statements)
	tx.currentClassElement = savedCurrentClassElement
	return result
}

func (tx *classFieldsTransformer) isAnonymousClassNeedingAssignedNameWorker(node *anonymousFunctionDefinition) bool {
	if ast.IsClassExpression(node) && node.Name() == nil {
		staticPropertiesOrClassStaticBlocks := tx.getStaticPropertiesAndClassStaticBlock(node)
		if core.Some(staticPropertiesOrClassStaticBlocks, func(n *ast.Node) bool {
			return isClassNamedEvaluationHelperBlock(tx.EmitContext(), n)
		}) {
			return false
		}
		hasTransformableStatics := (tx.shouldTransformPrivateElementsOrClassStaticBlocks ||
			tx.nodeHasTransformPrivateStaticElementsFlag(node)) &&
			core.Some(staticPropertiesOrClassStaticBlocks, func(n *ast.Node) bool {
				return ast.IsClassStaticBlockDeclaration(n) ||
					ast.IsPrivateIdentifierClassElementDeclaration(n) ||
					tx.shouldTransformInitializers && ast.IsInitializedProperty(n)
			})
		return hasTransformableStatics
	}
	return false
}

func (tx *classFieldsTransformer) visitBinaryExpression(node *ast.BinaryExpression, discarded bool) *ast.Node {
	if ast.IsDestructuringAssignment(node.AsNode()) {
		// ({ x: obj.#x } = ...)
		// ({ x: super.x } = ...)
		// ({ x: super[x] } = ...)
		savedPendingExpressions := tx.pendingExpressions
		tx.pendingExpressions = nil
		updated := tx.Factory().UpdateBinaryExpression(
			node,
			nil,
			tx.assignmentTargetVisitor.VisitNode(node.Left),
			nil,
			node.OperatorToken,
			tx.Visitor().VisitNode(node.Right),
		)
		var result *ast.Expression
		if len(tx.pendingExpressions) > 0 {
			exprs := append(tx.pendingExpressions, updated)
			result = tx.Factory().InlineExpressions(exprs)
		} else {
			result = updated
		}
		tx.pendingExpressions = savedPendingExpressions
		return result
	}

	if ast.IsAssignmentExpression(node.AsNode(), false /*excludeCompound*/) {
		// 13.15.2 RS: Evaluation
		//   AssignmentExpression : LeftHandSideExpression `=` AssignmentExpression
		//     1. If |LeftHandSideExpression| is neither an |ObjectLiteral| nor an |ArrayLiteral|, then
		//        a. Let _lref_ be ? Evaluation of |LeftHandSideExpression|.
		//        b. If IsAnonymousFunctionDefinition(|AssignmentExpression|) and IsIdentifierRef of |LeftHandSideExpression| are both *true*, then
		//           i. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]].
		//     ...
		//
		//   AssignmentExpression : LeftHandSideExpression `&&=` AssignmentExpression
		//     ...
		//     5. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then
		//        a. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]].
		//     ...
		//
		//   AssignmentExpression : LeftHandSideExpression `||=` AssignmentExpression
		//     ...
		//     5. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then
		//        a. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]].
		//     ...
		//
		//   AssignmentExpression : LeftHandSideExpression `??=` AssignmentExpression
		//     ...
		//     4. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then
		//        a. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]].
		//     ...

		if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
			node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsBinaryExpression()
			debug.Assert(node.AsNode() != nil && ast.IsAssignmentExpression(node.AsNode(), false))
		}

		left := ast.SkipOuterExpressions(node.Left, ast.OEKPartiallyEmittedExpressions|ast.OEKParentheses)
		if ast.IsPropertyAccessExpression(left) && ast.IsPrivateIdentifier(left.Name()) {
			// obj.#x = ...
			info := tx.accessPrivateIdentifier(left.Name())
			if info != nil {
				result := tx.createPrivateIdentifierAssignment(info, left.Expression(), node.Right, node.OperatorToken.Kind)
				tx.EmitContext().SetOriginal(result, node.AsNode())
				result.Loc = node.Loc
				return result
			}
		} else if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil &&
			ast.IsSuperProperty(node.Left) &&
			isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) &&
			tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
			// super.x = ...
			// super[x] = ...
			// super.x += ...
			// super.x -= ...
			data := tx.lexicalEnvironment.data
			if data.facts&classFactsClassWasDecorated != 0 {
				return tx.Factory().UpdateBinaryExpression(
					node,
					nil,
					tx.visitInvalidSuperProperty(node.Left),
					nil,
					node.OperatorToken,
					tx.Visitor().VisitNode(node.Right),
				)
			}
			if data.classConstructor != nil && data.superClassReference != nil {
				var setterName *ast.Expression
				if ast.IsElementAccessExpression(node.Left) {
					setterName = tx.Visitor().VisitNode(node.Left.AsElementAccessExpression().ArgumentExpression)
				} else if ast.IsPropertyAccessExpression(node.Left) && ast.IsIdentifier(node.Left.AsPropertyAccessExpression().Name()) {
					setterName = tx.Factory().NewStringLiteralFromNode(node.Left.AsPropertyAccessExpression().Name())
				}
				if setterName != nil {
					// converts `super.x = 1` into `(Reflect.set(_baseTemp, "x", _a = 1, _classTemp), _a)`
					// converts `super[f()] = 1` into `(Reflect.set(_baseTemp, f(), _a = 1, _classTemp), _a)`
					// converts `super.x += 1` into `(Reflect.set(_baseTemp, "x", _a = Reflect.get(_baseTemp, "x", _classtemp) + 1, _classTemp), _a)`
					// converts `super[f()] += 1` into `(Reflect.set(_baseTemp, _a = f(), _b = Reflect.get(_baseTemp, _a, _classtemp) + 1, _classTemp), _b)`

					expression := tx.Visitor().VisitNode(node.Right)
					if ast.IsCompoundAssignment(node.OperatorToken.Kind) {
						getterName := setterName
						if !transformers.IsSimpleInlineableExpression(setterName) {
							getterName = tx.Factory().NewTempVariable()
							tx.EmitContext().AddVariableDeclaration(getterName)
							setterName = tx.Factory().NewAssignmentExpression(getterName, setterName)
						}
						superPropertyGet := tx.Factory().NewReflectGetCall(
							data.superClassReference,
							getterName,
							data.classConstructor,
						)
						tx.EmitContext().SetOriginal(superPropertyGet, node.Left)
						superPropertyGet.Loc = node.Left.Loc
						expression = tx.Factory().NewBinaryExpression(
							nil,
							superPropertyGet,
							nil,
							tx.Factory().NewToken(transformers.GetNonAssignmentOperatorForCompoundAssignment(node.OperatorToken.Kind)),
							expression,
						)
						expression.Loc = node.Loc
					}

					var temp *ast.IdentifierNode
					if !discarded {
						temp = tx.Factory().NewTempVariable()
						tx.EmitContext().AddVariableDeclaration(temp)
					}
					if temp != nil {
						expression = tx.Factory().NewAssignmentExpression(temp, expression)
						expression.Loc = node.Loc
					}

					expression = tx.Factory().NewReflectSetCall(
						data.superClassReference,
						setterName,
						expression,
						data.classConstructor,
					)
					tx.EmitContext().SetOriginal(expression, node.AsNode())
					expression.Loc = node.Loc

					if temp != nil {
						expression = tx.Factory().NewCommaExpression(expression, temp)
						expression.Loc = node.Loc
					}
					return expression
				}
			}
		}
	}

	if node.OperatorToken.Kind == ast.KindInKeyword && ast.IsPrivateIdentifier(node.Left) {
		// #x in obj
		return tx.transformPrivateIdentifierInInExpression(node)
	}

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

func (tx *classFieldsTransformer) visitParenthesizedExpression(node *ast.ParenthesizedExpression, discarded bool) *ast.Node {
	// 8.4.5 RS: NamedEvaluation
	//   ParenthesizedExpression : `(` Expression `)`
	//     ...
	//     2. Return ? NamedEvaluation of |Expression| with argument _name_.
	if discarded {
		expression := tx.discardedValueVisitor.VisitNode(node.Expression)
		return tx.Factory().UpdateParenthesizedExpression(node, expression)
	}
	expression := tx.Visitor().VisitNode(node.Expression)
	return tx.Factory().UpdateParenthesizedExpression(node, expression)
}

func (tx *classFieldsTransformer) createPrivateIdentifierAssignment(info *privateIdentifierInfo, receiver *ast.Expression, right *ast.Expression, operator ast.Kind) *ast.Expression {
	receiver = tx.Visitor().VisitNode(receiver)
	right = tx.Visitor().VisitNode(right)

	if ast.IsCompoundAssignment(operator) {
		readExpression, initializeExpression := tx.createCopiableReceiverExpr(receiver)
		if initializeExpression != nil {
			receiver = initializeExpression
		} else {
			receiver = readExpression
		}
		right = tx.Factory().NewBinaryExpression(
			nil,
			tx.createPrivateIdentifierAccessHelper(info, readExpression),
			nil,
			tx.Factory().NewToken(transformers.GetNonAssignmentOperatorForCompoundAssignment(operator)),
			right,
		)
	}

	tx.EmitContext().SetCommentRange(receiver, core.NewTextRange(-1, receiver.End()))

	switch info.kind {
	case printer.PrivateIdentifierKindAccessor:
		return tx.Factory().NewClassPrivateFieldSetHelper(
			receiver,
			info.brandCheckIdentifier,
			right,
			info.kind,
			info.setterName,
		)
	case printer.PrivateIdentifierKindMethod:
		return tx.Factory().NewClassPrivateFieldSetHelper(
			receiver,
			info.brandCheckIdentifier,
			right,
			info.kind,
			nil,
		)
	case printer.PrivateIdentifierKindField:
		var f *ast.IdentifierNode
		if info.isStatic {
			f = info.variableName
		}
		return tx.Factory().NewClassPrivateFieldSetHelper(
			receiver,
			info.brandCheckIdentifier,
			right,
			info.kind,
			f,
		)
	case printer.PrivateIdentifierKindUntransformed:
		debug.Fail("Access helpers should not be created for untransformed private elements")
		return nil
	}
	debug.AssertNever(info, "Unknown private element type")
	return nil
}

func (tx *classFieldsTransformer) getPrivateInstanceMethodsAndAccessors(node *ast.Node) []*ast.Node {
	return core.Filter(node.Members(), isNonStaticMethodOrAccessorWithPrivateName)
}

// memberContainsConstructorReference checks if a class member's body contains an identifier
// that resolves to the class declaration. Replaces Strada's resolver.hasNodeCheckFlag(member,
// NodeCheckFlags.ContainsConstructorReference) by walking the AST with the EmitResolver.
// Only checks member bodies (not computed property names), since computed property names
// are evaluated during class definition when the binding is still correct.
func (tx *classFieldsTransformer) memberContainsConstructorReference(member *ast.Node, classDecl *ast.Node) bool {
	classOriginal := tx.EmitContext().MostOriginal(classDecl)
	className := ast.GetNameOfDeclaration(classDecl)
	var check func(n *ast.Node) bool
	check = func(n *ast.Node) bool {
		if ast.IsIdentifier(n) && n != className {
			decl := tx.resolver.GetReferencedValueDeclaration(n)
			if decl == classOriginal {
				return true
			}
		}
		// For PropertyAccessExpression, only check the expression, not the name.
		// The .Name() is a property access name, not a value reference to the class.
		if ast.IsPropertyAccessExpression(n) {
			return check(n.Expression())
		}
		return n.ForEachChild(check)
	}
	// Check only the body/initializer of the member, not the name (which may be
	// a computed property name that shouldn't trigger alias substitution).
	if ast.IsClassStaticBlockDeclaration(member) {
		body := member.AsClassStaticBlockDeclaration().Body
		if body != nil && check(body.AsNode()) {
			return true
		}
	} else {
		body := member.Body()
		if body != nil && check(body) {
			return true
		}
	}
	if ast.IsPropertyDeclaration(member) {
		init := member.Initializer()
		if init != nil && check(init) {
			return true
		}
	}
	return false
}

// classContainsConstructorReference checks if any member of a class contains
// references to the class's own constructor. Replaces Strada's
// resolver.hasNodeCheckFlag(node, NodeCheckFlags.ContainsConstructorReference).
func (tx *classFieldsTransformer) classContainsConstructorReference(node *ast.Node) bool {
	for _, member := range node.Members() {
		if tx.memberContainsConstructorReference(member, node) {
			return true
		}
	}
	return false
}

func (tx *classFieldsTransformer) getClassFacts(node *ast.Node) classFacts {
	facts := classFactsNone

	original := tx.EmitContext().MostOriginal(node)
	if ast.IsClassLike(original) && ast.ClassOrConstructorParameterIsDecorated(tx.legacyDecorators /*useLegacyDecorators*/, original) {
		facts |= classFactsClassWasDecorated
	}

	if tx.shouldTransformPrivateElementsOrClassStaticBlocks &&
		(classHasClassThisAssignment(tx.EmitContext(), node) || classHasExplicitlyAssignedName(tx.EmitContext(), node)) {
		facts |= classFactsNeedsClassConstructorReference
	}

	var containsPublicInstanceFields bool
	var containsInitializedPublicInstanceFields bool
	var containsInstancePrivateElements bool
	var containsInstanceAutoAccessors bool

	for _, member := range node.Members() {
		if ast.IsStatic(member) {
			if member.Name() != nil && (ast.IsPrivateIdentifier(member.Name()) || ast.IsAutoAccessorPropertyDeclaration(member)) &&
				tx.shouldTransformPrivateElementsOrClassStaticBlocks {
				facts |= classFactsNeedsClassConstructorReference
			} else if ast.IsAutoAccessorPropertyDeclaration(member) && tx.shouldTransformAutoAccessors &&
				node.Name() == nil && tx.EmitContext().ClassThis(node) == nil {
				facts |= classFactsNeedsClassConstructorReference
			}
			if ast.IsPropertyDeclaration(member) || ast.IsClassStaticBlockDeclaration(member) {
				if tx.shouldTransformThisInStaticInitializers && member.SubtreeFacts()&ast.SubtreeContainsLexicalThis != 0 {
					facts |= classFactsNeedsSubstitutionForThisInClassStaticField
					if facts&classFactsClassWasDecorated == 0 {
						facts |= classFactsNeedsClassConstructorReference
					}
				}
				if tx.shouldTransformSuperInStaticInitializers && member.SubtreeFacts()&ast.SubtreeContainsLexicalSuper != 0 {
					if facts&classFactsClassWasDecorated == 0 {
						facts |= classFactsNeedsClassConstructorReference | classFactsNeedsClassSuperReference
					}
				}
			}
		} else if !ast.HasAbstractModifier(tx.EmitContext().MostOriginal(member)) {
			if ast.IsAutoAccessorPropertyDeclaration(member) {
				containsInstanceAutoAccessors = true
				containsInstancePrivateElements = containsInstancePrivateElements || ast.IsPrivateIdentifierClassElementDeclaration(member)
			} else if ast.IsPrivateIdentifierClassElementDeclaration(member) {
				containsInstancePrivateElements = true
				if tx.memberContainsConstructorReference(member, node) {
					facts |= classFactsNeedsClassConstructorReference
				}
			} else if ast.IsPropertyDeclaration(member) {
				containsPublicInstanceFields = true
				containsInitializedPublicInstanceFields = containsInitializedPublicInstanceFields || member.Initializer() != nil
			}
		}
	}

	willHoistInitializersToConstructor := (tx.shouldTransformInitializersUsingDefine && containsPublicInstanceFields) ||
		(tx.shouldTransformInitializersUsingSet && containsInitializedPublicInstanceFields) ||
		(tx.shouldTransformPrivateElementsOrClassStaticBlocks && containsInstancePrivateElements) ||
		(tx.shouldTransformPrivateElementsOrClassStaticBlocks && containsInstanceAutoAccessors && tx.shouldTransformAutoAccessors)

	if willHoistInitializersToConstructor {
		facts |= classFactsWillHoistInitializersToConstructor
	}

	return facts
}

func (tx *classFieldsTransformer) visitExpressionWithTypeArgumentsInHeritageClause(node *ast.ExpressionWithTypeArguments) *ast.Node {
	facts := classFactsNone
	if tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
		facts = tx.lexicalEnvironment.data.facts
	}
	if facts&classFactsNeedsClassSuperReference != 0 {
		temp := tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{
			Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes,
		})
		tx.EmitContext().AddVariableDeclaration(temp)
		tx.getClassLexicalEnvironment().superClassReference = temp
		return tx.Factory().UpdateExpressionWithTypeArguments(
			node,
			tx.Factory().NewAssignmentExpression(temp, tx.Visitor().VisitNode(node.Expression)),
			nil, /*typeArguments*/
		)
	}
	return tx.heritageClauseVisitor.VisitEachChild(node.AsNode())
}

func (tx *classFieldsTransformer) visitInNewClassLexicalEnvironment(node *ast.Node, visitor func(tx *classFieldsTransformer, node *ast.Node, facts classFacts) *ast.Node) *ast.Node {
	savedCurrentClassContainer := tx.currentClassContainer
	savedPendingExpressions := tx.pendingExpressions
	savedLexicalEnvironment := tx.lexicalEnvironment
	tx.currentClassContainer = node
	tx.pendingExpressions = nil
	tx.startClassLexicalEnvironment()
	original := tx.EmitContext().MostOriginal(node)
	tx.enclosingClassDeclarations.Add(original)

	if tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.nodeHasTransformPrivateStaticElementsFlag(node) {
		name := ast.GetNameOfDeclaration(node)
		if name != nil && ast.IsIdentifier(name) {
			tx.getPrivateIdentifierEnvironment().data.className = name
		} else if assignedName := tx.EmitContext().AssignedName(node); assignedName != nil {
			if ast.IsStringLiteral(assignedName) {
				// If the assigned name has a textSourceNode that is an identifier, use it directly.
				if textSourceNode := tx.EmitContext().TextSource(assignedName); textSourceNode != nil && ast.IsIdentifier(textSourceNode) {
					tx.getPrivateIdentifierEnvironment().data.className = textSourceNode
				} else if scanner.IsIdentifierText(assignedName.Text(), core.LanguageVariantStandard) {
					// If the text is a valid identifier, create an identifier from it.
					prefixName := tx.Factory().NewIdentifier(assignedName.Text())
					tx.getPrivateIdentifierEnvironment().data.className = prefixName
				}
			}
		}
	}

	if tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		privateInstanceMethodsAndAccessors := tx.getPrivateInstanceMethodsAndAccessors(node)
		if len(privateInstanceMethodsAndAccessors) > 0 {
			tx.getPrivateIdentifierEnvironment().data.weakSetName = tx.createHoistedVariableForClass(
				"instances",
				privateInstanceMethodsAndAccessors[0].Name(),
				"",
			)
		}
	}

	facts := tx.getClassFacts(node)
	if facts != classFactsNone {
		tx.getClassLexicalEnvironment().facts = facts
	}

	result := visitor(tx, node, facts)
	tx.enclosingClassDeclarations.Delete(original)
	tx.endClassLexicalEnvironment()
	debug.Assert(tx.lexicalEnvironment == savedLexicalEnvironment)
	tx.currentClassContainer = savedCurrentClassContainer
	tx.pendingExpressions = savedPendingExpressions
	tx.lexicalEnvironment = savedLexicalEnvironment
	return result
}

func (tx *classFieldsTransformer) visitClassDeclaration(node *ast.ClassDeclaration) *ast.Node {
	return tx.visitInNewClassLexicalEnvironment(node.AsNode(), (*classFieldsTransformer).visitClassDeclarationInNewClassLexicalEnvironment)
}

func (tx *classFieldsTransformer) visitClassDeclarationInNewClassLexicalEnvironment(node *ast.Node, facts classFacts) *ast.Node {
	classDecl := node.AsClassDeclaration()
	// If a class has private static fields, or a static field has a `this` or `super` reference,
	// then we need to allocate a temp variable to hold on to that reference.
	var pendingClassReferenceAssignment *ast.Expression
	if facts&classFactsNeedsClassConstructorReference != 0 {
		// If we aren't transforming class static blocks, then we can't reuse `_classThis` since in
		// `class C { ... static { _classThis = ... } }; _classThis = C` the outer assignment would occur *after*
		// class static blocks evaluate and would overwrite the replacement constructor produced by class
		// decorators.

		// If we are transforming class static blocks, then we can reuse `_classThis` since the assignment
		// will be evaluated *before* the transformed static blocks are evaluated and thus won't overwrite
		// the replacement constructor.

		if tx.shouldTransformPrivateElementsOrClassStaticBlocks && tx.EmitContext().ClassThis(node) != nil {
			classThis := tx.EmitContext().ClassThis(node)
			tx.getClassLexicalEnvironment().classConstructor = classThis
			pendingClassReferenceAssignment = tx.Factory().NewAssignmentExpression(
				classThis,
				tx.Factory().GetLocalName(node),
			)
		} else {
			temp := tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{
				Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes,
			})
			tx.EmitContext().AddVariableDeclaration(temp)
			tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory())
			pendingClassReferenceAssignment = tx.Factory().NewAssignmentExpression(
				temp,
				tx.Factory().GetLocalName(node),
			)
		}
	}

	if tx.EmitContext().ClassThis(node) != nil {
		tx.getClassLexicalEnvironment().classThis = tx.EmitContext().ClassThis(node)
	}

	isClassWithConstructorReference := tx.classContainsConstructorReference(node)

	// Register class alias BEFORE visiting members (Strada registers after, since its
	// onSubstituteNode runs at emit time; we substitute eagerly during transformation).
	alias := tx.getClassLexicalEnvironment().classConstructor
	if isClassWithConstructorReference && alias != nil {
		tx.classAliases[tx.EmitContext().MostOriginal(node)] = alias
	}

	modifiers := tx.modifierVisitor.VisitModifiers(classDecl.Modifiers())
	heritageClauses := tx.heritageClauseVisitor.VisitNodes(classDecl.HeritageClauses)
	members, membersPrologue := tx.transformClassMembers(node)

	var statements []*ast.Node

	if pendingClassReferenceAssignment != nil {
		tx.pendingExpressions = append([]*ast.Expression{pendingClassReferenceAssignment}, tx.pendingExpressions...)
	}

	// Write any pending expressions from elided or moved computed property names
	if len(tx.pendingExpressions) > 0 {
		statements = append(statements, tx.Factory().NewExpressionStatement(tx.Factory().InlineExpressions(tx.pendingExpressions)))
	}

	// A class declaration without a name needs a generated name if it has static
	// initialized properties, since those will be moved outside the class body and
	// need to reference the class by name.
	name := classDecl.Name()

	if tx.shouldTransformInitializersUsingSet || tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		// Emit static property assignment. Because classDeclaration is lexically evaluated,
		// it is safe to emit static property assignment after classDeclaration
		// From ES6 specification:
		//   HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using
		//                               a lexical declaration such as a LexicalDeclaration or a ClassDeclaration.
		staticProperties := tx.getStaticPropertiesAndClassStaticBlock(node)
		if len(staticProperties) > 0 {
			if name == nil {
				name = tx.Factory().NewGeneratedNameForNode(node)
			}
			statements = tx.addPropertyOrClassStaticBlockStatements(statements, staticProperties, tx.Factory().GetLocalName(node))
		}
	}

	isExport := ast.HasSyntacticModifier(node, ast.ModifierFlagsExport)
	isDefault := ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault)

	if len(statements) > 0 && isExport && isDefault {
		modifiers = transformers.ExtractModifiers(tx.EmitContext(), modifiers, ^ast.ModifierFlagsExportDefault)
		exportAssignment := tx.Factory().NewExportAssignment(nil, false /*isExportEquals*/, nil /*typeNode*/, tx.Factory().GetLocalName(node))
		statements = append(statements, exportAssignment)
	}

	updatedClass := tx.Factory().UpdateClassDeclaration(
		classDecl,
		modifiers,
		name,
		nil, /*typeParameters*/
		heritageClauses,
		members,
	)

	result := make([]*ast.Node, 0, 1+len(statements)+1)
	if membersPrologue != nil {
		result = append(result, tx.Factory().NewExpressionStatement(membersPrologue))
	}
	result = append(result, updatedClass)
	result = append(result, statements...)
	return tx.Factory().NewSyntaxList(result)
}

func (tx *classFieldsTransformer) visitClassExpression(node *ast.ClassExpression) *ast.Node {
	return tx.visitInNewClassLexicalEnvironment(node.AsNode(), (*classFieldsTransformer).visitClassExpressionInNewClassLexicalEnvironment)
}

func (tx *classFieldsTransformer) visitClassExpressionInNewClassLexicalEnvironment(node *ast.Node, facts classFacts) *ast.Node {
	classExpr := node.AsClassExpression()

	// If this class expression is a transformation of a decorated class declaration,
	// then we want to output the pendingExpressions as statements, not as inlined
	// expressions with the class statement.
	//
	// In this case, we use pendingStatements to produce the same output as the
	// class declaration transformation. The VariableStatement visitor will insert
	// these statements after the class expression variable statement.
	isDecoratedClassDeclaration := facts&classFactsClassWasDecorated != 0

	if tx.EmitContext().ClassThis(node) != nil {
		tx.getClassLexicalEnvironment().classThis = tx.EmitContext().ClassThis(node)
	}

	var temp *ast.IdentifierNode
	if facts&classFactsNeedsClassConstructorReference != 0 {
		if (tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.nodeHasTransformPrivateStaticElementsFlag(node)) && tx.EmitContext().ClassThis(node) != nil {
			classThis := tx.EmitContext().ClassThis(node)
			tx.getClassLexicalEnvironment().classConstructor = classThis
			temp = classThis
		} else {
			temp = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{
				Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes,
			})
			if tx.classExpressionNeedsBlockScopedTemp() {
				tx.EmitContext().AddLexicalDeclaration(temp)
			} else {
				tx.EmitContext().AddVariableDeclaration(temp)
			}
			tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory())
		}
	}

	staticPropertiesOrClassStaticBlocks := tx.getStaticPropertiesAndClassStaticBlock(node)

	// Pre-compute whether the class expression will need a temp variable wrapper.
	// Strada registers class aliases AFTER transformClassMembers (since onSubstituteNode runs
	// at emit time), but we must predict this before visiting members since we substitute
	// eagerly. This requires pre-detecting willHavePrivatePendingExpressions.
	isClassWithConstructorReference := false
	hasTransformableStatics := false
	deferTempDeclaration := false
	if !isDecoratedClassDeclaration {
		isClassWithConstructorReference = tx.classContainsConstructorReference(node)
		hasTransformableStatics = (tx.shouldTransformPrivateElementsOrClassStaticBlocks ||
			tx.nodeHasTransformPrivateStaticElementsFlag(node)) &&
			core.Some(staticPropertiesOrClassStaticBlocks, func(n *ast.Node) bool {
				return ast.IsClassStaticBlockDeclaration(n) ||
					ast.IsPrivateIdentifierClassElementDeclaration(n) ||
					(tx.shouldTransformInitializers && ast.IsInitializedProperty(n))
			})

		// Private instance elements (fields, methods, accessors) transformed to
		// WeakMap/WeakSet will add initialization expressions to pendingExpressions
		// during transformClassMembers. Pre-detect this so we know whether the class
		// will be wrapped with a temp variable.
		willHavePrivatePendingExpressions := tx.shouldTransformPrivateElementsOrClassStaticBlocks &&
			core.Some(node.Members(), func(n *ast.Node) bool {
				return ast.IsPrivateIdentifierClassElementDeclaration(n) && !ast.HasStaticModifier(n) && tx.shouldTransformClassElementToWeakMap(n)
			})
		willNeedTempWrapper := hasTransformableStatics || willHavePrivatePendingExpressions

		// Register class alias BEFORE visiting members (Strada registers after, since its
		// onSubstituteNode runs at emit time). Only register when the class will be wrapped
		// with a temp, matching Strada's conditional registration.
		if isClassWithConstructorReference && willNeedTempWrapper && tx.getClassLexicalEnvironment().classConstructor == nil {
			// Create temp early so the alias is available during member visiting, even though in the Strada
			// reference the temp would be created later in the pendingExpressions branch.
			temp = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{
				Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes,
			})
			// Defer AddVariableDeclaration to preserve Strada's variable declaration ordering.
			deferTempDeclaration = true
			tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory())
		}
		if alias := tx.getClassLexicalEnvironment().classConstructor; isClassWithConstructorReference && willNeedTempWrapper && alias != nil {
			tx.classAliases[tx.EmitContext().MostOriginal(node)] = alias
		}
	}

	modifiers := tx.modifierVisitor.VisitModifiers(classExpr.Modifiers())
	heritageClauses := tx.heritageClauseVisitor.VisitNodes(classExpr.HeritageClauses)
	members, membersPrologue := tx.transformClassMembers(node)

	if deferTempDeclaration {
		if tx.classExpressionNeedsBlockScopedTemp() {
			tx.EmitContext().AddLexicalDeclaration(temp)
		} else {
			tx.EmitContext().AddVariableDeclaration(temp)
		}
	}

	classExpression := tx.Factory().UpdateClassExpression(
		classExpr,
		modifiers,
		classExpr.Name(),
		nil, /*typeParameters*/
		heritageClauses,
		members,
	)

	var expressions []*ast.Expression
	if membersPrologue != nil {
		expressions = append(expressions, membersPrologue)
	}

	if !isDecoratedClassDeclaration {
		if hasTransformableStatics || len(tx.pendingExpressions) > 0 {
			if temp == nil {
				temp = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{
					Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes,
				})
				if tx.classExpressionNeedsBlockScopedTemp() {
					tx.EmitContext().AddLexicalDeclaration(temp)
				} else {
					tx.EmitContext().AddVariableDeclaration(temp)
				}
				tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory())
				if isClassWithConstructorReference {
					tx.classAliases[tx.EmitContext().MostOriginal(node)] = tx.getClassLexicalEnvironment().classConstructor
				}
			}

			expressions = append(expressions, tx.Factory().NewAssignmentExpression(temp, classExpression))

			// Add any pending expressions leftover from elided or relocated computed property names
			expressions = append(expressions, tx.pendingExpressions...)

			expressions = append(expressions, tx.generateInitializedPropertyExpressionsOrClassStaticBlock(staticPropertiesOrClassStaticBlocks, temp)...)
			expressions = append(expressions, temp.Clone(tx.Factory()))
		} else {
			expressions = append(expressions, classExpression)
		}
	} else {
		// Decorated class declaration path: emit static properties as separate statements
		// via pendingStatements, matching the class declaration output structure.

		// Write any pending expressions from elided or moved computed property names
		if len(tx.pendingExpressions) > 0 {
			for _, expr := range tx.pendingExpressions {
				tx.pendingStatements = append(tx.pendingStatements, tx.Factory().NewExpressionStatement(expr))
			}
		}

		// Emit static properties as statements (via pendingStatements) using the class's
		// internal name as the receiver, matching the class declaration output structure.
		if len(staticPropertiesOrClassStaticBlocks) > 0 {
			classThisOrName := tx.EmitContext().ClassThis(node)
			if classThisOrName == nil {
				classThisOrName = tx.Factory().GetLocalName(node)
			}
			tx.pendingStatements = tx.addPropertyOrClassStaticBlockStatements(tx.pendingStatements, staticPropertiesOrClassStaticBlocks, classThisOrName)
		}

		if temp != nil {
			expressions = append(expressions, tx.Factory().NewAssignmentExpression(temp, classExpression))
		} else if tx.shouldTransformPrivateElementsOrClassStaticBlocks && tx.EmitContext().ClassThis(node) != nil {
			expressions = append(expressions, tx.Factory().NewAssignmentExpression(tx.EmitContext().ClassThis(node), classExpression))
		} else {
			expressions = append(expressions, classExpression)
		}
	}

	if len(expressions) > 1 {
		tx.EmitContext().AddEmitFlags(classExpression, printer.EFIndented)
		for _, expr := range expressions {
			tx.EmitContext().AddEmitFlags(expr, printer.EFStartOnNewLine)
		}
	}
	return tx.Factory().InlineExpressions(expressions)
}

func (tx *classFieldsTransformer) visitClassStaticBlockDeclaration(node *ast.Node) *ast.Node {
	if !tx.shouldTransformPrivateElementsOrClassStaticBlocks {
		return tx.Visitor().VisitEachChild(node)
	}
	// ClassStaticBlockDeclaration for classes are transformed in visitClassDeclaration/visitClassExpression.
	return nil
}

// visitThisExpression replaces Strada's substituteThisExpression / onSubstituteNode.
// Strada substitutes `this` at emit time; we do it eagerly during transformation.
//
// The Strada noSubstitution set (ensureDynamicThisIfNeeded) is not needed because
// transformAutoAccessor() passes the receiver directly rather than emitting `this`.
func (tx *classFieldsTransformer) visitThisExpression(node *ast.Node) *ast.Node {
	if tx.insideComputedPropertyName && tx.shouldTransformThisInStaticInitializers &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
		// Don't replace `this` in computed property names for ES-decorated classes.
		// The esDecorator transformer wraps them in an arrow IIFE where `this` already
		// refers to the correct outer scope.
		if tx.lexicalEnvironment.data.facts&classFactsClassWasDecorated == 0 || tx.legacyDecorators {
			if classThis := tx.tryGetClassThisNoContainer(); classThis != nil {
				return classThis
			}
		}
	}
	if tx.shouldTransformThisInStaticInitializers && tx.currentClassElement != nil &&
		(ast.IsClassStaticBlockDeclaration(tx.currentClassElement) ||
			(ast.IsPropertyDeclaration(tx.currentClassElement) && ast.HasStaticModifier(tx.currentClassElement))) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil {
		if classThis := tx.tryGetClassThisNoContainer(); classThis != nil {
			return classThis
		}
		// When the class was decorated with legacy decorators and no class constructor
		// reference is available, the decorator may replace the constructor, so `this`
		// cannot reliably point to the class. Use `(void 0)` instead.
		if tx.lexicalEnvironment.data.facts&classFactsClassWasDecorated != 0 && tx.legacyDecorators {
			return tx.Factory().NewParenthesizedExpression(tx.Factory().NewVoidZeroExpression())
		}
	}
	return node
}

func (tx *classFieldsTransformer) transformClassMembers(node *ast.Node) (members *ast.NodeList, prologue *ast.Expression) {
	shouldTransformPrivateStaticElementsInClass := tx.EmitContext().EmitFlags(node)&printer.EFTransformPrivateStaticElements != 0

	// Declare private names
	if tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.shouldTransformPrivateStaticElementsInFile {
		for _, member := range node.Members() {
			if ast.IsPrivateIdentifierClassElementDeclaration(member) {
				if tx.shouldTransformClassElementToWeakMap(member) {
					tx.addPrivateIdentifierToEnvironment(member)
				} else {
					env := tx.getPrivateIdentifierEnvironment()
					tx.setPrivateIdentifier(env, member.Name(), &privateIdentifierInfo{
						kind: printer.PrivateIdentifierKindUntransformed,
					})
				}
			}
		}

		if tx.shouldTransformPrivateElementsOrClassStaticBlocks {
			if len(tx.getPrivateInstanceMethodsAndAccessors(node)) > 0 {
				tx.createBrandCheckWeakSetForPrivateMethods()
			}
		}

		if tx.shouldTransformAutoAccessorsInCurrentClass() {
			for _, member := range node.Members() {
				if ast.IsAutoAccessorPropertyDeclaration(member) {
					storageName := tx.Factory().NewGeneratedPrivateNameForNodeEx(member.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"})
					if tx.shouldTransformPrivateElementsOrClassStaticBlocks ||
						shouldTransformPrivateStaticElementsInClass && ast.HasStaticModifier(member) {
						tx.addPrivateIdentifierPropertyDeclarationToEnvironment(member, storageName)
					} else {
						env := tx.getPrivateIdentifierEnvironment()
						// Only register as untransformed if it hasn't already been registered
						// by the first loop (e.g., if esDecorators expanded a private auto-accessor
						// into a backing field with the same generated name).
						if _, ok := tx.getPrivateIdentifier(env, storageName); !ok {
							tx.setPrivateIdentifier(env, storageName, &privateIdentifierInfo{
								kind: printer.PrivateIdentifierKindUntransformed,
							})
						}
					}
				}
			}
		}
	}

	members = tx.classElementVisitor.VisitNodes(node.MemberList())

	// Create a synthetic constructor if necessary
	var syntheticConstructor *ast.Node
	if !core.Some(members.Nodes, ast.IsConstructorDeclaration) {
		syntheticConstructor = tx.transformConstructor(nil, node)
	}

	// If there are pending expressions create a class static block in which to evaluate them, but only if
	// class static blocks are not also being transformed. This block will be injected at the top of the class
	// to ensure that expressions from computed property names are evaluated before any other static
	// initializers.
	var syntheticStaticBlock *ast.Node
	if !tx.shouldTransformPrivateElementsOrClassStaticBlocks && len(tx.pendingExpressions) > 0 {
		statement := tx.Factory().NewExpressionStatement(tx.Factory().InlineExpressions(tx.pendingExpressions))
		if statement.SubtreeFacts()&ast.SubtreeContainsLexicalThisOrSuper != 0 {
			// If there are `this` or `super` references from computed property names, shift the expression
			// into an arrow function to be evaluated in the outer scope so that `this` and `super` are
			// properly captured.
			temp := tx.Factory().NewTempVariable()
			tx.EmitContext().AddVariableDeclaration(temp)
			arrow := tx.Factory().NewArrowFunction(
				nil,                           /*modifiers*/
				nil,                           /*typeParameters*/
				tx.Factory().NewNodeList(nil), /*parameters*/
				nil,                           /*returnType*/
				nil,                           /*fullSignature*/
				tx.Factory().NewToken(ast.KindEqualsGreaterThanToken), /*equalsGreaterThanToken*/
				tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{statement}), false /*multiline*/),
			)
			prologue = tx.Factory().NewAssignmentExpression(temp, arrow)
			statement = tx.Factory().NewExpressionStatement(
				tx.Factory().NewCallExpression(temp, nil /*questionDotToken*/, nil /*typeArguments*/, tx.Factory().NewNodeList(nil), ast.NodeFlagsNone),
			)
		}

		block := tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{statement}), false /*multiline*/)
		syntheticStaticBlock = tx.Factory().NewClassStaticBlockDeclaration(nil /*modifiers*/, block)
		tx.pendingExpressions = nil
	}

	// If we created a synthetic constructor or class static block, add them to the visited members
	if syntheticConstructor != nil || syntheticStaticBlock != nil {
		membersArray := make([]*ast.Node, 0, len(members.Nodes)+2)

		// Find and preserve classThis assignment block and named evaluation helper block at the top
		classThisIdx := slices.IndexFunc(members.Nodes, func(n *ast.Node) bool {
			return isClassThisAssignmentBlock(tx.EmitContext(), n)
		})
		namedEvalIdx := slices.IndexFunc(members.Nodes, func(n *ast.Node) bool {
			return isClassNamedEvaluationHelperBlock(tx.EmitContext(), n)
		})

		if classThisIdx >= 0 {
			membersArray = append(membersArray, members.Nodes[classThisIdx])
		}
		if namedEvalIdx >= 0 {
			membersArray = append(membersArray, members.Nodes[namedEvalIdx])
		}
		if syntheticConstructor != nil {
			membersArray = append(membersArray, syntheticConstructor)
		}
		if syntheticStaticBlock != nil {
			membersArray = append(membersArray, syntheticStaticBlock)
		}

		for i, member := range members.Nodes {
			if i != classThisIdx && i != namedEvalIdx {
				membersArray = append(membersArray, member)
			}
		}
		members = tx.Factory().NewNodeList(membersArray)
		members.Loc = node.MemberList().Loc
	}

	return members, prologue
}

func (tx *classFieldsTransformer) createBrandCheckWeakSetForPrivateMethods() {
	env := tx.getPrivateIdentifierEnvironment()
	weakSetName := env.data.weakSetName
	debug.Assert(weakSetName != nil, "weakSetName should be set in private identifier environment")

	tx.addPendingExpressions(
		tx.Factory().NewAssignmentExpression(
			weakSetName,
			tx.Factory().NewNewExpression(
				tx.Factory().NewIdentifier("WeakSet"),
				nil, /*typeArguments*/
				tx.Factory().NewNodeList(nil),
			),
		),
	)
}

func (tx *classFieldsTransformer) transformConstructor(constructor *ast.ConstructorDeclaration, container *ast.Node) *ast.Node {
	// NOTE: The Strada reference pre-visits the constructor via `visitNode(constructor, visitor)` before
	// checking WillHoistInitializersToConstructor. This is not done here because Go's variable environment
	// (StartVariableEnvironment/EndAndMergeVariableEnvironment) is scoped inside transformConstructorBody.
	// Pre-visiting would hoist variables outside that scope, causing them to appear after field initializers
	// instead of before. Instead, we visit parameters and body separately within the correct scopes.
	if tx.lexicalEnvironment == nil || tx.lexicalEnvironment.data == nil ||
		tx.lexicalEnvironment.data.facts&classFactsWillHoistInitializersToConstructor == 0 {
		if constructor != nil {
			return tx.Visitor().VisitEachChild(constructor.AsNode())
		}
		return nil
	}

	extendsClauseElement := ast.GetClassExtendsHeritageElement(container)
	isDerivedClass := extendsClauseElement != nil && ast.SkipOuterExpressions(extendsClauseElement.Expression(), ast.OEKAll).Kind != ast.KindNullKeyword

	var parameters *ast.NodeList
	if constructor != nil {
		parameters = tx.Visitor().VisitNodes(constructor.Parameters)
	}

	body := tx.transformConstructorBody(container, constructor, isDerivedClass)
	if body == nil {
		if constructor != nil {
			return tx.Visitor().VisitEachChild(constructor.AsNode())
		}
		return nil
	}

	if constructor != nil {
		debug.Assert(parameters != nil)
		return tx.Factory().UpdateConstructorDeclaration(
			constructor,
			nil, /*modifiers*/
			nil, /*typeParameters*/
			parameters,
			nil, /*returnType*/
			nil, /*fullSignature*/
			body,
		)
	}

	if parameters == nil {
		parameters = tx.Factory().NewNodeList(nil)
	}

	result := tx.Factory().NewConstructorDeclaration(
		nil, /*modifiers*/
		nil, /*typeParameters*/
		parameters,
		nil, /*returnType*/
		nil, /*fullSignature*/
		body,
	)
	result.Loc = container.Loc
	return result
}

func (tx *classFieldsTransformer) transformConstructorBodyWorker(
	statementsOut []*ast.Statement,
	statementsIn []*ast.Statement,
	statementOffset int,
	superPath []int,
	superPathDepth int,
	initializerStatements []*ast.Statement,
	constructor *ast.ConstructorDeclaration,
) []*ast.Statement {
	superStatementIndex := superPath[superPathDepth]
	superStatement := statementsIn[superStatementIndex]

	// Visit statements before super
	visited, _ := tx.Visitor().VisitSlice(statementsIn[statementOffset:superStatementIndex])
	statementsOut = append(statementsOut, visited...)
	statementOffset = superStatementIndex + 1

	if ast.IsTryStatement(superStatement) {
		tryBlock := superStatement.AsTryStatement().TryBlock.AsBlock()
		tryBlockStatements := tx.transformConstructorBodyWorker(
			nil,
			tryBlock.Statements.Nodes,
			0, /*statementOffset*/
			superPath,
			superPathDepth+1,
			initializerStatements,
			constructor,
		)
		tryStatementList := tx.Factory().NewNodeList(tryBlockStatements)
		tryStatementList.Loc = tryBlock.Statements.Loc

		catchClause := tx.Visitor().VisitNode(superStatement.AsTryStatement().CatchClause)
		finallyBlock := tx.Visitor().VisitNode(superStatement.AsTryStatement().FinallyBlock)

		updated := tx.Factory().UpdateTryStatement(
			superStatement.AsTryStatement(),
			tx.Factory().UpdateBlock(tryBlock, tryStatementList, tryBlock.MultiLine),
			catchClause,
			finallyBlock,
		)
		statementsOut = append(statementsOut, updated)
	} else {
		visited, _ := tx.Visitor().VisitSlice(statementsIn[superStatementIndex : superStatementIndex+1])
		statementsOut = append(statementsOut, visited...)

		// Add the property initializers. Transforms this:
		//
		//  public x = 1;
		//
		// Into this:
		//
		//  constructor() {
		//      this.x = 1;
		//  }
		//
		// If we do useDefineForClassFields, they'll be converted elsewhere.
		// We instead *remove* them from the transformed output at this stage.

		// parameter-property assignments should occur immediately after the prologue and `super()`,
		// so only count the statements that immediately follow.
		for statementOffset < len(statementsIn) {
			stmt := statementsIn[statementOffset]
			orig := tx.EmitContext().MostOriginal(stmt)
			if ast.IsParameterPropertyDeclaration(orig, constructor.AsNode()) {
				statementOffset++
			} else {
				break
			}
		}

		statementsOut = append(statementsOut, initializerStatements...)
	}

	// Visit remaining statements
	visited2, _ := tx.Visitor().VisitSlice(statementsIn[statementOffset:])
	statementsOut = append(statementsOut, visited2...)
	return statementsOut
}

func (tx *classFieldsTransformer) transformConstructorBody(container *ast.Node, constructor *ast.ConstructorDeclaration, isDerivedClass bool) *ast.Node {
	instanceProperties := tx.getProperties(container, false /*requireInitializer*/, false /*isStatic*/)
	properties := instanceProperties
	if !tx.compilerOptions.GetUseDefineForClassFields() {
		properties = core.Filter(properties, func(prop *ast.Node) bool {
			return prop.Initializer() != nil || ast.IsPrivateIdentifier(prop.Name()) || ast.HasAccessorModifier(prop)
		})
	}

	privateMethodsAndAccessors := tx.getPrivateInstanceMethodsAndAccessors(container)
	needsConstructorBody := len(properties) > 0 || len(privateMethodsAndAccessors) > 0

	// Only generate synthetic constructor when there are property initializers to move.
	if constructor == nil && !needsConstructorBody {
		return tx.EmitContext().VisitFunctionBody(nil, tx.Visitor())
	}

	tx.EmitContext().StartVariableEnvironment()

	needsSyntheticConstructor := constructor == nil && isDerivedClass
	var statements []*ast.Statement

	// Add the property initializers. Transforms this:
	//
	//  public x = 1;
	//
	// Into this:
	//
	//  constructor() {
	//      this.x = 1;
	//  }
	//
	var initializerStatements []*ast.Statement
	receiver := tx.Factory().NewThisExpression()

	// private methods can be called in property initializers, they should execute first
	initializerStatements = tx.addInstanceMethodStatements(initializerStatements, privateMethodsAndAccessors, receiver)

	if constructor != nil {
		parameterProperties := core.Filter(instanceProperties, func(prop *ast.Node) bool {
			return ast.IsParameterPropertyDeclaration(tx.EmitContext().MostOriginal(prop), constructor.AsNode())
		})
		nonParameterProperties := core.Filter(properties, func(prop *ast.Node) bool {
			return !ast.IsParameterPropertyDeclaration(tx.EmitContext().MostOriginal(prop), constructor.AsNode())
		})
		initializerStatements = tx.addPropertyOrClassStaticBlockStatements(initializerStatements, parameterProperties, receiver)
		initializerStatements = tx.addPropertyOrClassStaticBlockStatements(initializerStatements, nonParameterProperties, receiver)
	} else {
		initializerStatements = tx.addPropertyOrClassStaticBlockStatements(initializerStatements, properties, receiver)
	}

	if constructor != nil && constructor.Body != nil {
		body := constructor.Body.AsBlock()

		// Copy prologue
		for _, stmt := range body.Statements.Nodes {
			if ast.IsPrologueDirective(stmt) {
				statements = append(statements, stmt)
			} else {
				break
			}
		}
		statementOffset := len(statements)

		superPath := transformers.FindSuperStatementIndexPath(body.Statements.Nodes, statementOffset)
		if len(superPath) > 0 {
			statements = tx.transformConstructorBodyWorker(statements, body.Statements.Nodes, statementOffset, superPath, 0, initializerStatements, constructor)
		} else {
			// parameter-property assignments should occur immediately after the prologue and `super()`,
			// so only count the statements that immediately follow.
			for statementOffset < len(body.Statements.Nodes) {
				stmt := body.Statements.Nodes[statementOffset]
				orig := tx.EmitContext().MostOriginal(stmt)
				if ast.IsParameterPropertyDeclaration(orig, constructor.AsNode()) {
					statementOffset++
				} else {
					break
				}
			}
			statements = append(statements, initializerStatements...)
			visited, _ := tx.Visitor().VisitSlice(body.Statements.Nodes[statementOffset:])
			statements = append(statements, visited...)
		}
	} else {
		if needsSyntheticConstructor {
			// Add a synthetic `super` call:
			//
			//  super(...arguments);
			//
			superCall := tx.Factory().NewExpressionStatement(
				tx.Factory().NewCallExpression(
					tx.Factory().NewKeywordExpression(ast.KindSuperKeyword),
					nil, /*typeArguments*/
					nil, /*questionDotToken*/
					tx.Factory().NewNodeList([]*ast.Node{
						tx.Factory().NewSpreadElement(tx.Factory().NewIdentifier("arguments")),
					}),
					ast.NodeFlagsNone,
				),
			)
			statements = append(statements, superCall)
		}
		statements = append(statements, initializerStatements...)
	}

	statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements)

	if len(statements) == 0 && constructor == nil {
		return nil
	}

	var multiLine bool
	if constructor != nil && constructor.Body != nil &&
		len(constructor.Body.AsBlock().Statements.Nodes) >= len(statements) {
		multiLine = constructor.Body.AsBlock().MultiLine
	} else {
		multiLine = len(statements) > 0
	}

	statementList := tx.Factory().NewNodeList(statements)
	if constructor != nil && constructor.Body != nil {
		statementList.Loc = constructor.Body.AsBlock().Statements.Loc
	} else {
		statementList.Loc = core.NewTextRange(container.MemberList().Loc.Pos(), container.MemberList().Loc.End())
	}

	block := tx.Factory().NewBlock(statementList, multiLine)
	if constructor != nil && constructor.Body != nil {
		block.Loc = constructor.Body.Loc
	}
	return block
}

// addPropertyOrClassStaticBlockStatements generates assignment statements for property initializers.
func (tx *classFieldsTransformer) addPropertyOrClassStaticBlockStatements(statements []*ast.Node, properties []*ast.Node, receiver *ast.Expression) []*ast.Node {
	for _, property := range properties {
		if ast.IsStatic(property) && !tx.shouldTransformPrivateElementsOrClassStaticBlocks {
			continue
		}
		statement := tx.transformPropertyOrClassStaticBlock(property, receiver)
		if statement != nil {
			statements = append(statements, statement)
		}
	}
	return statements
}

func (tx *classFieldsTransformer) transformPropertyOrClassStaticBlock(property *ast.Node, receiver *ast.Expression) *ast.Node {
	var expression *ast.Expression
	if ast.IsClassStaticBlockDeclaration(property) {
		expression = tx.setCurrentClassElementAnd(property, (*classFieldsTransformer).transformClassStaticBlockDeclaration, property)
	} else {
		expression = tx.transformProperty(property.AsPropertyDeclaration(), receiver)
	}
	if expression == nil {
		return nil
	}

	statement := tx.Factory().NewExpressionStatement(expression)
	tx.EmitContext().SetOriginal(statement, property)
	tx.EmitContext().AddEmitFlags(statement, tx.EmitContext().EmitFlags(property)&printer.EFNoComments)
	tx.EmitContext().SetCommentRange(statement, property.Loc)

	propertyOriginalNode := tx.EmitContext().MostOriginal(property)
	if ast.IsParameterDeclaration(propertyOriginalNode) {
		tx.EmitContext().SetSourceMapRange(statement, propertyOriginalNode.Loc)
		tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments)
	} else {
		tx.EmitContext().SetSourceMapRange(statement, transformers.MoveRangePastModifiers(property))
	}

	// `setOriginalNode` *copies* the `emitNode` from `property`, so now both
	// `statement` and `expression` have a copy of the synthesized comments.
	// Drop the comments from expression to avoid printing them twice.
	tx.EmitContext().SetSyntheticLeadingComments(expression, nil)
	tx.EmitContext().SetSyntheticTrailingComments(expression, nil)

	// If the property was originally an auto-accessor, don't emit comments here since they will be attached to
	// the synthesized getter.
	if ast.HasAccessorModifier(propertyOriginalNode) {
		tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments)
	}

	return statement
}

// generateInitializedPropertyExpressionsOrClassStaticBlock generates assignment expressions for property initializers.
func (tx *classFieldsTransformer) generateInitializedPropertyExpressionsOrClassStaticBlock(
	propertiesOrClassStaticBlocks []*ast.Node,
	receiver *ast.Expression,
) []*ast.Expression {
	var expressions []*ast.Expression
	for _, property := range propertiesOrClassStaticBlocks {
		var expression *ast.Expression
		if ast.IsClassStaticBlockDeclaration(property) {
			expression = tx.setCurrentClassElementAnd(property, (*classFieldsTransformer).transformClassStaticBlockDeclaration, property)
		} else {
			expression = tx.transformProperty(property.AsPropertyDeclaration(), receiver)
		}
		if expression == nil {
			continue
		}
		tx.EmitContext().SetOriginalEx(expression, property, true /*allowOverwrite*/)
		tx.EmitContext().AssignCommentAndSourceMapRanges(expression, property)
		expressions = append(expressions, expression)
	}
	return expressions
}

// transformProperty transforms a property initializer into an assignment expression.
func (tx *classFieldsTransformer) transformProperty(property *ast.PropertyDeclaration, receiver *ast.Expression) *ast.Expression {
	savedCurrentClassElement := tx.currentClassElement
	transformed := tx.transformPropertyWorker(property, receiver)
	if transformed != nil && ast.HasStaticModifier(property.AsNode()) {
		tx.EmitContext().AddEmitFlags(transformed, printer.EFNoLexicalThis)
	}
	if transformed != nil && ast.HasStaticModifier(property.AsNode()) &&
		tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil && tx.lexicalEnvironment.data.facts != 0 {
		// capture the lexical environment for the member
		tx.EmitContext().SetOriginal(transformed, property.AsNode())
		tx.EmitContext().SetSourceMapRange(transformed, tx.EmitContext().SourceMapRange(property.Name()))
	}
	tx.currentClassElement = savedCurrentClassElement
	return transformed
}

func (tx *classFieldsTransformer) transformPropertyWorker(property *ast.PropertyDeclaration, receiver *ast.Expression) *ast.Expression {
	// We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name)
	emitAssignment := !tx.compilerOptions.GetUseDefineForClassFields()

	if isNamedEvaluationAnd(tx.EmitContext(), property.AsNode(), tx.isAnonymousClassNeedingAssignedName) {
		property = transformNamedEvaluation(tx.EmitContext(), property.AsNode(), false, "").AsPropertyDeclaration()
	}

	propertyName := property.Name()
	if ast.HasAccessorModifier(property.AsNode()) {
		propertyName = tx.Factory().NewGeneratedPrivateNameForNodeEx(property.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"})
	} else if ast.IsComputedPropertyName(propertyName) && !transformers.IsSimpleInlineableExpression(propertyName.Expression()) {
		propertyName = tx.Factory().UpdateComputedPropertyName(
			propertyName.AsComputedPropertyName(),
			tx.Factory().NewGeneratedNameForNode(propertyName),
		)
	}

	if ast.HasStaticModifier(property.AsNode()) {
		tx.currentClassElement = property.AsNode()
	}

	if ast.IsPrivateIdentifier(propertyName) && tx.shouldTransformClassElementToWeakMap(property.AsNode()) {
		info := tx.accessPrivateIdentifier(propertyName)
		if info != nil {
			if info.kind == printer.PrivateIdentifierKindField {
				if !info.isStatic {
					return createPrivateInstanceFieldInitializer(
						tx.Factory(),
						receiver,
						tx.Visitor().VisitNode(property.Initializer),
						info.brandCheckIdentifier,
					)
				}
				return createPrivateStaticFieldInitializer(
					tx.Factory(),
					info.variableName,
					tx.Visitor().VisitNode(property.Initializer),
				)
			}
			return nil
		} else {
			debug.Fail("Undeclared private name for property declaration.")
		}
	}

	if (ast.IsPrivateIdentifier(propertyName) || ast.HasStaticModifier(property.AsNode())) && property.Initializer == nil {
		return nil
	}

	// TODO: can we get rid of this original checking and better coordinate with runtimesyntax?
	if ast.HasAbstractModifier(tx.EmitContext().MostOriginal(property.AsNode())) {
		return nil
	}

	initializer := tx.Visitor().VisitNode(property.Initializer)
	propertyOriginalNode := tx.EmitContext().MostOriginal(property.AsNode())
	if ast.IsParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.Parent) && ast.IsIdentifier(propertyName) { //nolint:customlint // MostOriginal returns parse-tree nodes, and this parent relationship is intentional.
		// A parameter-property declaration always overrides the initializer. The only time a parameter-property
		// declaration *should* have an initializer is when decorators have added initializers that need to run before
		// any other initializer
		localName := propertyName.Clone(tx.Factory())
		if initializer != nil {
			// unwrap `(__runInitializers(this, _instanceExtraInitializers), void 0)`
			if ast.IsParenthesizedExpression(initializer) &&
				ast.IsCommaExpression(initializer.Expression()) &&
				tx.EmitContext().IsCallToHelper(initializer.Expression().AsBinaryExpression().Left, "__runInitializers") &&
				ast.IsVoidExpression(initializer.Expression().AsBinaryExpression().Right) &&
				ast.IsNumericLiteral(initializer.Expression().AsBinaryExpression().Right.Expression()) {
				initializer = initializer.Expression().AsBinaryExpression().Left
			}
			initializer = tx.Factory().InlineExpressions([]*ast.Expression{initializer, localName})
		} else {
			initializer = localName
		}
		tx.EmitContext().AddEmitFlags(propertyName, printer.EFNoComments|printer.EFNoSourceMap)
		tx.EmitContext().SetSourceMapRange(localName, propertyOriginalNode.Name().Loc)
		tx.EmitContext().AddEmitFlags(localName, printer.EFNoComments)
	} else if initializer == nil {
		initializer = tx.Factory().NewVoidZeroExpression()
	}

	if emitAssignment || ast.IsPrivateIdentifier(propertyName) {
		memberAccess := createMemberAccessForPropertyName(tx.Factory(), tx.EmitContext(), receiver, propertyName, propertyName)
		tx.EmitContext().AddEmitFlags(memberAccess, printer.EFNoLeadingComments)
		return tx.Factory().NewAssignmentExpression(memberAccess, initializer)
	}

	// useDefineForClassFields: Object.defineProperty
	var name *ast.Expression
	if ast.IsComputedPropertyName(propertyName) {
		name = propertyName.Expression()
	} else if ast.IsIdentifier(propertyName) {
		name = tx.Factory().NewStringLiteral(propertyName.Text(), ast.TokenFlagsNone)
	} else {
		name = propertyName
	}
	descriptor := tx.Factory().NewObjectLiteralExpression(tx.Factory().NewNodeList([]*ast.Node{
		tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("enumerable"), nil, nil, tx.Factory().NewTrueExpression()),
		tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("configurable"), nil, nil, tx.Factory().NewTrueExpression()),
		tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("writable"), nil, nil, tx.Factory().NewTrueExpression()),
		tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("value"), nil, nil, initializer),
	}), true)
	return tx.Factory().NewObjectDefinePropertyCall(receiver, name, descriptor)
}

// addInstanceMethodStatements generates brand-check initializer for private methods.
func (tx *classFieldsTransformer) addInstanceMethodStatements(statements []*ast.Statement, methods []*ast.Node, receiver *ast.Expression) []*ast.Statement {
	if !tx.shouldTransformPrivateElementsOrClassStaticBlocks || len(methods) == 0 {
		return statements
	}

	env := tx.getPrivateIdentifierEnvironment()
	weakSetName := env.data.weakSetName
	debug.Assert(weakSetName != nil, "weakSetName should be set in private identifier environment")

	return append(
		statements,
		tx.Factory().NewExpressionStatement(
			createPrivateInstanceMethodInitializer(tx.Factory(), receiver, weakSetName),
		),
	)
}

func (tx *classFieldsTransformer) visitInvalidSuperProperty(node *ast.Node) *ast.Node {
	if ast.IsPropertyAccessExpression(node) {
		return tx.Factory().UpdatePropertyAccessExpression(
			node.AsPropertyAccessExpression(),
			tx.Factory().NewVoidZeroExpression(),
			nil,
			node.Name(),
			node.Flags,
		)
	}
	return tx.Factory().UpdateElementAccessExpression(
		node.AsElementAccessExpression(),
		tx.Factory().NewVoidZeroExpression(),
		nil,
		tx.Visitor().VisitNode(node.AsElementAccessExpression().ArgumentExpression),
		node.Flags,
	)
}

// getPropertyNameExpressionIfNeeded transforms a computed property name, then either returns an expression
// which caches the value of the result or the expression itself if the value is either unused or safe to
// inline into multiple locations.
// shouldHoist indicates whether the expression needs to be reused (i.e., for an initializer or a decorator).
func (tx *classFieldsTransformer) getPropertyNameExpressionIfNeeded(name *ast.PropertyName, shouldHoist bool) *ast.Expression {
	if !ast.IsComputedPropertyName(name) {
		return nil
	}
	cacheAssignment := findComputedPropertyNameCacheAssignment(tx.EmitContext(), name)
	// Switch to outer lex env for computed property name expressions, matching
	// Strada reference's onEmitNode behavior for ComputedPropertyName.
	savedLexicalEnvironment := tx.lexicalEnvironment
	savedInsideComputedPropertyName := tx.insideComputedPropertyName
	tx.insideComputedPropertyName = true
	if tx.lexicalEnvironment != nil && tx.lexicalEnvironment.previous != nil {
		tx.lexicalEnvironment = tx.lexicalEnvironment.previous
	}
	expression := tx.Visitor().VisitNode(name.Expression())
	tx.lexicalEnvironment = savedLexicalEnvironment
	tx.insideComputedPropertyName = savedInsideComputedPropertyName
	innerExpression := ast.SkipPartiallyEmittedExpressions(expression)
	inlinable := transformers.IsSimpleInlineableExpression(innerExpression)
	alreadyTransformed := cacheAssignment != nil || (ast.IsAssignmentExpression(innerExpression, true /*excludeCompoundAssignment*/) && ast.IsIdentifier(innerExpression.AsBinaryExpression().Left) && transformers.IsGeneratedIdentifier(tx.EmitContext(), innerExpression.AsBinaryExpression().Left))
	if !alreadyTransformed && !inlinable && shouldHoist {
		generatedName := tx.Factory().NewGeneratedNameForNode(name)
		if tx.requiresBlockScopedVar() {
			tx.EmitContext().AddLexicalDeclaration(generatedName)
		} else {
			tx.EmitContext().AddVariableDeclaration(generatedName)
		}
		return tx.Factory().NewAssignmentExpression(generatedName, expression)
	}
	if inlinable || ast.IsIdentifier(innerExpression) {
		return nil
	}
	return expression
}

func (tx *classFieldsTransformer) startClassLexicalEnvironment() {
	tx.lexicalEnvironment = &classLexicalEnv{previous: tx.lexicalEnvironment}
}

func (tx *classFieldsTransformer) endClassLexicalEnvironment() {
	tx.lexicalEnvironment = tx.lexicalEnvironment.previous
}

func (tx *classFieldsTransformer) getClassLexicalEnvironment() *classLexicalEnvironment {
	debug.Assert(tx.lexicalEnvironment != nil)
	if tx.lexicalEnvironment.data == nil {
		tx.lexicalEnvironment.data = &classLexicalEnvironment{}
	}
	return tx.lexicalEnvironment.data
}

func (tx *classFieldsTransformer) getPrivateIdentifierEnvironment() *privateEnvironment {
	debug.Assert(tx.lexicalEnvironment != nil)
	if tx.lexicalEnvironment.privateEnv == nil {
		tx.lexicalEnvironment.privateEnv = &privateEnvironment{
			members: make(map[string]*privateIdentifierInfo),
		}
	}
	return tx.lexicalEnvironment.privateEnv
}

func (tx *classFieldsTransformer) addPendingExpressions(exprs ...*ast.Expression) {
	tx.pendingExpressions = append(tx.pendingExpressions, exprs...)
}

func (tx *classFieldsTransformer) addPrivateIdentifierPropertyDeclarationToEnvironment(node *ast.Node, name *ast.Node) {
	lex := tx.getClassLexicalEnvironment()
	env := tx.getPrivateIdentifierEnvironment()
	isStatic := ast.HasStaticModifier(node)
	previousInfo, _ := tx.getPrivateIdentifier(env, name)
	isValid := !tx.isReservedPrivateName(name) && previousInfo == nil

	if isStatic {
		brandCheckIdentifier := lex.classThis
		if brandCheckIdentifier == nil {
			brandCheckIdentifier = lex.classConstructor
		}
		variableName := tx.createHoistedVariableForPrivateName(name, "")
		tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{
			kind:                 printer.PrivateIdentifierKindField,
			isStatic:             true,
			brandCheckIdentifier: brandCheckIdentifier,
			variableName:         variableName,
			isValid:              isValid,
		})
	} else {
		weakMapName := tx.createHoistedVariableForPrivateName(name, "")
		tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{
			kind:                 printer.PrivateIdentifierKindField,
			isStatic:             false,
			brandCheckIdentifier: weakMapName,
			isValid:              isValid,
		})
		tx.addPendingExpressions(
			tx.Factory().NewAssignmentExpression(
				weakMapName,
				tx.Factory().NewNewExpression(
					tx.Factory().NewIdentifier("WeakMap"),
					nil, /*typeArguments*/
					tx.Factory().NewNodeList(nil),
				),
			),
		)
	}
}

func (tx *classFieldsTransformer) addPrivateIdentifierMethodToEnvironment(name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool) {
	methodName := tx.createHoistedVariableForPrivateName(name, "")
	var brandCheckIdentifier *ast.IdentifierNode
	if isStatic {
		brandCheckIdentifier = lex.classThis
		if brandCheckIdentifier == nil {
			brandCheckIdentifier = lex.classConstructor
		}
		debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment")
	} else {
		brandCheckIdentifier = env.data.weakSetName
	}
	tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{
		kind:                 printer.PrivateIdentifierKindMethod,
		methodName:           methodName,
		brandCheckIdentifier: brandCheckIdentifier,
		isStatic:             isStatic,
		isValid:              isValid,
	})
}

func (tx *classFieldsTransformer) addPrivateIdentifierGetAccessorToEnvironment(name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool, previousInfo *privateIdentifierInfo) {
	getterName := tx.createHoistedVariableForPrivateName(name, "_get")
	var brandCheckIdentifier *ast.IdentifierNode
	if isStatic {
		brandCheckIdentifier = lex.classThis
		if brandCheckIdentifier == nil {
			brandCheckIdentifier = lex.classConstructor
		}
		debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment")
	} else {
		brandCheckIdentifier = env.data.weakSetName
		debug.Assert(brandCheckIdentifier != nil, "weakSetName should be set in private identifier environment")
	}

	if previousInfo != nil && previousInfo.kind == printer.PrivateIdentifierKindAccessor && previousInfo.isStatic == isStatic && previousInfo.getterName == nil {
		previousInfo.getterName = getterName
	} else {
		tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{
			kind:                 printer.PrivateIdentifierKindAccessor,
			getterName:           getterName,
			brandCheckIdentifier: brandCheckIdentifier,
			isStatic:             isStatic,
			isValid:              isValid,
		})
	}
}

func (tx *classFieldsTransformer) addPrivateIdentifierSetAccessorToEnvironment(name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool, previousInfo *privateIdentifierInfo) {
	setterName := tx.createHoistedVariableForPrivateName(name, "_set")
	var brandCheckIdentifier *ast.IdentifierNode
	if isStatic {
		brandCheckIdentifier = lex.classThis
		if brandCheckIdentifier == nil {
			brandCheckIdentifier = lex.classConstructor
		}
		debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment")
	} else {
		brandCheckIdentifier = env.data.weakSetName
		debug.Assert(brandCheckIdentifier != nil, "weakSetName should be set in private identifier environment")
	}

	if previousInfo != nil && previousInfo.kind == printer.PrivateIdentifierKindAccessor && previousInfo.isStatic == isStatic && previousInfo.setterName == nil {
		previousInfo.setterName = setterName
	} else {
		tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{
			kind:                 printer.PrivateIdentifierKindAccessor,
			setterName:           setterName,
			brandCheckIdentifier: brandCheckIdentifier,
			isStatic:             isStatic,
			isValid:              isValid,
		})
	}
}

func (tx *classFieldsTransformer) addPrivateIdentifierAutoAccessorToEnvironment(node *ast.Node, name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool) {
	getterName := tx.createHoistedVariableForPrivateName(name, "_get")
	setterName := tx.createHoistedVariableForPrivateName(name, "_set")
	var brandCheckIdentifier *ast.IdentifierNode
	if isStatic {
		brandCheckIdentifier = lex.classThis
		if brandCheckIdentifier == nil {
			brandCheckIdentifier = lex.classConstructor
		}
		debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment")
	} else {
		brandCheckIdentifier = env.data.weakSetName
		debug.Assert(brandCheckIdentifier != nil, "weakSetName should be set in private identifier environment")
	}

	tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{
		kind:                 printer.PrivateIdentifierKindAccessor,
		getterName:           getterName,
		setterName:           setterName,
		brandCheckIdentifier: brandCheckIdentifier,
		isStatic:             isStatic,
		isValid:              isValid,
	})
}

func (tx *classFieldsTransformer) addPrivateIdentifierToEnvironment(node *ast.Node) {
	lex := tx.getClassLexicalEnvironment()
	env := tx.getPrivateIdentifierEnvironment()
	name := node.Name()
	isStatic := ast.HasStaticModifier(node)
	previousInfo, _ := tx.getPrivateIdentifier(env, name)
	isValid := !tx.isReservedPrivateName(name) && previousInfo == nil

	if ast.IsAutoAccessorPropertyDeclaration(node) {
		tx.addPrivateIdentifierAutoAccessorToEnvironment(node, name, lex, env, isStatic, isValid)
	} else if ast.IsPropertyDeclaration(node) {
		tx.addPrivateIdentifierPropertyDeclarationToEnvironment(node, name)
	} else if ast.IsMethodDeclaration(node) {
		tx.addPrivateIdentifierMethodToEnvironment(name, lex, env, isStatic, isValid)
	} else if ast.IsGetAccessorDeclaration(node) {
		tx.addPrivateIdentifierGetAccessorToEnvironment(name, lex, env, isStatic, isValid, previousInfo)
	} else if ast.IsSetAccessorDeclaration(node) {
		tx.addPrivateIdentifierSetAccessorToEnvironment(name, lex, env, isStatic, isValid, previousInfo)
	}
}

func (tx *classFieldsTransformer) setPrivateIdentifier(env *privateEnvironment, name *ast.Node, info *privateIdentifierInfo) {
	if tx.EmitContext().HasAutoGenerateInfo(name) {
		if env.generatedIdentifiers == nil {
			env.generatedIdentifiers = make(map[*ast.Node]*privateIdentifierInfo)
		}
		env.generatedIdentifiers[tx.EmitContext().GetNodeForGeneratedName(name)] = info
	} else {
		env.members[name.Text()] = info
	}
}

func (tx *classFieldsTransformer) getPrivateIdentifier(env *privateEnvironment, name *ast.Node) (*privateIdentifierInfo, bool) {
	if tx.EmitContext().HasAutoGenerateInfo(name) {
		info, ok := env.generatedIdentifiers[tx.EmitContext().GetNodeForGeneratedName(name)]
		return info, ok
	}
	info, ok := env.members[name.Text()]
	return info, ok
}

func (tx *classFieldsTransformer) createHoistedVariableForClass(nameText string, node *ast.Node, suffix string) *ast.IdentifierNode {
	env := tx.getPrivateIdentifierEnvironment()
	var identifier *ast.IdentifierNode
	if env.data.className != nil {
		prefix := "_" + env.data.className.Text() + "_"
		identifier = tx.Factory().NewUniqueNameEx(prefix+nameText, printer.AutoGenerateOptions{
			Flags:  printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsReservedInNestedScopes,
			Suffix: suffix,
		})
	} else {
		identifier = tx.Factory().NewUniqueNameEx("_"+nameText, printer.AutoGenerateOptions{
			Flags:  printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsReservedInNestedScopes,
			Suffix: suffix,
		})
	}
	if tx.requiresBlockScopedVar() {
		tx.EmitContext().AddLexicalDeclaration(identifier)
	} else {
		tx.EmitContext().AddVariableDeclaration(identifier)
	}
	return identifier
}

func (tx *classFieldsTransformer) createHoistedVariableForClassFromNode(name *ast.Node, suffix string) *ast.IdentifierNode {
	env := tx.getPrivateIdentifierEnvironment()
	var prefix string
	if env.data.className != nil {
		prefix = "_" + env.data.className.Text() + "_"
	} else {
		prefix = "_"
	}
	identifier := tx.Factory().NewGeneratedNameForNodeEx(name, printer.AutoGenerateOptions{
		Flags:  printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsReservedInNestedScopes,
		Prefix: prefix,
		Suffix: suffix,
	})
	if tx.requiresBlockScopedVar() {
		tx.EmitContext().AddLexicalDeclaration(identifier)
	} else {
		tx.EmitContext().AddVariableDeclaration(identifier)
	}
	return identifier
}

func (tx *classFieldsTransformer) createHoistedVariableForPrivateName(name *ast.Node, suffix string) *ast.IdentifierNode {
	// If the name is a generated identifier (e.g., auto-accessor backing field),
	// use node-based name generation so the emitter can resolve the name properly.
	if tx.EmitContext().HasAutoGenerateInfo(name) {
		return tx.createHoistedVariableForClassFromNode(name, suffix)
	}
	text := name.Text()
	if len(text) >= 1 && text[0] == '#' {
		text = text[1:] // strip leading '#'
	}
	return tx.createHoistedVariableForClass(text, name, suffix)
}

// accessPrivateIdentifier accesses an already defined PrivateIdentifier in the current
// PrivateIdentifierEnvironment.
func (tx *classFieldsTransformer) accessPrivateIdentifier(name *ast.Node) *privateIdentifierInfo {
	for env := tx.lexicalEnvironment; env != nil; env = env.previous {
		if env.privateEnv != nil {
			if info, ok := tx.getPrivateIdentifier(env.privateEnv, name); ok {
				if info.kind == printer.PrivateIdentifierKindUntransformed {
					return nil
				}
				return info
			}
		}
	}
	return nil
}

func (tx *classFieldsTransformer) wrapPrivateIdentifierForDestructuringTarget(node *ast.Node) *ast.Node {
	prop := node.AsPropertyAccessExpression()
	parameter := tx.Factory().NewGeneratedNameForNode(node)
	info := tx.accessPrivateIdentifier(prop.Name())
	if info == nil {
		return tx.Visitor().VisitEachChild(node)
	}
	receiver := prop.Expression
	// We cannot copy `this` or `super` into the function because they will be bound
	// differently inside the function.
	isThisOrSuperProperty := prop.Expression.Kind == ast.KindThisKeyword || prop.Expression.Kind == ast.KindSuperKeyword
	if isThisOrSuperProperty || !transformers.IsSimpleCopiableExpression(prop.Expression) {
		receiver = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{
			Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes,
		})
		tx.EmitContext().AddVariableDeclaration(receiver)
		tx.pendingExpressions = append(
			tx.pendingExpressions,
			tx.Factory().NewAssignmentExpression(receiver, tx.Visitor().VisitNode(prop.Expression)),
		)
	}
	assignExpr := tx.createPrivateIdentifierAssignment(info, receiver, parameter, ast.KindEqualsToken)
	return tx.Factory().NewAssignmentTargetWrapper(parameter, assignExpr)
}

func (tx *classFieldsTransformer) visitAssignmentElement(node *ast.Node) *ast.Node {
	// 13.15.5.5 RS: IteratorDestructuringAssignmentEvaluation
	//   AssignmentElement : DestructuringAssignmentTarget Initializer?
	//     ...
	//     4. If |Initializer| is present and _value_ is *undefined*, then
	//        a. If IsAnonymousFunctionDefinition(|Initializer|) and IsIdentifierRef of |DestructuringAssignmentTarget| are both *true*, then
	//           i. Let _v_ be ? NamedEvaluation of |Initializer| with argument _lref_.[[ReferencedName]].
	//     ...

	if isNamedEvaluationAnd(tx.EmitContext(), node, tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node, false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/)
	}
	if ast.IsAssignmentExpression(node, true /*excludeCompoundAssignment*/) {
		left := tx.visitDestructuringAssignmentTarget(node.AsBinaryExpression().Left)
		right := tx.Visitor().VisitNode(node.AsBinaryExpression().Right)
		return tx.Factory().UpdateBinaryExpression(
			node.AsBinaryExpression(),
			nil,
			left,
			nil,
			node.AsBinaryExpression().OperatorToken,
			right,
		)
	}
	return tx.visitDestructuringAssignmentTarget(node)
}

func (tx *classFieldsTransformer) visitAssignmentRestElement(node *ast.Node) *ast.Node {
	spread := node.AsSpreadElement()
	if ast.IsLeftHandSideExpression(spread.Expression) {
		expr := tx.visitDestructuringAssignmentTarget(spread.Expression)
		return tx.Factory().UpdateSpreadElement(spread, expr)
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitArrayAssignmentElement(node *ast.Node) *ast.Node {
	if ast.IsArrayBindingOrAssignmentElement(node) {
		if ast.IsSpreadElement(node) {
			return tx.visitAssignmentRestElement(node)
		}
		if node.Kind != ast.KindOmittedExpression {
			return tx.visitAssignmentElement(node)
		}
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitAssignmentProperty(node *ast.Node) *ast.Node {
	// AssignmentProperty : PropertyName `:` AssignmentElement
	// AssignmentElement : DestructuringAssignmentTarget Initializer?

	// 13.15.5.6 RS: KeyedDestructuringAssignmentEvaluation
	//   AssignmentElement : DestructuringAssignmentTarget Initializer?
	//     ...
	//     3. If |Initializer| is present and _v_ is *undefined*, then
	//        a. If IsAnonymousfunctionDefinition(|Initializer|) and IsIdentifierRef of |DestructuringAssignmentTarget| are both *true*, then
	//           i. Let _rhsValue_ be ? NamedEvaluation of |Initializer| with argument _lref_.[[ReferencedName]].
	//     ...

	prop := node.AsPropertyAssignment()
	name := tx.Visitor().VisitNode(prop.Name())
	init := prop.Initializer
	if ast.IsAssignmentExpression(init, true /*excludeCompoundAssignment*/) {
		assignElem := tx.visitAssignmentElement(init)
		return tx.Factory().UpdatePropertyAssignment(prop, nil, name, nil, nil, assignElem)
	}
	if ast.IsLeftHandSideExpression(init) {
		target := tx.visitDestructuringAssignmentTarget(init)
		return tx.Factory().UpdatePropertyAssignment(prop, nil, name, nil, nil, target)
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitShorthandAssignmentProperty(node *ast.Node) *ast.Node {
	// AssignmentProperty : IdentifierReference Initializer?

	// 13.15.5.3 RS: PropertyDestructuringAssignmentEvaluation
	//   AssignmentProperty : IdentifierReference Initializer?
	//     ...
	//     4. If |Initializer?| is present and _v_ is *undefined*, then
	//        a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then
	//           i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _P_.
	//     ...

	if isNamedEvaluationAnd(tx.EmitContext(), node, tx.isAnonymousClassNeedingAssignedName) {
		node = transformNamedEvaluation(tx.EmitContext(), node, false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/)
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitAssignmentRestProperty(node *ast.Node) *ast.Node {
	spread := node.AsSpreadAssignment()
	if ast.IsLeftHandSideExpression(spread.Expression) {
		expr := tx.visitDestructuringAssignmentTarget(spread.Expression)
		return tx.Factory().UpdateSpreadAssignment(spread, expr)
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitObjectAssignmentElement(node *ast.Node) *ast.Node {
	debug.Assert(node != nil && ast.IsObjectBindingOrAssignmentElement(node))
	if ast.IsSpreadAssignment(node) {
		return tx.visitAssignmentRestProperty(node)
	}
	if ast.IsShorthandPropertyAssignment(node) {
		return tx.visitShorthandAssignmentProperty(node)
	}
	if ast.IsPropertyAssignment(node) {
		return tx.visitAssignmentProperty(node)
	}
	return tx.Visitor().VisitEachChild(node)
}

func (tx *classFieldsTransformer) visitAssignmentPattern(node *ast.Node) *ast.Node {
	if ast.IsArrayLiteralExpression(node) {
		// Transforms private names in destructuring assignment array bindings.
		// Transforms SuperProperty assignments in destructuring assignment array bindings in static initializers.
		//
		// Source:
		// ([ this.#myProp ] = [ "hello" ]);
		//
		// Transformation:
		// [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ];
		return tx.Factory().UpdateArrayLiteralExpression(
			node.AsArrayLiteralExpression(),
			tx.arrayAssignmentElementVisitor.VisitNodes(node.AsArrayLiteralExpression().Elements),
			node.AsArrayLiteralExpression().MultiLine,
		)
	}
	// Transforms private names in destructuring assignment object bindings.
	// Transforms SuperProperty assignments in destructuring assignment object bindings in static initializers.
	//
	// Source:
	// ({ stringProperty: this.#myProp } = { stringProperty: "hello" });
	//
	// Transformation:
	// ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" };
	return tx.Factory().UpdateObjectLiteralExpression(
		node.AsObjectLiteralExpression(),
		tx.objectAssignmentElementVisitor.VisitNodes(node.AsObjectLiteralExpression().Properties),
		node.AsObjectLiteralExpression().MultiLine,
	)
}

func createPrivateStaticFieldInitializer(factory *printer.NodeFactory, variableName *ast.IdentifierNode, initializer *ast.Expression) *ast.Expression {
	if initializer == nil {
		initializer = factory.NewVoidZeroExpression()
	}
	return factory.NewAssignmentExpression(
		variableName,
		factory.NewObjectLiteralExpression(
			factory.NewNodeList([]*ast.Node{
				factory.NewPropertyAssignment(nil, factory.NewIdentifier("value"), nil, nil, initializer),
			}),
			false,
		),
	)
}

func createPrivateInstanceFieldInitializer(factory *printer.NodeFactory, receiver *ast.Expression, initializer *ast.Expression, weakMapName *ast.IdentifierNode) *ast.Expression {
	if initializer == nil {
		initializer = factory.NewVoidZeroExpression()
	}
	return factory.NewMethodCall(weakMapName, factory.NewIdentifier("set"), []*ast.Node{receiver, initializer})
}

func createPrivateInstanceMethodInitializer(factory *printer.NodeFactory, receiver *ast.Expression, weakSetName *ast.IdentifierNode) *ast.Expression {
	return factory.NewMethodCall(weakSetName, factory.NewIdentifier("add"), []*ast.Node{receiver})
}

func (tx *classFieldsTransformer) isReservedPrivateName(node *ast.Node) bool {
	return !(ast.IsPrivateIdentifier(node) && tx.EmitContext().HasAutoGenerateInfo(node)) && node.Text() == "#constructor"
}

func isStaticPropertyDeclarationOrClassStaticBlock(node *ast.Node) bool {
	return ast.IsClassStaticBlockDeclaration(node) ||
		(ast.IsPropertyDeclaration(node) && ast.HasStaticModifier(node))
}

func (tx *classFieldsTransformer) getProperties(node *ast.Node, requireInitializer bool, isStatic bool) []*ast.Node {
	var result []*ast.Node
	for _, member := range node.Members() {
		if ast.IsPropertyDeclaration(member) &&
			(!requireInitializer || member.Initializer() != nil) &&
			ast.HasStaticModifier(member) == isStatic {
			result = append(result, member)
		}
	}
	return result
}

func (tx *classFieldsTransformer) getStaticPropertiesAndClassStaticBlock(node *ast.Node) []*ast.Node {
	var result []*ast.Node
	for _, member := range node.Members() {
		if ast.IsClassStaticBlockDeclaration(member) || (ast.IsPropertyDeclaration(member) && ast.HasStaticModifier(member)) {
			result = append(result, member)
		}
	}
	return result
}

// classHasClassThisAssignment checks if a class has a static block that is a class-this assignment.
func classHasClassThisAssignment(emitContext *printer.EmitContext, node *ast.Node) bool {
	for _, member := range node.Members() {
		if isClassThisAssignmentBlock(emitContext, member) {
			return true
		}
	}
	return false
}

func isNonStaticMethodOrAccessorWithPrivateName(member *ast.Node) bool {
	return !ast.IsStatic(member) &&
		(ast.IsMethodOrAccessor(member) || ast.IsAutoAccessorPropertyDeclaration(member)) &&
		ast.IsPrivateIdentifier(member.Name())
}

func createMemberAccessForPropertyName(factory *printer.NodeFactory, emitContext *printer.EmitContext, receiver *ast.Expression, name *ast.PropertyName, location *ast.PropertyName) *ast.Expression {
	if ast.IsComputedPropertyName(name) {
		expression := factory.NewElementAccessExpression(receiver, nil, name.Expression(), ast.NodeFlagsNone)
		expression.Loc = location.Loc
		return expression
	}
	var expression *ast.Expression
	if ast.IsIdentifier(name) || ast.IsPrivateIdentifier(name) {
		expression = factory.NewPropertyAccessExpression(receiver, nil, name, ast.NodeFlagsNone)
	} else {
		// string or numeric literal
		expression = factory.NewElementAccessExpression(receiver, nil, name, ast.NodeFlagsNone)
	}
	emitContext.SetCommentRange(expression, name.Loc)
	emitContext.SetSourceMapRange(expression, name.Loc)
	emitContext.AddEmitFlags(expression, printer.EFNoNestedSourceMaps)
	return expression
}

func (tx *classFieldsTransformer) createCallBinding(node *ast.Node) (thisArg *ast.Expression, target *ast.Expression) {
	if ast.IsSuperProperty(node) {
		return tx.Factory().NewThisExpression(), node
	}
	if ast.IsPropertyAccessExpression(node) {
		expr := node.AsPropertyAccessExpression()
		if shouldBeCapturedInTempVariable(expr.Expression) {
			thisArg = tx.Factory().NewTempVariable()
			tx.EmitContext().AddVariableDeclaration(thisArg)
			target = tx.Factory().NewPropertyAccessExpression(
				tx.Factory().NewParenthesizedExpression( // TODO: do we even need these?
					tx.Factory().NewAssignmentExpression(thisArg, expr.Expression),
				),
				nil,
				expr.Name(),
				ast.NodeFlagsNone,
			)
			return thisArg, target
		}
		return expr.Expression, node
	}
	thisArg = tx.Factory().NewVoidZeroExpression()
	target = node
	return thisArg, target
}

func shouldBeCapturedInTempVariable(node *ast.Node) bool {
	target := ast.SkipParentheses(node)
	switch target.Kind {
	case ast.KindIdentifier, ast.KindThisKeyword, ast.KindNumericLiteral, ast.KindBigIntLiteral, ast.KindStringLiteral:
		return false
	default:
		return true
	}
}

func (tx *classFieldsTransformer) createAccessorPropertyGetRedirector(node *ast.PropertyDeclaration, modifiers *ast.ModifierList, name *ast.PropertyName, receiver *ast.Expression) *ast.Node {
	backingFieldName := tx.Factory().NewGeneratedPrivateNameForNodeEx(node.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"})
	returnExpr := tx.Factory().NewPropertyAccessExpression(
		receiver,
		nil,
		backingFieldName,
		ast.NodeFlagsNone,
	)
	returnStmt := tx.Factory().NewReturnStatement(returnExpr)
	body := tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{returnStmt}), false)
	return tx.Factory().NewGetAccessorDeclaration(
		modifiers,
		name,
		nil, /*typeParameters*/
		tx.Factory().NewNodeList([]*ast.Node{}),
		nil, /*returnType*/
		nil, /*fullSignature*/
		body,
	)
}

func (tx *classFieldsTransformer) createAccessorPropertySetRedirector(node *ast.PropertyDeclaration, modifiers *ast.ModifierList, name *ast.PropertyName, receiver *ast.Expression) *ast.Node {
	backingFieldName := tx.Factory().NewGeneratedPrivateNameForNodeEx(node.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"})
	valueParam := tx.Factory().NewParameterDeclaration(
		nil, /*modifiers*/
		nil, /*dotDotDotToken*/
		tx.Factory().NewIdentifier("value"),
		nil, /*questionToken*/
		nil, /*typeNode*/
		nil, /*initializer*/
	)
	assignExpr := tx.Factory().NewAssignmentExpression(
		tx.Factory().NewPropertyAccessExpression(
			receiver,
			nil,
			backingFieldName,
			ast.NodeFlagsNone,
		),
		tx.Factory().NewIdentifier("value"),
	)
	exprStmt := tx.Factory().NewExpressionStatement(assignExpr)
	body := tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{exprStmt}), false)
	return tx.Factory().NewSetAccessorDeclaration(
		modifiers,
		name,
		nil, /*typeParameters*/
		tx.Factory().NewNodeList([]*ast.Node{valueParam}),
		nil, /*returnType*/
		nil, /*fullSignature*/
		body,
	)
}

// flattenCommaList decomposes a comma expression tree into a sequence of expressions.
func flattenCommaList(node *ast.Expression) iter.Seq[*ast.Expression] {
	return func(yield func(*ast.Expression) bool) {
		flattenCommaListWorker(node, yield)
	}
}

func flattenCommaListWorker(node *ast.Expression, yield func(*ast.Expression) bool) bool {
	if ast.IsParenthesizedExpression(node) && ast.NodeIsSynthesized(node) {
		return flattenCommaListWorker(node.Expression(), yield)
	} else if ast.IsCommaExpression(node.AsNode()) {
		return flattenCommaListWorker(node.AsBinaryExpression().Left, yield) &&
			flattenCommaListWorker(node.AsBinaryExpression().Right, yield)
	} else {
		return yield(node)
	}
}

func findComputedPropertyNameCacheAssignment(emitContext *printer.EmitContext, name *ast.Node) *ast.BinaryExpression {
	node := name.Expression()
	for {
		node = ast.SkipOuterExpressions(node, 0)
		if ast.IsBinaryExpression(node) && node.AsBinaryExpression().OperatorToken.Kind == ast.KindCommaToken {
			node = node.AsBinaryExpression().Right
			continue
		}
		if ast.IsAssignmentExpression(node, true /*excludeCompoundAssignment*/) && ast.IsIdentifier(node.AsBinaryExpression().Left) {
			return node.AsBinaryExpression()
		}
		break
	}
	return nil
}

func expandPreOrPostfixIncrementOrDecrementExpression(factory *printer.NodeFactory, emitContext *printer.EmitContext, node *ast.Node, expression *ast.Expression, resultVariable *ast.IdentifierNode) *ast.Expression {
	var operator ast.Kind
	var operand *ast.Node
	if ast.IsPrefixUnaryExpression(node) {
		operator = node.AsPrefixUnaryExpression().Operator
		operand = node.AsPrefixUnaryExpression().Operand
	} else {
		operator = node.AsPostfixUnaryExpression().Operator
		operand = node.AsPostfixUnaryExpression().Operand
	}

	temp := factory.NewTempVariable()
	emitContext.AddVariableDeclaration(temp)
	expression = factory.NewAssignmentExpression(temp, expression)
	expression.Loc = operand.Loc

	var operation *ast.Expression
	if ast.IsPrefixUnaryExpression(node) {
		operation = factory.NewPrefixUnaryExpression(operator, temp)
	} else {
		operation = factory.NewPostfixUnaryExpression(temp, operator)
	}
	operation.Loc = node.Loc

	if resultVariable != nil {
		operation = factory.NewAssignmentExpression(resultVariable, operation)
		operation.Loc = node.Loc
	}

	expression = factory.NewCommaExpression(expression, operation)
	expression.Loc = node.Loc

	if ast.IsPostfixUnaryExpression(node) {
		expression = factory.NewCommaExpression(expression, temp)
		expression.Loc = node.Loc
	}

	return expression
}
