package estransforms

import (
	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/collections"
	"github.com/microsoft/typescript-go/internal/printer"
	"github.com/microsoft/typescript-go/internal/transformers"
)

func convertClassDeclarationToClassExpression(emitContext *printer.EmitContext, node *ast.ClassDeclaration) *ast.Expression {
	updated := emitContext.Factory.NewClassExpression(
		transformers.ExtractModifiers(emitContext, node.Modifiers(), ^ast.ModifierFlagsExportDefault),
		node.Name(),
		node.TypeParameters,
		node.HeritageClauses,
		node.Members,
	)
	emitContext.SetOriginal(updated, node.AsNode())
	updated.Loc = node.Loc
	return updated
}

func createNotNullCondition(emitContext *printer.EmitContext, left *ast.Node, right *ast.Node, invert bool) *ast.Node {
	token := ast.KindExclamationEqualsEqualsToken
	op := ast.KindAmpersandAmpersandToken
	if invert {
		token = ast.KindEqualsEqualsEqualsToken
		op = ast.KindBarBarToken
	}

	return emitContext.Factory.NewBinaryExpression(
		nil,
		emitContext.Factory.NewBinaryExpression(
			nil,
			left,
			nil,
			emitContext.Factory.NewToken(token),
			emitContext.Factory.NewKeywordExpression(ast.KindNullKeyword),
		),
		nil,
		emitContext.Factory.NewToken(op),
		emitContext.Factory.NewBinaryExpression(
			nil,
			right,
			nil,
			emitContext.Factory.NewToken(token),
			emitContext.Factory.NewVoidZeroExpression(),
		),
	)
}

// superAccessState tracks super property/element accesses and super property assignments
// within async function or async generator bodies. It is embedded by both asyncTransformer
// and forawaitTransformer to share the tracking logic.
type superAccessState struct {
	factory *printer.NodeFactory

	// Keeps track of property names accessed on super (`super.x`) within async functions.
	capturedSuperProperties *collections.OrderedSet[string]
	// Whether the async function contains an element access on super (`super[x]`).
	hasSuperElementAccess      bool
	hasSuperPropertyAssignment bool

	superBinding       *ast.IdentifierNode
	superIndexBinding  *ast.IdentifierNode
	superAccessVisitor *ast.NodeVisitor
}

func (s *superAccessState) initSuperAccessVisitor(emitContext *printer.EmitContext, factory *printer.NodeFactory) {
	s.factory = factory
	s.superAccessVisitor = emitContext.NewNodeVisitor(s.visitSuperAccessNode)
}

// visitSuperAccessNode walks the async/generator body and replaces super property/element
// accesses with _super/_superIndex references. This is necessary because the async body
// ends up inside a generator function where `super` is not valid.
func (s *superAccessState) visitSuperAccessNode(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindCallExpression:
		call := node.AsCallExpression()
		if ast.IsSuperProperty(call.Expression) {
			return s.substituteCallExpressionWithSuperAccess(call, s.superAccessVisitor)
		}
		return s.superAccessVisitor.VisitEachChild(node)
	case ast.KindPropertyAccessExpression:
		if node.Expression().Kind == ast.KindSuperKeyword {
			// super.x → _super.x
			return s.factory.NewPropertyAccessExpression(
				s.superBinding, nil, node.Name(), ast.NodeFlagsNone,
			)
		}
		return s.superAccessVisitor.VisitEachChild(node)
	case ast.KindElementAccessExpression:
		if node.Expression().Kind == ast.KindSuperKeyword {
			// super[x] → _superIndex(x) or _superIndex(x).value
			return s.createSuperElementAccessInAsyncMethod(
				node.AsElementAccessExpression().ArgumentExpression,
			)
		}
		return s.superAccessVisitor.VisitEachChild(node)
	// Don't recurse into non-arrow function scopes or classes
	case ast.KindFunctionExpression, ast.KindFunctionDeclaration,
		ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor,
		ast.KindConstructor, ast.KindClassDeclaration, ast.KindClassExpression:
		return node
	default:
		return s.superAccessVisitor.VisitEachChild(node)
	}
}

func (s *superAccessState) substituteSuperAccessesInBody(body *ast.Node) *ast.Node {
	return s.superAccessVisitor.VisitNode(body)
}

// substituteCallExpressionWithSuperAccess handles super.x(args) and super[x](args).
func (s *superAccessState) substituteCallExpressionWithSuperAccess(call *ast.CallExpression, visitor *ast.NodeVisitor) *ast.Node {
	expression := call.Expression
	var target *ast.Node

	if ast.IsPropertyAccessExpression(expression) {
		// super.x(args) → _super.x.call(this, args)
		target = s.factory.NewPropertyAccessExpression(
			s.superBinding, nil,
			expression.AsPropertyAccessExpression().Name(), ast.NodeFlagsNone,
		)
	} else if ast.IsElementAccessExpression(expression) {
		// super[x](args) → _superIndex(x).call(this, args) or _superIndex(x).value.call(this, args)
		target = s.createSuperElementAccessInAsyncMethod(
			expression.AsElementAccessExpression().ArgumentExpression,
		)
	} else {
		return visitor.VisitEachChild(call.AsNode())
	}

	callTarget := s.factory.NewPropertyAccessExpression(
		target, nil,
		s.factory.NewIdentifier("call"), ast.NodeFlagsNone,
	)

	var allArgs []*ast.Node
	allArgs = append(allArgs, s.factory.NewThisExpression())
	if call.Arguments != nil {
		visitedArgs := visitor.VisitNodes(call.Arguments)
		if visitedArgs != nil {
			allArgs = append(allArgs, visitedArgs.Nodes...)
		}
	}

	result := s.factory.NewCallExpression(
		callTarget, nil, nil,
		s.factory.NewNodeList(allArgs), ast.NodeFlagsNone,
	)
	result.Loc = call.Loc
	return result
}

// createSuperElementAccessInAsyncMethod creates _superIndex(x) or _superIndex(x).value.
func (s *superAccessState) createSuperElementAccessInAsyncMethod(argumentExpression *ast.Node) *ast.Node {
	superIndexCall := s.factory.NewCallExpression(
		s.superIndexBinding, nil, nil,
		s.factory.NewNodeList([]*ast.Node{argumentExpression}),
		ast.NodeFlagsNone,
	)
	if s.hasSuperPropertyAssignment {
		return s.factory.NewPropertyAccessExpression(
			superIndexCall, nil,
			s.factory.NewIdentifier("value"), ast.NodeFlagsNone,
		)
	}
	return superIndexCall
}

// createSuperAccessVariableStatement creates a variable named `_super` with accessor
// properties for the given property names.
//
// Create a variable declaration with a getter/setter (if binding) definition for each name:
//
//	const _super = Object.create(null, {
//	    x: { get: () => super.x },                           // read-only
//	    x: { get: () => super.x, set: (v) => super.x = v }, // read-write
//	});
func (s *superAccessState) createSuperAccessVariableStatement() *ast.Node {
	f := s.factory
	var accessors []*ast.Node

	for name := range s.capturedSuperProperties.Values() {
		var descriptorProperties []*ast.Node

		// getter: get: () => super.name
		getterBody := f.NewPropertyAccessExpression(
			f.NewKeywordExpression(ast.KindSuperKeyword), nil,
			f.NewIdentifier(name), ast.NodeFlagsNone,
		)
		getterArrow := f.NewArrowFunction(
			nil, nil,
			f.NewNodeList([]*ast.Node{}),
			nil, nil,
			f.NewToken(ast.KindEqualsGreaterThanToken),
			getterBody,
		)
		getter := f.NewPropertyAssignment(nil, f.NewIdentifier("get"), nil, nil, getterArrow)
		descriptorProperties = append(descriptorProperties, getter)

		if s.hasSuperPropertyAssignment {
			// setter: set: v => super.name = v
			vParam := f.NewParameterDeclaration(nil, nil, f.NewIdentifier("v"), nil, nil, nil)
			superProp := f.NewPropertyAccessExpression(
				f.NewKeywordExpression(ast.KindSuperKeyword), nil,
				f.NewIdentifier(name), ast.NodeFlagsNone,
			)
			assignExpr := f.NewAssignmentExpression(superProp, f.NewIdentifier("v"))
			setterArrow := f.NewArrowFunction(
				nil, nil,
				f.NewNodeList([]*ast.Node{vParam}),
				nil, nil,
				f.NewToken(ast.KindEqualsGreaterThanToken),
				assignExpr,
			)
			setter := f.NewPropertyAssignment(nil, f.NewIdentifier("set"), nil, nil, setterArrow)
			descriptorProperties = append(descriptorProperties, setter)
		}

		descriptor := f.NewObjectLiteralExpression(f.NewNodeList(descriptorProperties), false)
		accessor := f.NewPropertyAssignment(nil, f.NewIdentifier(name), nil, nil, descriptor)
		accessors = append(accessors, accessor)
	}

	descriptorsObject := f.NewObjectLiteralExpression(f.NewNodeList(accessors), true)

	objectCreateCall := f.NewCallExpression(
		f.NewPropertyAccessExpression(
			f.NewIdentifier("Object"), nil,
			f.NewIdentifier("create"), ast.NodeFlagsNone,
		), nil, nil,
		f.NewNodeList([]*ast.Node{
			f.NewKeywordExpression(ast.KindNullKeyword),
			descriptorsObject,
		}),
		ast.NodeFlagsNone,
	)

	decl := f.NewVariableDeclaration(s.superBinding, nil, nil, objectCreateCall)
	declList := f.NewVariableDeclarationList(f.NewNodeList([]*ast.Node{decl}), ast.NodeFlagsConst)
	return f.NewVariableStatement(nil, declList)
}

// trackSuperAccess records super property/element accesses and super property assignments
// for the enclosing async method body. Called from both the main visitor and auxiliary
// visitors to ensure super accesses are tracked regardless of whether the node has
// transform flags.
func (s *superAccessState) trackSuperAccess(node *ast.Node) {
	if s.capturedSuperProperties == nil {
		return
	}
	switch node.Kind {
	case ast.KindPropertyAccessExpression:
		if node.Expression().Kind == ast.KindSuperKeyword {
			s.capturedSuperProperties.Add(node.Name().Text())
		}
	case ast.KindElementAccessExpression:
		if node.Expression().Kind == ast.KindSuperKeyword {
			s.hasSuperElementAccess = true
		}
	case ast.KindBinaryExpression:
		if ast.IsAssignmentOperator(node.AsBinaryExpression().OperatorToken.Kind) && assignmentTargetContainsSuperProperty(node.AsBinaryExpression().Left) {
			s.hasSuperPropertyAssignment = true
		}
	case ast.KindPrefixUnaryExpression:
		if isUpdateExpression(node) && assignmentTargetContainsSuperProperty(node.AsPrefixUnaryExpression().Operand) {
			s.hasSuperPropertyAssignment = true
		}
	case ast.KindPostfixUnaryExpression:
		if isUpdateExpression(node) && assignmentTargetContainsSuperProperty(node.AsPostfixUnaryExpression().Operand) {
			s.hasSuperPropertyAssignment = true
		}
	}
}

// createAccessorPropertyBackingField creates a private backing field for an `accessor` PropertyDeclaration.
func createAccessorPropertyBackingField(f *printer.NodeFactory, node *ast.PropertyDeclaration, modifiers *ast.ModifierList, initializer *ast.Expression) *ast.Node {
	return f.UpdatePropertyDeclaration(
		node,
		modifiers,
		f.NewGeneratedPrivateNameForNodeEx(node.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"}),
		nil, /*postfixToken*/
		nil, /*typeNode*/
		initializer,
	)
}
