package estransforms

import (
	"strconv"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/transformers"
)

type pendingDecl struct {
	pendingExpressions []*ast.Node
	name               *ast.Node
	value              *ast.Node
	location           core.TextRange
	original           *ast.Node
}

type flattenLevel int

const (
	flattenLevelAll flattenLevel = iota
	flattenLevelObjectRest
)

type flattenContext struct {
	level                                  flattenLevel
	currentExpressions                     []*ast.Node
	currentDeclarations                    []pendingDecl
	hasTransformedPriorElement             bool
	emitBindingOrAssignment                func(t *objectRestSpreadTransformer, target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node)
	createArrayBindingOrAssignmentPattern  func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node
	createObjectBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node
	createArrayBindingOrAssignmentElement  func(t *objectRestSpreadTransformer, expr *ast.Node) *ast.Node
	hoistTempVariables                     bool
}

type oldFlattenContext flattenContext

type objectRestSpreadTransformer struct {
	transformers.Transformer
	compilerOptions *core.CompilerOptions

	inExportedVariableStatement bool

	ctx                                       flattenContext
	parametersWithPrecedingObjectRestOrSpread map[*ast.Node]struct{}
}

func (ch *objectRestSpreadTransformer) enterFlattenContext(
	level flattenLevel,
	emitBindingOrAssignment func(t *objectRestSpreadTransformer, target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node),
	createArrayBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node,
	createObjectBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node,
	createArrayBindingOrAssignmentElement func(t *objectRestSpreadTransformer, expr *ast.Node) *ast.Node,
	hoistTempVariables bool,
) oldFlattenContext {
	old := ch.ctx
	ch.ctx = flattenContext{
		level:                                  level,
		emitBindingOrAssignment:                emitBindingOrAssignment,
		createArrayBindingOrAssignmentPattern:  createArrayBindingOrAssignmentPattern,
		createObjectBindingOrAssignmentPattern: createObjectBindingOrAssignmentPattern,
		createArrayBindingOrAssignmentElement:  createArrayBindingOrAssignmentElement,
		hoistTempVariables:                     hoistTempVariables,
	}
	return oldFlattenContext(old)
}

func (ch *objectRestSpreadTransformer) exitFlattenContext(old oldFlattenContext) {
	ch.ctx = flattenContext(old)
}

func (ch *objectRestSpreadTransformer) visit(node *ast.Node) *ast.Node {
	if node.SubtreeFacts()&ast.SubtreeContainsESObjectRestOrSpread == 0 && ch.parametersWithPrecedingObjectRestOrSpread == nil {
		return node
	}
	switch node.Kind {
	case ast.KindSourceFile:
		return ch.visitSourceFile(node.AsSourceFile())
	case ast.KindObjectLiteralExpression:
		return ch.visitObjectLiteralExpression(node.AsObjectLiteralExpression())
	case ast.KindBinaryExpression:
		return ch.visitBinaryExpression(node.AsBinaryExpression())
	case ast.KindForOfStatement:
		return ch.visitForOftatement(node.AsForInOrOfStatement())
	case ast.KindVariableStatement:
		return ch.visitVariableStatement(node.AsVariableStatement())
	case ast.KindVariableDeclaration:
		return ch.visitVariableDeclaration(node.AsVariableDeclaration())
	case ast.KindCatchClause:
		return ch.visitCatchClause(node.AsCatchClause())
	case ast.KindParameter:
		return ch.visitParameter(node.AsParameterDeclaration())
	case ast.KindConstructor:
		return ch.visitContructorDeclaration(node.AsConstructorDeclaration())
	case ast.KindGetAccessor:
		return ch.visitGetAccessorDeclaration(node.AsGetAccessorDeclaration())
	case ast.KindSetAccessor:
		return ch.visitSetAccessorDeclaration(node.AsSetAccessorDeclaration())
	case ast.KindMethodDeclaration:
		return ch.visitMethodDeclaration(node.AsMethodDeclaration())
	case ast.KindFunctionDeclaration:
		return ch.visitFunctionDeclaration(node.AsFunctionDeclaration())
	case ast.KindArrowFunction:
		return ch.visitArrowFunction(node.AsArrowFunction())
	case ast.KindFunctionExpression:
		return ch.visitFunctionExpression(node.AsFunctionExpression())
	default:
		return ch.Visitor().VisitEachChild(node)
	}
}

func (ch *objectRestSpreadTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node {
	visited := ch.Visitor().VisitEachChild(node.AsNode())
	ch.EmitContext().AddEmitHelper(visited.AsNode(), ch.EmitContext().ReadEmitHelpers()...)
	return visited
}

func (ch *objectRestSpreadTransformer) visitParameter(node *ast.ParameterDeclaration) *ast.Node {
	if ch.parametersWithPrecedingObjectRestOrSpread != nil {
		if _, ok := ch.parametersWithPrecedingObjectRestOrSpread[node.AsNode()]; ok {
			name := node.Name()
			if ast.IsBindingPattern(name) {
				name = ch.Factory().NewGeneratedNameForNode(node.AsNode())
			}
			return ch.Factory().UpdateParameterDeclaration(
				node,
				nil,
				node.DotDotDotToken,
				name,
				nil,
				nil,
				nil,
			)
		}
	}
	if node.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 {
		// Binding patterns are converted into a generated name and are
		// evaluated inside the function body.
		return ch.Factory().UpdateParameterDeclaration(
			node,
			nil,
			node.DotDotDotToken,
			ch.Factory().NewGeneratedNameForNode(node.AsNode()),
			nil,
			nil,
			ch.Visitor().VisitNode(node.Initializer),
		)
	}
	return ch.Visitor().VisitEachChild(node.AsNode())
}

func (ch *objectRestSpreadTransformer) collectParametersWithPrecedingObjectRestOrSpread(node *ast.Node) map[*ast.Node]struct{} {
	var result map[*ast.Node]struct{}
	for _, parameter := range node.Parameters() {
		if result != nil {
			result[parameter] = struct{}{}
		} else if parameter.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 {
			result = make(map[*ast.Node]struct{})
		}
	}
	return result
}

type oldParamScope map[*ast.Node]struct{}

func (ch *objectRestSpreadTransformer) enterParameterListContext(node *ast.Node) oldParamScope {
	old := ch.parametersWithPrecedingObjectRestOrSpread
	ch.parametersWithPrecedingObjectRestOrSpread = ch.collectParametersWithPrecedingObjectRestOrSpread(node)
	return oldParamScope(old)
}

func (ch *objectRestSpreadTransformer) exitParameterListContext(scope oldParamScope) {
	ch.parametersWithPrecedingObjectRestOrSpread = map[*ast.Node]struct{}(scope)
}

func (ch *objectRestSpreadTransformer) visitContructorDeclaration(node *ast.ConstructorDeclaration) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateConstructorDeclaration(
		node,
		node.Modifiers(),
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) visitGetAccessorDeclaration(node *ast.GetAccessorDeclaration) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateGetAccessorDeclaration(
		node,
		node.Modifiers(),
		ch.Visitor().VisitNode(node.Name()),
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) visitSetAccessorDeclaration(node *ast.SetAccessorDeclaration) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateSetAccessorDeclaration(
		node,
		node.Modifiers(),
		ch.Visitor().VisitNode(node.Name()),
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) visitMethodDeclaration(node *ast.MethodDeclaration) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateMethodDeclaration(
		node,
		node.Modifiers(),
		node.AsteriskToken,
		ch.Visitor().VisitNode(node.Name()),
		node.PostfixToken,
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) visitFunctionDeclaration(node *ast.FunctionDeclaration) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateFunctionDeclaration(
		node,
		node.Modifiers(),
		node.AsteriskToken,
		ch.Visitor().VisitNode(node.Name()),
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) visitArrowFunction(node *ast.ArrowFunction) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateArrowFunction(
		node,
		node.Modifiers(),
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		node.EqualsGreaterThanToken,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) visitFunctionExpression(node *ast.FunctionExpression) *ast.Node {
	old := ch.enterParameterListContext(node.AsNode())
	defer ch.exitParameterListContext(old)
	return ch.Factory().UpdateFunctionExpression(
		node,
		node.Modifiers(),
		node.AsteriskToken,
		ch.Visitor().VisitNode(node.Name()),
		nil,
		ch.Visitor().VisitNodes(node.Parameters),
		nil,
		nil,
		ch.transformFunctionBody(node.AsNode()),
	)
}

func (ch *objectRestSpreadTransformer) transformFunctionBody(node *ast.Node) *ast.Node {
	ch.EmitContext().StartVariableEnvironment()
	body := ch.Visitor().VisitNode(node.Body())
	extras := ch.EmitContext().EndVariableEnvironment()
	ch.EmitContext().StartVariableEnvironment()
	newStatements := ch.collectObjectRestAssignments(node)
	extras = ch.EmitContext().EndAndMergeVariableEnvironment(extras)
	if len(newStatements) == 0 && len(extras) == 0 {
		return body
	}

	if body == nil {
		body = ch.Factory().NewBlock(ch.Factory().NewNodeList([]*ast.Node{}), true)
	}
	var prefix []*ast.Node
	var suffix []*ast.Node
	if ast.IsBlock(body) {
		custom := false
		for i, statement := range body.AsBlock().Statements.Nodes {
			if !custom && ast.IsPrologueDirective(statement) {
				prefix = append(prefix, statement)
			} else if ch.EmitContext().EmitFlags(statement)&printer.EFCustomPrologue != 0 {
				custom = true
				prefix = append(prefix, statement)
			} else {
				suffix = body.AsBlock().Statements.Nodes[i:]
				break
			}
		}
	} else {
		ret := ch.Factory().NewReturnStatement(body)
		ret.Loc = body.Loc
		list := ch.Factory().NewNodeList([]*ast.Node{})
		list.Loc = body.Loc
		body = ch.Factory().NewBlock(list, true)
		suffix = append(suffix, ret)
	}

	newStatementList := ch.Factory().NewNodeList(append(append(append(prefix, extras...), newStatements...), suffix...))
	newStatementList.Loc = body.AsBlock().Statements.Loc
	return ch.Factory().UpdateBlock(body.AsBlock(), newStatementList)
}

func (ch *objectRestSpreadTransformer) collectObjectRestAssignments(node *ast.Node) []*ast.Node {
	containsPrecedingObjectRestOrSpread := false
	var results []*ast.Node
	for _, parameter := range node.Parameters() {
		if containsPrecedingObjectRestOrSpread {
			if ast.IsBindingPattern(parameter.Name()) {
				// In cases where a binding pattern is simply '[]' or '{}',
				// we usually don't want to emit a var declaration; however, in the presence
				// of an initializer, we must emit that expression to preserve side effects.
				if len(parameter.Name().AsBindingPattern().Elements.Nodes) > 0 {
					declarations := ch.flattenDestructuringBinding(flattenLevelAll, parameter, ch.Factory().NewGeneratedNameForNode(parameter), false, false)
					if declarations != nil {
						declarationList := ch.Factory().NewVariableDeclarationList(ast.NodeFlagsNone, ch.Factory().NewNodeList([]*ast.Node{}))
						decls := []*ast.Node{declarations}
						if declarations.Kind == ast.KindSyntaxList {
							decls = declarations.AsSyntaxList().Children
						}
						declarationList.AsVariableDeclarationList().Declarations.Nodes = append(declarationList.AsVariableDeclarationList().Declarations.Nodes, decls...)
						statement := ch.Factory().NewVariableStatement(nil, declarationList)
						ch.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue)
						results = append(results, statement)
					}
				} else if parameter.Initializer() != nil {
					name := ch.Factory().NewGeneratedNameForNode(parameter)
					initializer := ch.Visitor().VisitNode(parameter.Initializer())
					assignment := ch.Factory().NewAssignmentExpression(name, initializer)
					statement := ch.Factory().NewExpressionStatement(assignment)
					ch.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue)
					results = append(results, statement)

				}
			} else if parameter.Initializer() != nil {
				// Converts a parameter initializer into a function body statement, i.e.:
				//
				//  function f(x = 1) { }
				//
				// becomes
				//
				//  function f(x) {
				//    if (typeof x === "undefined") { x = 1; }
				//  }
				name := parameter.Name().Clone(ch.Factory())
				name.Loc = parameter.Name().Loc
				ch.EmitContext().AddEmitFlags(name, printer.EFNoSourceMap)

				initializer := ch.Visitor().VisitNode(parameter.Initializer())
				ch.EmitContext().AddEmitFlags(initializer, printer.EFNoSourceMap|printer.EFNoComments)

				assignment := ch.Factory().NewAssignmentExpression(name, initializer)
				assignment.Loc = parameter.Loc
				ch.EmitContext().AddEmitFlags(assignment, printer.EFNoComments)

				block := ch.Factory().NewBlock(ch.Factory().NewNodeList([]*ast.Node{ch.Factory().NewExpressionStatement(assignment)}), false)
				block.Loc = parameter.Loc
				ch.EmitContext().AddEmitFlags(block, printer.EFSingleLine|printer.EFNoTrailingSourceMap|printer.EFNoTokenSourceMaps|printer.EFNoComments)

				typeCheck := ch.Factory().NewTypeCheck(name.Clone(ch.Factory()), "undefined")
				statement := ch.Factory().NewIfStatement(typeCheck, block, nil)
				statement.Loc = parameter.Loc
				ch.EmitContext().AddEmitFlags(statement, printer.EFNoTokenSourceMaps|printer.EFNoTrailingSourceMap|printer.EFCustomPrologue|printer.EFNoComments|printer.EFStartOnNewLine)
				results = append(results, statement)
			}
		} else if parameter.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 {
			containsPrecedingObjectRestOrSpread = true
			declarations := ch.flattenDestructuringBinding(flattenLevelObjectRest, parameter, ch.Factory().NewGeneratedNameForNode(parameter), false, true)
			if declarations != nil {
				declarationList := ch.Factory().NewVariableDeclarationList(ast.NodeFlagsNone, ch.Factory().NewNodeList([]*ast.Node{}))
				decls := []*ast.Node{declarations}
				if declarations.Kind == ast.KindSyntaxList {
					decls = declarations.AsSyntaxList().Children
				}
				declarationList.AsVariableDeclarationList().Declarations.Nodes = append(declarationList.AsVariableDeclarationList().Declarations.Nodes, decls...)
				statement := ch.Factory().NewVariableStatement(nil, declarationList)
				ch.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue)
				results = append(results, statement)
			}
		}
	}

	return results
}

func (ch *objectRestSpreadTransformer) visitCatchClause(node *ast.CatchClause) *ast.Node {
	if node.VariableDeclaration != nil && ast.IsBindingPattern(node.VariableDeclaration.Name()) && node.VariableDeclaration.Name().SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 {
		name := ch.Factory().NewGeneratedNameForNode(node.VariableDeclaration.Name())
		updatedDecl := ch.Factory().UpdateVariableDeclaration(node.VariableDeclaration.AsVariableDeclaration(), node.VariableDeclaration.Name(), nil, nil, name)
		visitedBindings := ch.flattenDestructuringBinding(flattenLevelObjectRest, updatedDecl, nil, false, false)
		block := ch.Visitor().VisitNode(node.Block)
		if visitedBindings != nil {
			var decls []*ast.Node
			if visitedBindings.Kind&ast.KindSyntaxList != 0 {
				decls = visitedBindings.AsSyntaxList().Children
			} else {
				decls = []*ast.Node{visitedBindings}
			}
			newStatement := ch.Factory().NewVariableStatement(nil, ch.Factory().NewVariableDeclarationList(ast.NodeFlagsNone, ch.Factory().NewNodeList(decls)))
			statements := []*ast.Node{newStatement}
			if block.AsBlock().Statements != nil && len(block.AsBlock().Statements.Nodes) > 0 {
				statements = append(statements, block.AsBlock().Statements.Nodes...)
			}
			statementList := ch.Factory().NewNodeList(statements)
			statementList.Loc = block.AsBlock().Statements.Loc

			block = ch.Factory().UpdateBlock(block.AsBlock(), statementList)
		}
		return ch.Factory().UpdateCatchClause(
			node,
			ch.Factory().UpdateVariableDeclaration(node.VariableDeclaration.AsVariableDeclaration(), name, nil, nil, nil),
			block,
		)
	}
	return ch.Visitor().VisitEachChild(node.AsNode())
}

func (ch *objectRestSpreadTransformer) visitVariableStatement(node *ast.VariableStatement) *ast.Node {
	if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) {
		oldInExportedVariableStatement := ch.inExportedVariableStatement
		ch.inExportedVariableStatement = true
		result := ch.Visitor().VisitEachChild(node.AsNode())
		ch.inExportedVariableStatement = oldInExportedVariableStatement
		return result
	}
	return ch.Visitor().VisitEachChild(node.AsNode())
}

func (ch *objectRestSpreadTransformer) visitVariableDeclaration(node *ast.VariableDeclaration) *ast.Node {
	if ch.inExportedVariableStatement {
		ch.inExportedVariableStatement = false
		result := ch.visitVariableDeclarationWorker(node, true)
		ch.inExportedVariableStatement = true
		return result
	}
	return ch.visitVariableDeclarationWorker(node, false)
}

func (ch *objectRestSpreadTransformer) visitVariableDeclarationWorker(node *ast.VariableDeclaration, exported bool) *ast.Node {
	// If we are here it is because the name contains a binding pattern with a rest somewhere in it.
	if ast.IsBindingPattern(node.Name()) && node.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 {
		return ch.flattenDestructuringBinding(
			flattenLevelObjectRest,
			node.AsNode(),
			nil,
			exported,
			false,
		)
	}
	return ch.Visitor().VisitEachChild(node.AsNode())
}

func (ch *objectRestSpreadTransformer) flattenDestructuringBinding(level flattenLevel, node *ast.Node, rvalue *ast.Node, hoist bool, skipInitializer bool) *ast.Node {
	old := ch.enterFlattenContext(level, (*objectRestSpreadTransformer).emitBinding, (*objectRestSpreadTransformer).createArrayBindingPattern, (*objectRestSpreadTransformer).createObjectBindingPattern, (*objectRestSpreadTransformer).createArrayBindingElement, hoist)
	defer ch.exitFlattenContext(old)

	if ast.IsVariableDeclaration(node) {
		initializer := getInitializerOfBindingOrAssignmentElement(node)
		if initializer != nil && (ast.IsIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.AsIdentifier().Text) || bindingOrAssignmentElementContainsNonLiteralComputedName(node)) {
			// If the right-hand value of the assignment is also an assignment target then
			// we need to cache the right-hand value.
			initializer = ch.ensureIdentifier(ch.Visitor().VisitNode(initializer), false, initializer.Loc)
			node = ch.Factory().UpdateVariableDeclaration(node.AsVariableDeclaration(), node.Name(), nil, nil, initializer)
		}
	}

	ch.flattenBindingOrAssignmentElement(node, rvalue, node.Loc, skipInitializer)

	if len(ch.ctx.currentExpressions) > 0 {
		temp := ch.Factory().NewTempVariable()
		ch.EmitContext().AddVariableDeclaration(temp)
		last := &ch.ctx.currentDeclarations[len(ch.ctx.currentDeclarations)-1]
		last.pendingExpressions = append(last.pendingExpressions, ch.Factory().NewAssignmentExpression(temp, last.value))
		last.pendingExpressions = append(last.pendingExpressions, ch.ctx.currentExpressions...)
		last.value = temp
	}
	decls := make([]*ast.Node, 0, len(ch.ctx.currentDeclarations))
	for _, pending := range ch.ctx.currentDeclarations {
		expr := pending.value
		if len(pending.pendingExpressions) > 0 {
			expr = ch.Factory().InlineExpressions(append(pending.pendingExpressions, pending.value))
		}
		decl := ch.Factory().NewVariableDeclaration(
			pending.name,
			nil,
			nil,
			expr,
		)
		decl.Loc = pending.location
		if pending.original != nil {
			ch.EmitContext().SetOriginal(decl, pending.original)
		}
		decls = append(decls, decl)
	}
	if len(decls) == 1 {
		return decls[0]
	}
	if len(decls) == 0 {
		return nil
	}
	return ch.Factory().NewSyntaxList(decls)
}

func (ch *objectRestSpreadTransformer) visitForOftatement(node *ast.ForInOrOfStatement) *ast.Node {
	if node.Initializer.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 || (ast.IsAssignmentPattern(node.Initializer) && ast.ContainsObjectRestOrSpread(node.Initializer)) {
		initializerWithoutParens := ast.SkipParentheses(node.Initializer)
		if ast.IsVariableDeclarationList(initializerWithoutParens) || ast.IsAssignmentPattern(initializerWithoutParens) {
			var bodyLocation core.TextRange
			var statementsLocation core.TextRange
			temp := ch.Factory().NewTempVariable()
			res := ch.Visitor().VisitNode(ch.createForOfBindingStatement(initializerWithoutParens, temp))
			statements := make([]*ast.Node, 0, 1)
			if res != nil {
				statements = append(statements, res)
			}
			if ast.IsBlock(node.Statement) {
				for _, statement := range node.Statement.AsBlock().Statements.Nodes {
					visited := ch.Visitor().VisitEachChild(statement)
					if visited != nil {
						statements = append(statements, visited)
					}
				}
				bodyLocation = node.Statement.Loc
				statementsLocation = node.Statement.AsBlock().Statements.Loc
			} else if node.Statement != nil {
				statements = append(statements, ch.Visitor().VisitEachChild(node.Statement))
				bodyLocation = node.Statement.Loc
				statementsLocation = node.Statement.Loc
			}

			list := ch.Factory().NewVariableDeclarationList(
				ast.NodeFlagsLet,
				ch.Factory().NewNodeList([]*ast.Node{ch.Factory().NewVariableDeclaration(temp, nil, nil, nil)}),
			)
			list.Loc = node.Initializer.Loc

			expr := ch.Visitor().VisitEachChild(node.Expression)

			statementsList := ch.Factory().NewNodeList(statements)
			statementsList.Loc = statementsLocation

			block := ch.Factory().NewBlock(statementsList, true)
			block.Loc = bodyLocation

			return ch.Factory().UpdateForInOrOfStatement(
				node,
				node.AwaitModifier,
				list,
				expr,
				block,
			)
		}
	}
	return ch.Visitor().VisitEachChild(node.AsNode())
}

func (ch *objectRestSpreadTransformer) createForOfBindingStatement(node *ast.Node, boundValue *ast.Node) *ast.Node {
	if ast.IsVariableDeclarationList(node) {
		firstDeclaration := node.AsVariableDeclarationList().Declarations.Nodes[0]
		updatedDeclaration := ch.Factory().UpdateVariableDeclaration(
			firstDeclaration.AsVariableDeclaration(),
			firstDeclaration.Name(),
			nil,
			nil,
			boundValue,
		)
		statement := ch.Factory().NewVariableStatement(
			nil,
			ch.Factory().UpdateVariableDeclarationList(
				node.AsVariableDeclarationList(),
				ch.Factory().NewNodeList([]*ast.Node{updatedDeclaration}),
			),
		)
		statement.Loc = node.Loc
		return statement
	} else {
		updatedExpression := ch.Factory().NewAssignmentExpression(node, boundValue)
		updatedExpression.Loc = node.Loc
		statement := ch.Factory().NewExpressionStatement(updatedExpression)
		statement.Loc = node.Loc
		return statement
	}
}

func (ch *objectRestSpreadTransformer) visitBinaryExpression(node *ast.BinaryExpression) *ast.Node {
	if !(ast.IsDestructuringAssignment(node.AsNode()) && ast.ContainsObjectRestOrSpread(node.Left)) {
		return ch.Visitor().VisitEachChild(node.AsNode())
	}
	return ch.flattenDestructuringAssignment(
		node,
	)
}

func (ch *objectRestSpreadTransformer) flattenDestructuringAssignment(node *ast.BinaryExpression) *ast.Node {
	location := node.Loc
	var value *ast.Node
	if ast.IsDestructuringAssignment(node.AsNode()) {
		value = node.Right
		for ast.IsEmptyArrayLiteral(node.Left) || ast.IsEmptyObjectLiteral(node.Left) {
			if ast.IsDestructuringAssignment(value) {
				node = value.AsBinaryExpression()
				location = node.Loc
				value = node.Right
			} else {
				return ch.Visitor().VisitNode(value)
			}
		}
	}
	old := ch.enterFlattenContext(flattenLevelObjectRest, (*objectRestSpreadTransformer).emitAssignment, (*objectRestSpreadTransformer).createArrayAssignmentPattern, (*objectRestSpreadTransformer).createObjectAssignmentPattern, (*objectRestSpreadTransformer).createArrayAssignmentElement, true)
	defer ch.exitFlattenContext(old)

	if value != nil {
		value = ch.Visitor().VisitNode(value)

		if ast.IsIdentifier(value) && bindingOrAssignmentElementAssignsToName(node.AsNode(), value.AsIdentifier().Text) || bindingOrAssignmentElementContainsNonLiteralComputedName(node.AsNode()) {
			// If the right-hand value of the assignment is also an assignment target then
			// we need to cache the right-hand value.
			value = ch.ensureIdentifier(value, false, location)
		} else {
			value = ch.ensureIdentifier(value, true, location)
		}

		if ast.NodeIsSynthesized(node.AsNode()) {
			// Generally, the source map location for a destructuring assignment is the root
			// expression.
			//
			// However, if the root expression is synthesized (as in the case
			// of the initializer when transforming a ForOfStatement), then the source map
			// location should point to the right-hand value of the expression.
			location = value.Loc
		}
	}

	ch.flattenBindingOrAssignmentElement(node.AsNode(), value, location, ast.IsDestructuringAssignment(node.AsNode()))

	res := ch.Factory().InlineExpressions(ch.ctx.currentExpressions)
	if res != nil {
		return res
	}
	return ch.Factory().NewOmittedExpression()
}

func (ch *objectRestSpreadTransformer) flattenBindingOrAssignmentElement(element *ast.Node, value *ast.Node, location core.TextRange, skipInitializer bool) {
	bindingTarget := ast.GetTargetOfBindingOrAssignmentElement(element)
	if !skipInitializer {
		initializer := ch.Visitor().VisitNode(getInitializerOfBindingOrAssignmentElement(element))
		if initializer != nil {
			// Combine value and initializer
			if value != nil {
				value = ch.createDefaultValueCheck(value, initializer, location)
				// If 'value' is not a simple expression, it could contain side-effecting code that should evaluate before an object or array binding pattern.
				if !transformers.IsSimpleCopiableExpression(initializer) && (ast.IsBindingPattern(bindingTarget) || ast.IsAssignmentPattern(bindingTarget)) {
					value = ch.ensureIdentifier(value, true, location)
				}
			} else {
				value = initializer
			}
		} else if value == nil {
			// Use 'void 0' in absence of value and initializer
			value = ch.Factory().NewVoidZeroExpression()
		}
	}

	if isObjectBindingOrAssignmentPattern(bindingTarget) {
		ch.flattenObjectBindingOrAssignmentPattern(element, bindingTarget, value, location)
	} else if isArrayBindingOrAssignmentPattern(bindingTarget) {
		ch.flattenArrayBindingOrAssignmentPattern(element, bindingTarget, value, location)
	} else {
		ch.ctx.emitBindingOrAssignment(ch, bindingTarget, value, location, element)
	}
}

func (ch *objectRestSpreadTransformer) flattenObjectBindingOrAssignmentPattern(parent *ast.Node, pattern *ast.Node, value *ast.Node, location core.TextRange) {
	elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern)
	numElements := len(elements)
	if numElements != 1 {
		// For anything other than a single-element destructuring we need to generate a temporary
		// to ensure value is evaluated exactly once. Additionally, if we have zero elements
		// we need to emit *something* to ensure that in case a 'var' keyword was already emitted,
		// so in that case, we'll intentionally create that temporary.
		reuseIdentifierExpressions := !ast.IsDeclarationBindingElement(parent) || numElements != 0
		value = ch.ensureIdentifier(value, reuseIdentifierExpressions, location)
	}
	var bindingElements []*ast.Node
	var computedTempVariables []*ast.Node
	for i, element := range elements {
		if ast.GetRestIndicatorOfBindingOrAssignmentElement(element) == nil {
			propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element)
			if ch.ctx.level >= flattenLevelObjectRest && element.SubtreeFacts()&(ast.SubtreeContainsRestOrSpread|ast.SubtreeContainsObjectRestOrSpread) == 0 && ast.GetTargetOfBindingOrAssignmentElement(element).SubtreeFacts()&(ast.SubtreeContainsRestOrSpread|ast.SubtreeContainsObjectRestOrSpread) == 0 && !ast.IsComputedPropertyName(propertyName) {
				bindingElements = append(bindingElements, ch.Visitor().VisitNode(element))
			} else {
				if len(bindingElements) > 0 {
					ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createObjectBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern)
					bindingElements = nil
				}
				rhsValue := ch.createDestructuringPropertyAccess(value, propertyName)
				if ast.IsComputedPropertyName(propertyName) {
					computedTempVariables = append(computedTempVariables, rhsValue.AsElementAccessExpression().ArgumentExpression)
				}
				ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false)
			}
		} else if i == numElements-1 {
			if len(bindingElements) > 0 {
				ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createObjectBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern)
				bindingElements = nil
			}
			rhsValue := ch.Factory().NewRestHelper(value, elements, computedTempVariables, pattern.Loc)
			ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false)
		}
	}
	if len(bindingElements) > 0 {
		ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createObjectBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern)
	}
}

type restIdElemPair struct {
	id      *ast.Node
	element *ast.Node
}

func (ch *objectRestSpreadTransformer) flattenArrayBindingOrAssignmentPattern(parent *ast.Node, pattern *ast.Node, value *ast.Node, location core.TextRange) {
	elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern)
	numElements := len(elements)
	if numElements != 1 && (ch.ctx.level < flattenLevelObjectRest || numElements == 0) || core.Every(elements, ast.IsOmittedExpression) {
		// For anything other than a single-element destructuring we need to generate a temporary
		// to ensure value is evaluated exactly once. Additionally, if we have zero elements
		// we need to emit *something* to ensure that in case a 'var' keyword was already emitted,
		// so in that case, we'll intentionally create that temporary.
		// Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]",
		// then we will create temporary variable.
		reuseIdentifierExpressions := !ast.IsDeclarationBindingElement(parent) || numElements != 0
		value = ch.ensureIdentifier(value, reuseIdentifierExpressions, location)
	}
	var bindingElements []*ast.Node
	var restContainingElements []restIdElemPair
	for i, element := range elements {
		if ch.ctx.level >= flattenLevelObjectRest {
			// If an array pattern contains an ObjectRest, we must cache the result so that we
			// can perform the ObjectRest destructuring in a different declaration
			if element.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 || ch.ctx.hasTransformedPriorElement && !isSimpleBindingOrAssignmentElement(element) {
				ch.ctx.hasTransformedPriorElement = true
				temp := ch.Factory().NewTempVariable()
				if ch.ctx.hoistTempVariables {
					ch.EmitContext().AddVariableDeclaration(temp)
				}

				restContainingElements = append(restContainingElements, restIdElemPair{temp, element})
				bindingElements = append(bindingElements, ch.ctx.createArrayBindingOrAssignmentElement(ch, temp))
			} else {
				bindingElements = append(bindingElements, element)
			}
		} else if ast.IsOmittedExpression(element) {
			continue
		} else if ast.GetRestIndicatorOfBindingOrAssignmentElement(element) == nil {
			rhsValue := ch.Factory().NewElementAccessExpression(value, nil, ch.Factory().NewNumericLiteral(strconv.Itoa(i)), ast.NodeFlagsNone)
			ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false)
		} else if i == numElements-1 {
			rhsValue := ch.Factory().NewArraySliceCall(value, i)
			ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false)
		}
	}
	if len(bindingElements) > 0 {
		ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createArrayBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern)
	}
	if len(restContainingElements) > 0 {
		for _, pair := range restContainingElements {
			ch.flattenBindingOrAssignmentElement(pair.element, pair.id, pair.element.Loc, false)
		}
	}
}

/**
 * Creates either a PropertyAccessExpression or an ElementAccessExpression for the
 * right-hand side of a transformed destructuring assignment.
 *
 * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation
 *
 * @param flattenContext Options used to control flattening.
 * @param value The RHS value that is the source of the property.
 * @param propertyName The destructuring property name.
 */
func (ch *objectRestSpreadTransformer) createDestructuringPropertyAccess(value *ast.Node, propertyName *ast.Node) *ast.Node {
	if ast.IsComputedPropertyName(propertyName) {
		argumentExpression := ch.ensureIdentifier(ch.Visitor().VisitNode(propertyName.AsComputedPropertyName().Expression), false, propertyName.Loc)
		return ch.Factory().NewElementAccessExpression(
			value,
			nil,
			argumentExpression,
			ast.NodeFlagsNone,
		)
	} else if ast.IsStringOrNumericLiteralLike(propertyName) || ast.IsBigIntLiteral(propertyName) {
		argumentExpression := propertyName.Clone(ch.Factory())
		return ch.Factory().NewElementAccessExpression(
			value,
			nil,
			argumentExpression,
			ast.NodeFlagsNone,
		)
	} else {
		name := ch.Factory().NewIdentifier(propertyName.AsIdentifier().Text)
		return ch.Factory().NewPropertyAccessExpression(
			value,
			nil,
			name,
			ast.NodeFlagsNone,
		)
	}
}

func (ch *objectRestSpreadTransformer) createObjectBindingPattern(elements []*ast.Node) *ast.Node {
	return ch.Factory().NewBindingPattern(ast.KindObjectBindingPattern, ch.Factory().NewNodeList(elements))
}

func (ch *objectRestSpreadTransformer) createArrayBindingPattern(elements []*ast.Node) *ast.Node {
	return ch.Factory().NewBindingPattern(ast.KindArrayBindingPattern, ch.Factory().NewNodeList(elements))
}

func (ch *objectRestSpreadTransformer) createObjectAssignmentPattern(elements []*ast.Node) *ast.Node {
	return ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(elements), false)
}

func (ch *objectRestSpreadTransformer) createArrayAssignmentPattern(elements []*ast.Node) *ast.Node {
	return ch.Factory().NewArrayLiteralExpression(ch.Factory().NewNodeList(elements), false)
}

func (ch *objectRestSpreadTransformer) createArrayAssignmentElement(expr *ast.Node) *ast.Node {
	return expr
}

func (ch *objectRestSpreadTransformer) createArrayBindingElement(expr *ast.Node) *ast.Node {
	return ch.Factory().NewBindingElement(nil, nil, expr, nil)
}

func (ch *objectRestSpreadTransformer) emitExpression(node *ast.Node) {
	ch.ctx.currentExpressions = append(ch.ctx.currentExpressions, node)
}

func (ch *objectRestSpreadTransformer) emitAssignment(target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node) {
	debug.AssertNode(target, ast.IsExpression)
	expr := ch.Factory().NewAssignmentExpression(ch.Visitor().VisitNode(target), value)
	expr.Loc = location
	ch.EmitContext().SetOriginal(expr, original)
	ch.emitExpression(expr)
}

func isBindingName(node *ast.Node) bool {
	return node.Kind == ast.KindIdentifier || node.Kind == ast.KindArrayBindingPattern || node.Kind == ast.KindObjectBindingPattern
}

func (ch *objectRestSpreadTransformer) emitBinding(target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node) {
	debug.AssertNode(target, isBindingName)
	if len(ch.ctx.currentExpressions) > 0 {
		value = ch.Factory().InlineExpressions(append(ch.ctx.currentExpressions, value))
		ch.ctx.currentExpressions = nil
	}
	ch.ctx.currentDeclarations = append(ch.ctx.currentDeclarations, pendingDecl{
		ch.ctx.currentExpressions,
		target,
		value,
		location,
		original,
	})
}

func (ch *objectRestSpreadTransformer) ensureIdentifier(value *ast.Node, reuseIdentifierExpressions bool, location core.TextRange) *ast.Node {
	if reuseIdentifierExpressions && ast.IsIdentifier(value) {
		return value
	}

	temp := ch.Factory().NewTempVariable()
	if ch.ctx.hoistTempVariables {
		ch.EmitContext().AddVariableDeclaration(temp)
		assign := ch.Factory().NewAssignmentExpression(temp, value)
		assign.Loc = location
		ch.emitExpression(assign)
	} else {
		ch.ctx.emitBindingOrAssignment(ch, temp, value, location, nil)
	}
	return temp
}

func (ch *objectRestSpreadTransformer) createDefaultValueCheck(value *ast.Expression, defaultValue *ast.Expression, location core.TextRange) *ast.Node {
	value = ch.ensureIdentifier(value, true, location)
	return ch.Factory().NewConditionalExpression(
		ch.Factory().NewTypeCheck(value, "undefined"),
		ch.Factory().NewToken(ast.KindQuestionToken),
		defaultValue,
		ch.Factory().NewToken(ast.KindColonToken),
		value,
	)
}

func (ch *objectRestSpreadTransformer) visitObjectLiteralExpression(node *ast.ObjectLiteralExpression) *ast.Node {
	if (node.SubtreeFacts() & ast.SubtreeContainsObjectRestOrSpread) == 0 {
		return ch.Visitor().VisitEachChild(node.AsNode())
	}
	// spread elements emit like so:
	// non-spread elements are chunked together into object literals, and then all are passed to __assign:
	//     { a, ...o, b } => __assign(__assign({a}, o), {b});
	// If the first element is a spread element, then the first argument to __assign is {}:
	//     { ...o, a, b, ...o2 } => __assign(__assign(__assign({}, o), {a, b}), o2)
	//
	// We cannot call __assign with more than two elements, since any element could cause side effects. For
	// example:
	//      var k = { a: 1, b: 2 };
	//      var o = { a: 3, ...k, b: k.a++ };
	//      // expected: { a: 1, b: 1 }
	// If we translate the above to `__assign({ a: 3 }, k, { b: k.a++ })`, the `k.a++` will evaluate before
	// `k` is spread and we end up with `{ a: 2, b: 1 }`.
	//
	// This also occurs for spread elements, not just property assignments:
	//      var k = { a: 1, get b() { l = { z: 9 }; return 2; } };
	//      var l = { c: 3 };
	//      var o = { ...k, ...l };
	//      // expected: { a: 1, b: 2, z: 9 }
	// If we translate the above to `__assign({}, k, l)`, the `l` will evaluate before `k` is spread and we
	// end up with `{ a: 1, b: 2, c: 3 }`

	objects := ch.chunkObjectLiteralElements(node.Properties)
	if len(objects) > 0 && objects[0].Kind != ast.KindObjectLiteralExpression {
		objects = append([]*ast.Node{ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(nil), false)}, objects...)
	}
	expression := objects[0]
	if len(objects) > 1 {
		for i, obj := range objects {
			if i == 0 {
				continue
			}
			expression = ch.Factory().NewAssignHelper([]*ast.Node{expression, obj}, ch.compilerOptions.GetEmitScriptTarget())
		}
		return expression
	}
	return ch.Factory().NewAssignHelper(objects, ch.compilerOptions.GetEmitScriptTarget())
}

func (ch *objectRestSpreadTransformer) chunkObjectLiteralElements(list *ast.NodeList) []*ast.Node {
	if list == nil || len(list.Nodes) == 0 {
		return nil
	}
	elements := list.Nodes
	var chunkObject []*ast.Node
	objects := make([]*ast.Node, 0, 1)
	for _, e := range elements {
		if e.Kind == ast.KindSpreadAssignment {
			if len(chunkObject) > 0 {
				objects = append(objects, ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(chunkObject), false))
				chunkObject = nil
			}
			target := e.Expression()
			objects = append(objects, ch.Visitor().VisitNode(target))
		} else {
			var elem *ast.Node
			if e.Kind == ast.KindPropertyAssignment {
				elem = ch.Factory().NewPropertyAssignment(nil, e.Name(), nil, nil, ch.Visitor().VisitNode(e.Initializer()))
			} else {
				elem = ch.Visitor().VisitNode(e)
			}
			chunkObject = append(chunkObject, elem)
		}
	}
	if len(chunkObject) > 0 {
		objects = append(objects, ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(chunkObject), false))
	}
	return objects
}

func newObjectRestSpreadTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
	tx := &objectRestSpreadTransformer{compilerOptions: opts.CompilerOptions}
	return tx.NewTransformer(tx.visit, opts.Context)
}

func bindingOrAssignmentElementAssignsToName(element *ast.Node, name string) bool {
	target := ast.GetTargetOfBindingOrAssignmentElement(element)
	if target == nil {
		return false
	}
	if ast.IsBindingPattern(target) || ast.IsAssignmentPattern(target) {
		return bindingOrAssignmentPatternAssignsToName(target, name)
	} else if ast.IsIdentifier(target) {
		return target.AsIdentifier().Text == name
	}
	return false
}

func bindingOrAssignmentPatternAssignsToName(pattern *ast.Node, name string) bool {
	elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern)
	for _, element := range elements {
		if bindingOrAssignmentElementAssignsToName(element, name) {
			return true
		}
	}
	return false
}

func bindingOrAssignmentElementContainsNonLiteralComputedName(element *ast.Node) bool {
	propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element)
	if propertyName != nil && ast.IsComputedPropertyName(propertyName) && !ast.IsLiteralExpression(propertyName.AsComputedPropertyName().Expression) {
		return true
	}
	target := ast.GetTargetOfBindingOrAssignmentElement(element)
	return target != nil && (ast.IsBindingPattern(target) || ast.IsAssignmentPattern(target)) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target)
}

func bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern *ast.Node) bool {
	elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern)
	for _, element := range elements {
		if bindingOrAssignmentElementContainsNonLiteralComputedName(element) {
			return true
		}
	}
	return false
}

func getInitializerOfBindingOrAssignmentElement(bindingElement *ast.Node) *ast.Node {
	if ast.IsDeclarationBindingElement(bindingElement) {
		// `1` in `let { a = 1 } = ...`
		// `1` in `let { a: b = 1 } = ...`
		// `1` in `let { a: {b} = 1 } = ...`
		// `1` in `let { a: [b] = 1 } = ...`
		// `1` in `let [a = 1] = ...`
		// `1` in `let [{a} = 1] = ...`
		// `1` in `let [[a] = 1] = ...`
		return bindingElement.Initializer()
	}

	if ast.IsPropertyAssignment(bindingElement) {
		// `1` in `({ a: b = 1 } = ...)`
		// `1` in `({ a: {b} = 1 } = ...)`
		// `1` in `({ a: [b] = 1 } = ...)`
		initializer := bindingElement.Initializer()
		if ast.IsAssignmentExpression(initializer, true) {
			return initializer.AsBinaryExpression().Right
		}
		return nil
	}

	if ast.IsShorthandPropertyAssignment(bindingElement) {
		// `1` in `({ a = 1 } = ...)`
		return bindingElement.AsShorthandPropertyAssignment().ObjectAssignmentInitializer
	}

	if ast.IsAssignmentExpression(bindingElement, true) {
		// `1` in `[a = 1] = ...`
		// `1` in `[{a} = 1] = ...`
		// `1` in `[[a] = 1] = ...`
		return bindingElement.AsBinaryExpression().Right
	}

	if ast.IsSpreadElement(bindingElement) {
		// Recovery consistent with existing emit.
		return getInitializerOfBindingOrAssignmentElement(bindingElement.Expression())
	}
	return nil
}

func isObjectBindingOrAssignmentPattern(node *ast.Node) bool {
	return node.Kind == ast.KindObjectBindingPattern || node.Kind == ast.KindObjectLiteralExpression
}

func isArrayBindingOrAssignmentPattern(node *ast.Node) bool {
	return node.Kind == ast.KindArrayBindingPattern || node.Kind == ast.KindArrayLiteralExpression
}

func isSimpleBindingOrAssignmentElement(element *ast.Node) bool {
	target := ast.GetTargetOfBindingOrAssignmentElement(element)
	if target == nil || ast.IsOmittedExpression(target) {
		return true
	}
	propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element)
	if propertyName != nil && !ast.IsPropertyNameLiteral(propertyName) {
		return false
	}
	initializer := getInitializerOfBindingOrAssignmentElement(element)
	if initializer != nil && !transformers.IsSimpleInlineableExpression(initializer) {
		return false
	}
	if ast.IsBindingPattern(target) || ast.IsAssignmentPattern(target) {
		return core.Every(ast.GetElementsOfBindingOrAssignmentPattern(target), isSimpleBindingOrAssignmentElement)
	}
	return ast.IsIdentifier(target)
}
