package goja

import (
	
	
)

type PromiseState int
type PromiseRejectionOperation int

type promiseReactionType int

const (
	PromiseStatePending PromiseState = iota
	PromiseStateFulfilled
	PromiseStateRejected
)

const (
	PromiseRejectionReject PromiseRejectionOperation = iota
	PromiseRejectionHandle
)

const (
	promiseReactionFulfill promiseReactionType = iota
	promiseReactionReject
)

type PromiseRejectionTracker func(p *Promise, operation PromiseRejectionOperation)

type jobCallback struct {
	callback func(FunctionCall) Value
}

type promiseCapability struct {
	promise               *Object
	resolveObj, rejectObj *Object
}

type promiseReaction struct {
	capability  *promiseCapability
	typ         promiseReactionType
	handler     *jobCallback
	asyncRunner *asyncRunner
	asyncCtx    interface{}
}

var typePromise = reflect.TypeOf((*Promise)(nil))

// Promise is a Go wrapper around ECMAScript Promise. Calling Runtime.ToValue() on it
// returns the underlying Object. Calling Export() on a Promise Object returns a Promise.
//
// Use Runtime.NewPromise() to create one. Calling Runtime.ToValue() on a zero object or nil returns null Value.
//
// WARNING: Instances of Promise are not goroutine-safe. See Runtime.NewPromise() for more details.
type Promise struct {
	baseObject
	state            PromiseState
	result           Value
	fulfillReactions []*promiseReaction
	rejectReactions  []*promiseReaction
	handled          bool
}

func ( *Promise) () PromiseState {
	return .state
}

func ( *Promise) () Value {
	return .result
}

func ( *Promise) ( *Runtime) Value {
	if  == nil || .val == nil {
		return _null
	}
	 := .val
	if .runtime !=  {
		panic(.NewTypeError("Illegal runtime transition of a Promise"))
	}
	return 
}

func ( *Promise) () (,  *Object) {
	 := .val.runtime
	 := false
	return .val.runtime.newNativeFunc(func( FunctionCall) Value {
			if  {
				return _undefined
			}
			 = true
			 := .Argument(0)
			if .SameAs(.val) {
				return .reject(.NewTypeError("Promise self-resolution"))
			}
			if ,  := .(*Object);  {
				var  Value
				 := .vm.try(func() {
					 = .self.getStr("then", nil)
				})
				if  != nil {
					return .reject(.val)
				}
				if ,  := assertCallable();  {
					 := .newPromiseResolveThenableJob(, , &jobCallback{callback: })
					.enqueuePromiseJob()
					return _undefined
				}
			}
			return .fulfill()
		}, "", 1),
		.val.runtime.newNativeFunc(func( FunctionCall) Value {
			if  {
				return _undefined
			}
			 = true
			 := .Argument(0)
			return .reject()
		}, "", 1)
}

func ( *Promise) ( Value) Value {
	 := .rejectReactions
	.result = 
	.fulfillReactions, .rejectReactions = nil, nil
	.state = PromiseStateRejected
	 := .val.runtime
	if !.handled {
		.trackPromiseRejection(, PromiseRejectionReject)
	}
	.triggerPromiseReactions(, )
	return _undefined
}

func ( *Promise) ( Value) Value {
	 := .fulfillReactions
	.result = 
	.fulfillReactions, .rejectReactions = nil, nil
	.state = PromiseStateFulfilled
	.val.runtime.triggerPromiseReactions(, )
	return _undefined
}

func ( *Promise) () reflect.Type {
	return typePromise
}

func ( *Promise) (*objectExportCtx) interface{} {
	return 
}

func ( *Promise) ( *promiseReaction,  *promiseReaction) {
	 := .val.runtime
	if  := .asyncContextTracker;  != nil {
		 := .Grab()
		.asyncCtx = 
		.asyncCtx = 
	}
	switch .state {
	case PromiseStatePending:
		.fulfillReactions = append(.fulfillReactions, )
		.rejectReactions = append(.rejectReactions, )
	case PromiseStateFulfilled:
		.enqueuePromiseJob(.newPromiseReactionJob(, .result))
	default:
		 := .result
		if !.handled {
			.trackPromiseRejection(, PromiseRejectionHandle)
		}
		.enqueuePromiseJob(.newPromiseReactionJob(, ))
	}
	.handled = true
}

func ( *Runtime) ( *Promise,  Value,  *jobCallback) func() {
	return func() {
		,  := .createResolvingFunctions()
		 := .vm.try(func() {
			.callJobCallback(, , , )
		})
		if  != nil {
			if ,  := .self.assertCallable();  {
				(FunctionCall{Arguments: []Value{.val}})
			}
		}
	}
}

func ( *Runtime) ( func()) {
	.jobQueue = append(.jobQueue, )
}

func ( *Runtime) ( []*promiseReaction,  Value) {
	for ,  := range  {
		.enqueuePromiseJob(.newPromiseReactionJob(, ))
	}
}

func ( *Runtime) ( *promiseReaction,  Value) func() {
	return func() {
		var  Value
		 := false
		if .handler == nil {
			 = 
			if .typ == promiseReactionFulfill {
				 = true
			}
		} else {
			if  := .asyncContextTracker;  != nil {
				.Resumed(.asyncCtx)
			}
			 := .vm.try(func() {
				 = .callJobCallback(.handler, _undefined, )
				 = true
			})
			if  != nil {
				 = .val
			}
			if  := .asyncContextTracker;  != nil {
				.Exited()
			}
		}
		if .capability != nil {
			if  {
				.capability.resolve()
			} else {
				.capability.reject()
			}
		}
	}
}

func ( *Runtime) ( *Object) *Promise {
	 := &Object{runtime: }

	 := &Promise{}
	.class = classObject
	.val = 
	.extensible = true
	.self = 
	.prototype = 
	.init()
	return 
}

func ( *Runtime) ( []Value,  *Object) *Object {
	if  == nil {
		panic(.needNew("Promise"))
	}
	var  Value
	if len() > 0 {
		 = [0]
	}
	 := .toCallable()

	 := .getPrototypeFromCtor(, .global.Promise, .getPromisePrototype())
	 := .newPromise()

	,  := .createResolvingFunctions()
	 := .vm.try(func() {
		(FunctionCall{Arguments: []Value{, }})
	})
	if  != nil {
		if ,  := .self.assertCallable();  {
			(FunctionCall{Arguments: []Value{.val}})
		}
	}
	return .val
}

func ( *Runtime) ( FunctionCall) Value {
	 := .toObject(.This)
	if ,  := .self.(*Promise);  {
		 := .speciesConstructorObj(, .getPromise())
		 := .newPromiseCapability()
		return .performPromiseThen(, .Argument(0), .Argument(1), )
	}
	panic(.NewTypeError("Method Promise.prototype.then called on incompatible receiver %s", .objectproto_toString(FunctionCall{This: })))
}

func ( *Runtime) ( *Object) *promiseCapability {
	 := new(promiseCapability)
	if  == .getPromise() {
		 := .newPromise(.getPromisePrototype())
		.resolveObj, .rejectObj = .createResolvingFunctions()
		.promise = .val
	} else {
		var ,  Value
		 := .newNativeFunc(func( FunctionCall) Value {
			if  != nil {
				panic(.NewTypeError("resolve is already set"))
			}
			if  != nil {
				panic(.NewTypeError("reject is already set"))
			}
			if  := .Argument(0);  != _undefined {
				 = 
			}
			if  := .Argument(1);  != _undefined {
				 = 
			}
			return nil
		}, "", 2)
		.promise = .toConstructor()([]Value{}, )
		.resolveObj = .toObject()
		.toCallable(.resolveObj) // make sure it's callable
		.rejectObj = .toObject()
		.toCallable(.rejectObj)
	}
	return 
}

func ( *Runtime) ( *Promise, ,  Value,  *promiseCapability) Value {
	var ,  *jobCallback
	if ,  := assertCallable();  {
		 = &jobCallback{callback: }
	}
	if ,  := assertCallable();  {
		 = &jobCallback{callback: }
	}
	 := &promiseReaction{
		capability: ,
		typ:        promiseReactionFulfill,
		handler:    ,
	}
	 := &promiseReaction{
		capability: ,
		typ:        promiseReactionReject,
		handler:    ,
	}
	.addReactions(, )
	if  == nil {
		return _undefined
	}
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	return .invoke(.This, "then", _undefined, .Argument(0))
}

func ( *Runtime) ( *Object,  Value) *Object {
	if ,  := .(*Object);  {
		 := nilSafe(.self.getStr("constructor", nil))
		if .SameAs() {
			return 
		}
	}
	 := .newPromiseCapability()
	.resolve()
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	 := .toObject(.This)
	 := .speciesConstructorObj(, .getPromise())
	 := .Argument(0)
	var ,  Value
	if ,  := assertCallable(); ! {
		,  = , 
	} else {
		 = .newNativeFunc(func( FunctionCall) Value {
			 := .Argument(0)
			 := (FunctionCall{})
			 := .promiseResolve(, )
			 := .newNativeFunc(func( FunctionCall) Value {
				return 
			}, "", 0)
			return .invoke(, "then", )
		}, "", 1)

		 = .newNativeFunc(func( FunctionCall) Value {
			 := .Argument(0)
			 := (FunctionCall{})
			 := .promiseResolve(, )
			 := .newNativeFunc(func( FunctionCall) Value {
				panic()
			}, "", 0)
			return .invoke(, "then", )
		}, "", 1)
	}
	return .invoke(, "then", , )
}

func ( *promiseCapability) ( Value) {
	.promise.runtime.toCallable(.resolveObj)(FunctionCall{Arguments: []Value{}})
}

func ( *promiseCapability) ( Value) {
	.promise.runtime.toCallable(.rejectObj)(FunctionCall{Arguments: []Value{}})
}

func ( *promiseCapability) ( func()) bool {
	 := .promise.runtime.vm.try()
	if  != nil {
		.reject(.val)
		return false
	}
	return true
}

func ( *Runtime) ( FunctionCall) Value {
	 := .toObject(.This)
	 := .newPromiseCapability()

	.try(func() {
		 := .toCallable(.self.getStr("resolve", nil))
		 := .getIterator(.Argument(0), nil)
		var  []Value
		 := 1
		.iterate(func( Value) {
			 := len()
			 = append(, _undefined)
			 := (FunctionCall{This: , Arguments: []Value{}})
			 := false
			 := .newNativeFunc(func( FunctionCall) Value {
				if  {
					return _undefined
				}
				 = true
				[] = .Argument(0)
				--
				if  == 0 {
					.resolve(.newArrayValues())
				}
				return _undefined
			}, "", 1)
			++
			.invoke(, "then", , .rejectObj)
		})
		--
		if  == 0 {
			.resolve(.newArrayValues())
		}
	})
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	 := .toObject(.This)
	 := .newPromiseCapability()

	.try(func() {
		 := .toCallable(.self.getStr("resolve", nil))
		 := .getIterator(.Argument(0), nil)
		var  []Value
		 := 1
		.iterate(func( Value) {
			 := len()
			 = append(, _undefined)
			 := (FunctionCall{This: , Arguments: []Value{}})
			 := false
			 := func( Value,  unistring.String) *Object {
				return .newNativeFunc(func( FunctionCall) Value {
					if  {
						return _undefined
					}
					 = true
					 := .NewObject()
					.self._putProp("status", , true, true, true)
					.self._putProp(, .Argument(0), true, true, true)
					[] = 
					--
					if  == 0 {
						.resolve(.newArrayValues())
					}
					return _undefined
				}, "", 1)
			}
			 := (asciiString("fulfilled"), "value")
			 := (asciiString("rejected"), "reason")
			++
			.invoke(, "then", , )
		})
		--
		if  == 0 {
			.resolve(.newArrayValues())
		}
	})
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	 := .toObject(.This)
	 := .newPromiseCapability()

	.try(func() {
		 := .toCallable(.self.getStr("resolve", nil))
		 := .getIterator(.Argument(0), nil)
		var  []Value
		 := 1
		.iterate(func( Value) {
			 := len()
			 = append(, _undefined)
			 := (FunctionCall{This: , Arguments: []Value{}})
			 := false
			 := .newNativeFunc(func( FunctionCall) Value {
				if  {
					return _undefined
				}
				 = true
				[] = .Argument(0)
				--
				if  == 0 {
					 := .builtin_new(.getAggregateError(), nil)
					.self._putProp("errors", .newArrayValues(), true, false, true)
					.reject()
				}
				return _undefined
			}, "", 1)

			++
			.invoke(, "then", .resolveObj, )
		})
		--
		if  == 0 {
			 := .builtin_new(.getAggregateError(), nil)
			.self._putProp("errors", .newArrayValues(), true, false, true)
			.reject()
		}
	})
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	 := .toObject(.This)
	 := .newPromiseCapability()

	.try(func() {
		 := .toCallable(.self.getStr("resolve", nil))
		 := .getIterator(.Argument(0), nil)
		.iterate(func( Value) {
			 := (FunctionCall{This: , Arguments: []Value{}})
			.invoke(, "then", .resolveObj, .rejectObj)
		})
	})
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	 := .newPromiseCapability(.toObject(.This))
	.reject(.Argument(0))
	return .promise
}

func ( *Runtime) ( FunctionCall) Value {
	return .promiseResolve(.toObject(.This), .Argument(0))
}

func ( *Runtime) ( *Object) objectImpl {
	 := newBaseObjectObj(, .global.ObjectPrototype, classObject)
	._putProp("constructor", .getPromise(), true, false, true)

	._putProp("catch", .newNativeFunc(.promiseProto_catch, "catch", 1), true, false, true)
	._putProp("finally", .newNativeFunc(.promiseProto_finally, "finally", 1), true, false, true)
	._putProp("then", .newNativeFunc(.promiseProto_then, "then", 2), true, false, true)

	._putSym(SymToStringTag, valueProp(asciiString(classPromise), false, false, true))

	return 
}

func ( *Runtime) ( *Object) objectImpl {
	 := .newNativeConstructOnly(, .builtin_newPromise, .getPromisePrototype(), "Promise", 1)

	._putProp("all", .newNativeFunc(.promise_all, "all", 1), true, false, true)
	._putProp("allSettled", .newNativeFunc(.promise_allSettled, "allSettled", 1), true, false, true)
	._putProp("any", .newNativeFunc(.promise_any, "any", 1), true, false, true)
	._putProp("race", .newNativeFunc(.promise_race, "race", 1), true, false, true)
	._putProp("reject", .newNativeFunc(.promise_reject, "reject", 1), true, false, true)
	._putProp("resolve", .newNativeFunc(.promise_resolve, "resolve", 1), true, false, true)

	.putSpeciesReturnThis()

	return 
}

func ( *Runtime) () *Object {
	 := .global.PromisePrototype
	if  == nil {
		 = &Object{runtime: }
		.global.PromisePrototype = 
		.self = .createPromiseProto()
	}
	return 
}

func ( *Runtime) () *Object {
	 := .global.Promise
	if  == nil {
		 = &Object{runtime: }
		.global.Promise = 
		.self = .createPromise()
	}
	return 
}

func ( *Runtime) ( *Object) func(interface{}) {
	,  := AssertFunction()
	return func( interface{}) {
		_, _ = (nil, .ToValue())
	}
}

// NewPromise creates and returns a Promise and resolving functions for it.
//
// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://github.com/dop251/goja_nodejs)
// where it can be used like this:
//
//	loop := NewEventLoop()
//	loop.Start()
//	defer loop.Stop()
//	loop.RunOnLoop(func(vm *goja.Runtime) {
//	    p, resolve, _ := vm.NewPromise()
//	    vm.Set("p", p)
//	    go func() {
//	        time.Sleep(500 * time.Millisecond)   // or perform any other blocking operation
//	        loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
//	            resolve(result)
//	        })
//	    }()
//	}
func ( *Runtime) () ( *Promise,  func( interface{}),  func( interface{})) {
	 := .newPromise(.getPromisePrototype())
	,  := .createResolvingFunctions()
	return , .wrapPromiseReaction(), .wrapPromiseReaction()
}

// SetPromiseRejectionTracker registers a function that will be called in two scenarios: when a promise is rejected
// without any handlers (with operation argument set to PromiseRejectionReject), and when a handler is added to a
// rejected promise for the first time (with operation argument set to PromiseRejectionHandle).
//
// Setting a tracker replaces any existing one. Setting it to nil disables the functionality.
//
// See https://tc39.es/ecma262/#sec-host-promise-rejection-tracker for more details.
func ( *Runtime) ( PromiseRejectionTracker) {
	.promiseRejectionTracker = 
}

// SetAsyncContextTracker registers a handler that allows to track async execution contexts. See AsyncContextTracker
// documentation for more details. Setting it to nil disables the functionality.
// This method (as Runtime in general) is not goroutine-safe.
func ( *Runtime) ( AsyncContextTracker) {
	.asyncContextTracker = 
}