use crate::host::HostFunction;
use crate::object::function::JSFunction;
use crate::object::object::JSObject;
use crate::runtime::context::JSContext;
use crate::value::JSValue;
use std::collections::VecDeque;

const PROMISE_STATE_SLOT: &str = "__promise_state__";
const PROMISE_RESULT_SLOT: &str = "__promise_result__";
const PROMISE_REACTIONS_SLOT: &str = "__promise_reactions__";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PromiseState {
    Pending,
    Fulfilled,
    Rejected,
}

impl PromiseState {
    pub fn is_pending(&self) -> bool {
        matches!(self, PromiseState::Pending)
    }
}

#[derive(Clone)]
pub struct Reaction {
    pub handler: JSValue,
    pub is_reject: bool,
    pub is_all_settled: bool,
    pub is_finally: bool,

    pub target_promise: JSValue,
}

pub struct MicrotaskQueue {
    queue: VecDeque<Microtask>,
}

pub enum Microtask {
    Reaction(Reaction, JSValue),

    UserCallback(JSValue, Vec<JSValue>),
}

impl MicrotaskQueue {
    pub fn new() -> Self {
        MicrotaskQueue {
            queue: VecDeque::new(),
        }
    }

    pub fn enqueue(&mut self, task: Microtask) {
        self.queue.push_back(task);
    }

    pub fn is_empty(&self) -> bool {
        self.queue.is_empty()
    }

    pub fn dequeue(&mut self) -> Option<Microtask> {
        self.queue.pop_front()
    }
}

impl Default for MicrotaskQueue {
    fn default() -> Self {
        Self::new()
    }
}

fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
    create_builtin_function_arity(ctx, name, 1)
}

fn create_builtin_function_arity(ctx: &mut JSContext, name: &str, arity: u32) -> JSValue {
    let mut func = JSFunction::new_builtin(ctx.intern(name), arity);
    func.set_builtin_marker(ctx, name);
    let ptr = Box::into_raw(Box::new(func)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
    JSValue::new_function(ptr)
}

fn intern_str(ctx: &mut JSContext, s: &str) -> crate::runtime::atom::Atom {
    ctx.intern(s)
}

fn get_array_element(obj: &JSObject, index: usize, ctx: &mut JSContext) -> JSValue {
    if obj.is_dense_array() {
        let ptr = obj as *const JSObject as *const crate::object::array_obj::JSArrayObject;
        let arr = unsafe { &*ptr };
        if index < arr.elements.len() {
            return arr.elements[index];
        }
        return JSValue::undefined();
    }
    if let Some(val) = obj.get_indexed(index) {
        return val;
    }
    let idx_atom = ctx.intern(&index.to_string());
    obj.get(idx_atom).unwrap_or(JSValue::undefined())
}

fn get_array_length(obj: &JSObject, ctx: &mut JSContext) -> usize {
    if obj.is_dense_array() {
        let ptr = obj as *const JSObject as *const crate::object::array_obj::JSArrayObject;
        let arr = unsafe { &*ptr };
        return arr.elements.len();
    }
    let length_atom = intern_str(ctx, "length");
    let len_val = obj.get(length_atom).unwrap_or(JSValue::new_int(0));
    if len_val.is_int() {
        len_val.get_int().max(0) as usize
    } else {
        0
    }
}

fn get_promise_state(ctx: &mut JSContext, obj: &JSObject) -> PromiseState {
    let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
    if let Some(state_val) = obj.get(state_atom) {
        let val = state_val.get_int();
        match val {
            1 => PromiseState::Fulfilled,
            2 => PromiseState::Rejected,
            _ => PromiseState::Pending,
        }
    } else {
        PromiseState::Pending
    }
}

fn set_promise_state(ctx: &mut JSContext, obj: &mut JSObject, state: PromiseState) {
    let state_val = match state {
        PromiseState::Pending => JSValue::new_int(0),
        PromiseState::Fulfilled => JSValue::new_int(1),
        PromiseState::Rejected => JSValue::new_int(2),
    };
    let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
    obj.set(state_atom, state_val);
}

fn get_promise_result(ctx: &mut JSContext, obj: &JSObject) -> JSValue {
    let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
    obj.get(result_atom).unwrap_or(JSValue::undefined())
}

fn set_promise_result(ctx: &mut JSContext, obj: &mut JSObject, value: JSValue) {
    let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
    obj.set(result_atom, value);
}

fn get_promise_reactions(ctx: &mut JSContext, obj: &JSObject) -> Vec<Reaction> {
    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
    if let Some(reactions_val) = obj.get(reactions_atom) {
        if reactions_val.is_object() {
            let ptr = reactions_val.get_ptr();

            if ptr == 0 {
                return Vec::new();
            }
            let arr = reactions_val.as_object();
            let len_atom = intern_str(ctx, "length");
            let len = arr.get(len_atom).map(|v| v.get_int() as usize).unwrap_or(0);
            let mut reactions = Vec::new();
            for i in 0..len {
                let idx_atom = intern_str(ctx, &i.to_string());
                if let Some(r_val) = arr.get(idx_atom) {
                    if r_val.is_object() {
                        let r_obj = r_val.as_object();
                        let handler_atom = intern_str(ctx, "handler");
                        let is_reject_atom = intern_str(ctx, "isReject");
                        let all_settled_atom = intern_str(ctx, "allSettled");
                        let finally_atom = intern_str(ctx, "isFinally");
                        let target_atom = intern_str(ctx, "target");
                        if let Some(handler) = r_obj.get(handler_atom) {
                            let is_reject = r_obj
                                .get(is_reject_atom)
                                .map(|v| v.get_bool())
                                .unwrap_or(false);
                            let is_all_settled = r_obj
                                .get(all_settled_atom)
                                .map(|v| v.get_bool())
                                .unwrap_or(false);
                            let is_finally = r_obj
                                .get(finally_atom)
                                .map(|v| v.get_bool())
                                .unwrap_or(false);
                            let target_promise = r_obj
                                .get(target_atom)
                                .unwrap_or(JSValue::undefined());
                            reactions.push(Reaction {
                                handler,
                                is_reject,
                                is_all_settled,
                                is_finally,
                                target_promise,
                            });
                        }
                    }
                }
            }
            return reactions;
        }
    }
    Vec::new()
}

fn create_reaction_array(ctx: &mut JSContext, reactions: Vec<Reaction>) -> JSValue {
    let mut arr = JSObject::new_array();
    let len_atom = intern_str(ctx, "length");
    arr.set(len_atom, JSValue::new_int(reactions.len() as i64));
    for (i, reaction) in reactions.into_iter().enumerate() {
        let mut r_obj = JSObject::new();
        let handler_atom = intern_str(ctx, "handler");
        let is_reject_atom = intern_str(ctx, "isReject");
        let all_settled_atom = intern_str(ctx, "allSettled");
        let finally_atom = intern_str(ctx, "isFinally");
        let target_atom = intern_str(ctx, "target");
        r_obj.set(handler_atom, reaction.handler);
        r_obj.set(is_reject_atom, JSValue::bool(reaction.is_reject));
        r_obj.set(all_settled_atom, JSValue::bool(reaction.is_all_settled));
        r_obj.set(finally_atom, JSValue::bool(reaction.is_finally));
        r_obj.set(target_atom, reaction.target_promise);
        let r_ptr = Box::into_raw(Box::new(r_obj)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(r_ptr);
        let idx_atom = intern_str(ctx, &i.to_string());
        arr.set(idx_atom, JSValue::new_object(r_ptr));
    }
    let ptr = Box::into_raw(Box::new(arr)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(ptr);
    JSValue::new_object(ptr)
}

pub fn is_promise(value: &JSValue) -> bool {
    if !value.is_object() {
        return false;
    }
    let obj = value.as_object();
    obj.is_promise()
}

fn create_promise(ctx: &mut JSContext) -> JSObject {
    let mut promise = JSObject::new_promise();

    if let Some(proto_ptr) = ctx.get_promise_prototype() {
        promise.prototype = Some(proto_ptr);
    }
    let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
    let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
    promise.set(state_atom, JSValue::new_int(0));
    promise.set(result_atom, JSValue::undefined());
    promise.set(reactions_atom, JSValue::null());
    promise
}

fn fulfill_promise(ctx: &mut JSContext, promise: &mut JSObject, value: JSValue) {
    set_promise_state(ctx, promise, PromiseState::Fulfilled);
    set_promise_result(ctx, promise, value);
    process_reactions(ctx, promise, PromiseState::Fulfilled);
}

fn reject_promise(ctx: &mut JSContext, promise: &mut JSObject, reason: JSValue) {
    set_promise_state(ctx, promise, PromiseState::Rejected);
    set_promise_result(ctx, promise, reason);
    process_reactions(ctx, promise, PromiseState::Rejected);
}

fn process_reactions(ctx: &mut JSContext, promise: &mut JSObject, state: PromiseState) {
    let reactions = get_promise_reactions(ctx, promise);
    for reaction in reactions {
        let argument = get_promise_result(ctx, promise);
        let microtask = if reaction.is_reject && state == PromiseState::Rejected {
            Microtask::Reaction(reaction, argument)
        } else if !reaction.is_reject && state == PromiseState::Fulfilled {
            Microtask::Reaction(reaction, argument)
        } else {
            continue;
        };
        ctx.microtask_enqueue(microtask);
    }
}

fn promise_resolve(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let value = if args.len() >= 2 {
        args[1]
    } else if !args.is_empty() {
        args[0]
    } else {
        JSValue::undefined()
    };
    if value.is_object() {
        let obj = value.as_object();
        if obj.is_promise() {
            return value;
        }
    }
    let mut promise = create_promise(ctx);
    fulfill_promise(ctx, &mut promise, value);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    JSValue::new_object(ptr)
}

fn promise_reject(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let reason = if args.len() >= 2 {
        args[1]
    } else if !args.is_empty() {
        args[0]
    } else {
        JSValue::undefined()
    };
    let mut promise = create_promise(ctx);
    reject_promise(ctx, &mut promise, reason);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    JSValue::new_object(ptr)
}

fn promise_then(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this_val = args.get(0).copied().unwrap_or(JSValue::undefined());
    if !this_val.is_object() {
        let mut err = JSObject::new();
        err.set(ctx.common_atoms.name, JSValue::new_string(ctx.intern("TypeError")));
        err.set(ctx.common_atoms.message, JSValue::new_string(ctx.intern("Promise.prototype.then called on incompatible receiver")));
        if let Some(proto) = ctx.get_type_error_prototype() {
            err.prototype = Some(proto);
        }
        let ptr = Box::into_raw(Box::new(err)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(ptr);
        ctx.pending_exception = Some(JSValue::new_object(ptr));
        return JSValue::undefined();
    }
    let promise = this_val.as_object_mut();
    if !promise.is_promise() {
        return JSValue::undefined();
    }
    let on_fulfilled = args.get(1).copied().unwrap_or(JSValue::undefined());
    let on_rejected = args.get(2).copied().unwrap_or(JSValue::undefined());
    let new_promise = create_promise(ctx);
    let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
    let target = JSValue::new_object(ptr_new);
    let state = get_promise_state(ctx, promise);
    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
    match state {
        PromiseState::Fulfilled => {
            let result = get_promise_result(ctx, promise);
            let reaction = Reaction {
                handler: on_fulfilled,
                is_reject: false,
                is_all_settled: false, is_finally: false,
                target_promise: target,
            };
            ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
        }
        PromiseState::Rejected => {
            let reason = get_promise_result(ctx, promise);
            let reaction = Reaction {
                handler: on_rejected,
                is_reject: true,
                is_all_settled: false, is_finally: false,
                target_promise: target,
            };
            ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
        }
        PromiseState::Pending => {
            let mut reactions = get_promise_reactions(ctx, promise);
            reactions.push(Reaction {
                handler: on_fulfilled,
                is_reject: false,
                is_all_settled: false, is_finally: false,
                target_promise: target,
            });
            reactions.push(Reaction {
                handler: on_rejected,
                is_reject: true,
                is_all_settled: false, is_finally: false,
                target_promise: target,
            });
            let reactions_arr = create_reaction_array(ctx, reactions);
            promise.set(reactions_atom, reactions_arr);
        }
    }
    target
}

fn promise_catch(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this_val = args.get(0).copied().unwrap_or(JSValue::undefined());
    let on_rejected = args.get(1).copied().unwrap_or(JSValue::undefined());

    let then_atom = ctx.intern("then");
    let then_fn = if this_val.is_object() {
        this_val.as_object().get(then_atom)
    } else {
        None
    };

    if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
        let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
        match then_fn {
            Some(f) if f.is_function() => {
                let result = vm.call_function_with_this(
                    ctx,
                    f,
                    this_val,
                    &[JSValue::undefined(), on_rejected],
                );
                match result {
                    Ok(v) => v,
                    Err(_) => {
                        if let Some(exc) = vm.last_caught_exception.take() {
                            vm.pending_throw = Some(exc);
                        }
                        JSValue::undefined()
                    }
                }
            }
            _ => {
                let mut err = JSObject::new();
                err.set(ctx.common_atoms.name, JSValue::new_string(ctx.intern("TypeError")));
                err.set(ctx.common_atoms.message, JSValue::new_string(ctx.intern("Promise.prototype.catch called on non-Promise")));
                if let Some(proto) = ctx.get_type_error_prototype() {
                    err.prototype = Some(proto);
                }
                let ptr = Box::into_raw(Box::new(err)) as usize;
                ctx.runtime_mut().gc_heap_mut().track(ptr);
                ctx.pending_exception = Some(JSValue::new_object(ptr));
                JSValue::undefined()
            }
        }
    } else {
        JSValue::undefined()
    }
}

fn promise_finally(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this_val = args.get(0).copied().unwrap_or(JSValue::undefined());
    let on_finally = args.get(1).copied().unwrap_or(JSValue::undefined());

    let then_atom = ctx.intern("then");
    let then_fn = if this_val.is_object() {
        this_val.as_object().get(then_atom)
    } else {
        None
    };

    if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
        let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
        match then_fn {
            Some(f) if f.is_function() => {
                let result = vm.call_function_with_this(
                    ctx,
                    f,
                    this_val,
                    &[on_finally, on_finally],
                );
                match result {
                    Ok(v) => v,
                    Err(_) => {
                        if let Some(exc) = vm.last_caught_exception.take() {
                            vm.pending_throw = Some(exc);
                        }
                        JSValue::undefined()
                    }
                }
            }
            _ => {
                let mut err = JSObject::new();
                err.set(ctx.common_atoms.name, JSValue::new_string(ctx.intern("TypeError")));
                err.set(ctx.common_atoms.message, JSValue::new_string(ctx.intern("Promise.prototype.finally called on non-Promise")));
                if let Some(proto) = ctx.get_type_error_prototype() {
                    err.prototype = Some(proto);
                }
                let ptr = Box::into_raw(Box::new(err)) as usize;
                ctx.runtime_mut().gc_heap_mut().track(ptr);
                ctx.pending_exception = Some(JSValue::new_object(ptr));
                JSValue::undefined()
            }
        }
    } else {
        JSValue::undefined()
    }
}

fn promise_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());

    let result_promise = create_promise(ctx);
    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
    let result_promise_val = JSValue::new_object(result_promise_ptr);
    ctx.runtime_mut().gc_heap_mut().track(result_promise_ptr);

    let rp = result_promise_val.as_object_mut();

    if !iterable.is_object() {
        let msg = JSValue::new_string(ctx.intern("TypeError: object is not iterable (cannot read property Symbol.iterator)"));
        reject_promise(ctx, rp, msg);
        return result_promise_val;
    }

    let obj = iterable.as_object();

    let sym_iter = crate::builtins::symbol::get_symbol_iterator(ctx);
    if sym_iter.is_symbol() {
        let sym_atom = crate::runtime::atom::Atom(0x40000000 | sym_iter.get_symbol_id());
        let iter_fn = obj.get(sym_atom).or_else(|| {
            let mut current = obj.prototype;
            while let Some(p) = current {
                let pobj = unsafe { &*p };
                if let Some(v) = pobj.get(sym_atom) {
                    return Some(v);
                }
                current = pobj.prototype;
            }
            None
        });
        match iter_fn {
            Some(f) if f.is_function() => {}
            _ => {
                let msg = JSValue::new_string(ctx.intern("TypeError: object is not iterable (cannot read property Symbol.iterator)"));
                reject_promise(ctx, rp, msg);
                return result_promise_val;
            }
        }
    }

    let len = get_array_length(&obj, ctx);

    if len == 0 {
        let mut empty_arr = JSObject::new_array();
        let length_atom = intern_str(ctx, "length");
        empty_arr.set(length_atom, JSValue::new_int(0));
        let arr_ptr = Box::into_raw(Box::new(empty_arr)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(arr_ptr);
        let rp = result_promise_val.as_object_mut();
        fulfill_promise(ctx, rp, JSValue::new_object(arr_ptr));
        return result_promise_val;
    }

    let mut results_arr = JSObject::new_array();
    let length_atom = intern_str(ctx, "length");
    results_arr.set(length_atom, JSValue::new_int(len as i64));
    let results_ptr = Box::into_raw(Box::new(results_arr)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(results_ptr);
    let results_val = JSValue::new_object(results_ptr);

    let remaining_atom = ctx.intern("__all_remaining__");
    let results_slot_atom = ctx.intern("__all_results__");
    let rp = result_promise_val.as_object_mut();
    rp.set(remaining_atom, JSValue::new_int(len as i64));
    rp.set(results_slot_atom, results_val);

    for i in 0..len {
        let item = get_array_element(&obj, i, ctx);

        let resolved = resolve_value_as_promise(ctx, item);
        let promise = resolved.as_object_mut();
        let state = get_promise_state(ctx, promise);
        match state {
            PromiseState::Fulfilled => {
                let value = get_promise_result(ctx, promise);
                let reaction = Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: false,
                    is_all_settled: false, is_finally: false,
                    target_promise: result_promise_val,
                };
                ctx.microtask_enqueue(Microtask::Reaction(reaction, value));
            }
            PromiseState::Rejected => {
                let reason = get_promise_result(ctx, promise);
                let rp = result_promise_val.as_object_mut();
                reject_promise(ctx, rp, reason);
                return result_promise_val;
            }
            PromiseState::Pending => {
                let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
                let mut reactions = get_promise_reactions(ctx, promise);
                reactions.push(Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: false,
                    is_all_settled: false, is_finally: false,
                    target_promise: result_promise_val,
                });
                reactions.push(Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: true,
                    is_all_settled: false, is_finally: false,
                    target_promise: result_promise_val,
                });
                let reactions_arr = create_reaction_array(ctx, reactions);
                promise.set(reactions_atom, reactions_arr);
            }
        }
    }

    result_promise_val
}

fn promise_race(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());

    let result_promise = create_promise(ctx);
    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
    let result_promise_val = JSValue::new_object(result_promise_ptr);
    ctx.runtime_mut().gc_heap_mut().track(result_promise_ptr);

    if !iterable.is_object() {
        let rp = result_promise_val.as_object_mut();
        let msg = JSValue::new_string(ctx.intern("TypeError: object is not iterable (cannot read property Symbol.iterator)"));
        reject_promise(ctx, rp, msg);
        return result_promise_val;
    }

    let obj = iterable.as_object();
    let len = get_array_length(&obj, ctx);

    for i in 0..len {
        let item = get_array_element(&obj, i, ctx);
        let resolved = resolve_value_as_promise(ctx, item);
        let promise = resolved.as_object_mut();
        let state = get_promise_state(ctx, promise);

        match state {
            PromiseState::Fulfilled => {
                let value = get_promise_result(ctx, promise);
                let rp = result_promise_val.as_object_mut();
                fulfill_promise(ctx, rp, value);
                return result_promise_val;
            }
            PromiseState::Rejected => {
                let reason = get_promise_result(ctx, promise);
                let rp = result_promise_val.as_object_mut();
                reject_promise(ctx, rp, reason);
                return result_promise_val;
            }
            PromiseState::Pending => {
                let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
                let mut reactions = get_promise_reactions(ctx, promise);
                reactions.push(Reaction {
                    handler: JSValue::new_int(-1),
                    is_reject: false,
                    is_all_settled: false, is_finally: false,
                    target_promise: result_promise_val,
                });
                reactions.push(Reaction {
                    handler: JSValue::new_int(-1),
                    is_reject: true,
                    is_all_settled: false, is_finally: false,
                    target_promise: result_promise_val,
                });
                let reactions_arr = create_reaction_array(ctx, reactions);
                promise.set(reactions_atom, reactions_arr);
            }
        }
    }

    result_promise_val
}

fn promise_all_settled(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());

    let result_promise = create_promise(ctx);
    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
    let result_promise_val = JSValue::new_object(result_promise_ptr);

    if !iterable.is_object() {
        let rp = result_promise_val.as_object_mut();
        let msg = JSValue::new_string(ctx.intern("TypeError: object is not iterable (cannot read property Symbol.iterator)"));
        reject_promise(ctx, rp, msg);
        return result_promise_val;
    }

    let obj = iterable.as_object();

    let len = get_array_length(&obj, ctx);

    if len == 0 {
        let mut empty_arr = JSObject::new_array();
        let length_atom = intern_str(ctx, "length");
        empty_arr.set(length_atom, JSValue::new_int(0));
        let arr_ptr = Box::into_raw(Box::new(empty_arr)) as usize;
        let rp = result_promise_val.as_object_mut();
        fulfill_promise(ctx, rp, JSValue::new_object(arr_ptr));
        ctx.runtime_mut().gc_heap_mut().track(arr_ptr);
        return result_promise_val;
    }

    let mut results_arr = JSObject::new_array();
    let length_atom = intern_str(ctx, "length");
    results_arr.set(length_atom, JSValue::new_int(len as i64));
    let results_ptr = Box::into_raw(Box::new(results_arr)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(results_ptr);
    let results_val = JSValue::new_object(results_ptr);

    let remaining_atom = ctx.intern("__allSettled_remaining__");
    let results_slot_atom = ctx.intern("__allSettled_results__");
    let rp = result_promise_val.as_object_mut();
    rp.set(remaining_atom, JSValue::new_int(len as i64));
    rp.set(results_slot_atom, results_val);

    for i in 0..len {
        let item = get_array_element(&obj, i, ctx);

        let resolved = resolve_value_as_promise(ctx, item);

        let promise = resolved.as_object_mut();
        let state = get_promise_state(ctx, promise);
        match state {
            PromiseState::Fulfilled => {
                let value = get_promise_result(ctx, promise);
                let reaction = Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: false,
                    is_all_settled: true, is_finally: false,
                    target_promise: result_promise_val,
                };
                ctx.microtask_enqueue(Microtask::Reaction(reaction, value));
            }
            PromiseState::Rejected => {
                let reason = get_promise_result(ctx, promise);
                let reaction = Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: true,
                    is_all_settled: true, is_finally: false,
                    target_promise: result_promise_val,
                };
                ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
            }
            PromiseState::Pending => {
                let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
                let mut reactions = get_promise_reactions(ctx, promise);
                reactions.push(Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: false,
                    is_all_settled: true, is_finally: false,
                    target_promise: result_promise_val,
                });
                reactions.push(Reaction {
                    handler: JSValue::new_int(i as i64),
                    is_reject: true,
                    is_all_settled: true, is_finally: false,
                    target_promise: result_promise_val,
                });
                let reactions_arr = create_reaction_array(ctx, reactions);
                promise.set(reactions_atom, reactions_arr);
            }
        }
    }

    result_promise_val
}

fn promise_any(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());

    let result_promise = create_promise(ctx);
    let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
    let result_promise_val = JSValue::new_object(result_promise_ptr);

    if !iterable.is_object() {
        let msg = JSValue::new_string(ctx.intern("TypeError: object is not iterable (cannot read property Symbol.iterator)"));
        let rp = result_promise_val.as_object_mut();
        reject_promise(ctx, rp, msg);
        return result_promise_val;
    }

    let obj = iterable.as_object();

    let len = get_array_length(&obj, ctx);

    if len == 0 {
        let rp = result_promise_val.as_object_mut();
        let mut err = JSObject::new();
        err.set(intern_str(ctx, "name"), JSValue::new_string(ctx.intern("AggregateError")));
        err.set(intern_str(ctx, "message"), JSValue::new_string(ctx.intern("All promises were rejected")));
        if let Some(proto) = ctx.get_error_prototype() {
            err.prototype = Some(proto);
        }
        let err_ptr = Box::into_raw(Box::new(err)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(err_ptr);
        reject_promise(ctx, rp, JSValue::new_object(err_ptr));
        return result_promise_val;
    }

    let remaining_atom = ctx.intern("__any_remaining__");
    let rp = result_promise_val.as_object_mut();
    rp.set(remaining_atom, JSValue::new_int(len as i64));

    for i in 0..len {
        let item = get_array_element(&obj, i, ctx);

        let resolved = resolve_value_as_promise(ctx, item);
        let promise = resolved.as_object_mut();
        let state = get_promise_state(ctx, promise);

        match state {
            PromiseState::Fulfilled => {
                let value = get_promise_result(ctx, promise);
                let rp = result_promise_val.as_object_mut();
                fulfill_promise(ctx, rp, value);
                return result_promise_val;
            }
            PromiseState::Rejected | PromiseState::Pending => {
                if state == PromiseState::Pending {
                    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
                    let mut reactions = get_promise_reactions(ctx, promise);
                    reactions.push(Reaction {
                        handler: JSValue::new_int(i as i64),
                        is_reject: false,
                        is_all_settled: false, is_finally: false,
                        target_promise: result_promise_val,
                    });
                    reactions.push(Reaction {
                        handler: JSValue::new_int(i as i64),
                        is_reject: true,
                        is_all_settled: false, is_finally: false,
                        target_promise: result_promise_val,
                    });
                    let reactions_arr = create_reaction_array(ctx, reactions);
                    promise.set(reactions_atom, reactions_arr);
                } else {
                    let reason = get_promise_result(ctx, promise);
                    let reaction = Reaction {
                        handler: JSValue::new_int(i as i64),
                        is_reject: true,
                        is_all_settled: false, is_finally: false,
                        target_promise: result_promise_val,
                    };
                    ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
                }
            }
        }
    }

    result_promise_val
}

fn promise_with_resolvers(ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
    let mut promise = create_promise(ctx);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(ptr);
    let promise_val = JSValue::new_object(ptr);

    let (resolve_val, reject_val) = create_resolve_reject_fns(ctx, promise_val);

    let mut result = JSObject::new();
    let promise_atom = ctx.intern("promise");
    let resolve_atom = ctx.intern("resolve");
    let reject_atom = ctx.intern("reject");
    result.set(promise_atom, promise_val);
    result.set(resolve_atom, resolve_val);
    result.set(reject_atom, reject_val);
    let result_ptr = Box::into_raw(Box::new(result)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(result_ptr);
    JSValue::new_object(result_ptr)
}

fn resolve_value_as_promise(ctx: &mut JSContext, value: JSValue) -> JSValue {
    if value.is_object() {
        let obj = value.as_object();
        if obj.is_promise() {
            return value;
        }
    }
    let mut promise = create_promise(ctx);
    fulfill_promise(ctx, &mut promise, value);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(ptr);
    JSValue::new_object(ptr)
}

fn promise_internal_resolve(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let callee_fn = args.get(0).copied().unwrap_or(JSValue::undefined());
    let value = args.get(1).copied().unwrap_or(JSValue::undefined());
    if !callee_fn.is_function() {
        return JSValue::undefined();
    }
    let target_atom = ctx.intern("__target_promise__");
    let func_obj = callee_fn.as_function();
    let promise_val = func_obj.base.get(target_atom).unwrap_or(JSValue::undefined());
    if !promise_val.is_object() {
        return JSValue::undefined();
    }
    let promise = promise_val.as_object_mut();
    if !promise.is_promise() || !get_promise_state(ctx, promise).is_pending() {
        return JSValue::undefined();
    }
    if value.is_object() {
        let val_obj = value.as_object();
        if val_obj.is_promise() {
            let state = get_promise_state(ctx, val_obj);
            let result_val = get_promise_result(ctx, val_obj);
            match state {
                PromiseState::Fulfilled => fulfill_promise(ctx, promise, result_val),
                PromiseState::Rejected => reject_promise(ctx, promise, result_val),
                PromiseState::Pending => {
                    let already_atom = ctx.intern("__already_resolved__");
                    promise.set(already_atom, JSValue::bool(true));
                    let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
                    let mut reactions = get_promise_reactions(ctx, val_obj);
                    let target = promise_val;
                    reactions.push(Reaction {
                        handler: JSValue::undefined(),
                        is_reject: false,
                        is_all_settled: false, is_finally: false,
                        target_promise: target,
                    });
                    reactions.push(Reaction {
                        handler: JSValue::undefined(),
                        is_reject: true,
                        is_all_settled: false, is_finally: false,
                        target_promise: target,
                    });
                    let reactions_arr = create_reaction_array(ctx, reactions);
                    let val_mut = value.as_object_mut();
                    val_mut.set(reactions_atom, reactions_arr);
                    return JSValue::undefined();
                }
            }
            return JSValue::undefined();
        }
    }
    fulfill_promise(ctx, promise, value);
    JSValue::undefined()
}

fn promise_internal_reject(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let callee_fn = args.get(0).copied().unwrap_or(JSValue::undefined());
    let reason = args.get(1).copied().unwrap_or(JSValue::undefined());
    if !callee_fn.is_function() {
        return JSValue::undefined();
    }
    let target_atom = ctx.intern("__target_promise__");
    let func_obj = callee_fn.as_function();
    let promise_val = func_obj.base.get(target_atom).unwrap_or(JSValue::undefined());
    if !promise_val.is_object() {
        return JSValue::undefined();
    }
    let promise = promise_val.as_object_mut();
    if promise.is_promise() && get_promise_state(ctx, promise).is_pending() {
        reject_promise(ctx, promise, reason);
    }
    JSValue::undefined()
}

fn create_resolve_reject_fns(ctx: &mut JSContext, promise_val: JSValue) -> (JSValue, JSValue) {
    let empty_name = ctx.intern("");
    let mut resolve_fn = crate::object::function::JSFunction::new_builtin(empty_name, 1);
    resolve_fn.set_builtin_marker(ctx, "promise_internal_resolve");
    resolve_fn.name = empty_name;
    let name_desc = crate::object::object::PropertyDescriptor {
        value: Some(JSValue::new_string(empty_name)),
        writable: false,
        enumerable: false,
        configurable: true,
        get: None,
        set: None,
    };
    resolve_fn.base.define_property(ctx.common_atoms.name, name_desc);
    let resolve_target_atom = ctx.intern("__target_promise__");
    resolve_fn.base.set(resolve_target_atom, promise_val);
    let resolve_ptr = Box::into_raw(Box::new(resolve_fn)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(resolve_ptr);
    let resolve_val = JSValue::new_function(resolve_ptr);

    let mut reject_fn = crate::object::function::JSFunction::new_builtin(empty_name, 1);
    reject_fn.set_builtin_marker(ctx, "promise_internal_reject");
    reject_fn.name = empty_name;
    let name_desc = crate::object::object::PropertyDescriptor {
        value: Some(JSValue::new_string(empty_name)),
        writable: false,
        enumerable: false,
        configurable: true,
        get: None,
        set: None,
    };
    reject_fn.base.define_property(ctx.common_atoms.name, name_desc);
    let reject_target_atom = ctx.intern("__target_promise__");
    reject_fn.base.set(reject_target_atom, promise_val);
    let reject_ptr = Box::into_raw(Box::new(reject_fn)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(reject_ptr);
    let reject_val = JSValue::new_function(reject_ptr);

    (resolve_val, reject_val)
}

fn promise_executor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let executor_fn = args.get(0).copied().unwrap_or(JSValue::undefined());
    if !executor_fn.is_function() {
        let mut promise = create_promise(ctx);
        let ptr = Box::into_raw(Box::new(promise)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(ptr);
        let promise_val = JSValue::new_object(ptr);
        let rp = promise_val.as_object_mut();
        let msg = JSValue::new_string(ctx.intern("TypeError: Promise resolver undefined is not a function"));
        reject_promise(ctx, rp, msg);
        return promise_val;
    }

    let mut promise = create_promise(ctx);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(ptr);
    let promise_val = JSValue::new_object(ptr);

    let (resolve_val, reject_val) = create_resolve_reject_fns(ctx, promise_val);

    if let Err(e) = call_callback(ctx, executor_fn, &[resolve_val, reject_val]) {
        let rp = promise_val.as_object_mut();
        let msg = JSValue::new_string(ctx.intern(&e));
        reject_promise(ctx, rp, msg);
    }

    promise_val
}

fn call_callback(
    ctx: &mut JSContext,
    callback: JSValue,
    args: &[JSValue],
) -> Result<JSValue, String> {
    if let Some(ptr) = ctx.get_register_vm_ptr() {
        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
        vm.call_function(ctx, callback, args)
    } else {
        Err("VM not available".to_string())
    }
}

fn create_promise_prototype(ctx: &mut JSContext) -> JSValue {
    let mut proto = JSObject::new_promise();
    let constructor_atom = intern_str(ctx, "constructor");
    let then_atom = intern_str(ctx, "then");
    let catch_atom = intern_str(ctx, "catch");
    let finally_atom = intern_str(ctx, "finally");
    proto.set(constructor_atom, JSValue::null());

    use crate::builtins::global::set_non_enumerable;
    set_non_enumerable(&mut proto, then_atom, create_builtin_function_arity(ctx, "promise_then", 2));
    set_non_enumerable(&mut proto, catch_atom, create_builtin_function(ctx, "promise_catch"));
    set_non_enumerable(&mut proto, finally_atom, create_builtin_function(ctx, "promise_finally"));

    let to_string_tag = crate::builtins::symbol::get_symbol_to_string_tag(ctx);
    if to_string_tag.is_symbol() {
        let sym_atom = crate::runtime::atom::Atom(0x40000000 | to_string_tag.get_symbol_id());
        set_non_enumerable(&mut proto, sym_atom, JSValue::new_string(ctx.intern("Promise")));
    }

    if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
        proto.prototype = Some(obj_proto_ptr);
    }
    let ptr = Box::into_raw(Box::new(proto)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(ptr);
    JSValue::new_object(ptr)
}

pub fn init_promise(ctx: &mut JSContext) {
    let promise_atom = ctx.intern("Promise");
    let mut promise_func = crate::object::function::JSFunction::new_builtin(promise_atom, 1);
    promise_func.set_builtin_marker(ctx, "promise_executor");
    let proto_value = create_promise_prototype(ctx);

    let proto_ptr = proto_value.get_ptr();
    ctx.set_promise_prototype(proto_ptr);
    use crate::builtins::global::set_non_enumerable;
    set_non_enumerable(&mut promise_func.base, ctx.intern("prototype"), proto_value);
    set_non_enumerable(&mut promise_func.base, ctx.intern("resolve"), create_builtin_function(ctx, "promise_resolve"));
    set_non_enumerable(&mut promise_func.base, ctx.intern("reject"), create_builtin_function(ctx, "promise_reject"));
    set_non_enumerable(&mut promise_func.base, ctx.intern("all"), create_builtin_function(ctx, "promise_all"));
    set_non_enumerable(&mut promise_func.base, ctx.intern("race"), create_builtin_function(ctx, "promise_race"));
    set_non_enumerable(&mut promise_func.base, ctx.intern("allSettled"), create_builtin_function(ctx, "promise_allSettled"));
    set_non_enumerable(&mut promise_func.base, ctx.intern("any"), create_builtin_function(ctx, "promise_any"));
    set_non_enumerable(&mut promise_func.base, ctx.intern("withResolvers"), create_builtin_function(ctx, "promise_with_resolvers"));
    let promise_ptr = Box::into_raw(Box::new(promise_func)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(promise_ptr);
    let promise_value = JSValue::new_function(promise_ptr);

    crate::builtins::symbol::install_species_accessor(ctx, &promise_value);

    let proto_mut = unsafe { &mut *(proto_ptr as *mut JSObject) };
    proto_mut.set(ctx.intern("constructor"), promise_value);

    let global = ctx.global();
    if global.is_object() {
        let global_obj = global.as_object_mut();
        crate::builtins::global::set_non_enumerable(global_obj, promise_atom, promise_value);
    }
}

pub fn create_resolved_promise(ctx: &mut JSContext, value: JSValue) -> JSValue {
    let mut promise = create_promise(ctx);
    fulfill_promise(ctx, &mut promise, value);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    let result = JSValue::new_object(ptr);
    result
}

pub fn create_rejected_promise(ctx: &mut JSContext, reason: JSValue) -> JSValue {
    let mut promise = create_promise(ctx);
    reject_promise(ctx, &mut promise, reason);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    JSValue::new_object(ptr)
}

#[cfg(any(feature = "process", feature = "fetch"))]
pub(crate) fn fulfill_promise_with_value(
    ctx: &mut JSContext,
    promise_obj_ptr: usize,
    value: JSValue,
) {
    let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
    fulfill_promise(ctx, promise, value);
}

#[cfg(any(feature = "process", feature = "fetch"))]
pub(crate) fn reject_promise_with_value(
    ctx: &mut JSContext,
    promise_obj_ptr: usize,
    reason: JSValue,
) {
    let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
    reject_promise(ctx, promise, reason);
}

#[cfg(feature = "fetch")]
pub fn create_pending_promise(ctx: &mut JSContext) -> JSValue {
    let promise = create_promise(ctx);
    let ptr = Box::into_raw(Box::new(promise)) as usize;
    JSValue::new_object(ptr)
}

pub fn run_microtasks_with_vm(ctx: &mut JSContext, vm: &mut crate::runtime::vm::VM) {
    while let Some(task) = ctx.microtask_dequeue() {
        match task {
            Microtask::Reaction(reaction, argument) => {
                if reaction.is_all_settled {
                    handle_all_settled_reaction(ctx, vm, &reaction, argument);
                    continue;
                }
                let handler = reaction.handler;
                if handler.is_int() && reaction.target_promise.is_object() {
                    let target = reaction.target_promise.as_object_mut();
                    if target.is_promise() && get_promise_state(ctx, target) == PromiseState::Pending {
                        let all_remaining_atom = ctx.intern("__all_remaining__");
                        let any_remaining_atom = ctx.intern("__any_remaining__");
                        if target.get(all_remaining_atom).is_some() {
                            if reaction.is_reject {
                                reject_promise(ctx, target, argument);
                            } else {
                                let idx = handler.get_int() as usize;
                                let results_slot = ctx.intern("__all_results__");
                                if let Some(results_val) = target.get(results_slot) {
                                    if results_val.is_object() {
                                        let results_arr = results_val.as_object_mut();
                                        let idx_atom = ctx.intern(&idx.to_string());
                                        results_arr.set(idx_atom, argument);
                                    }
                                }
                                let remaining = target.get(all_remaining_atom).unwrap().get_int() - 1;
                                target.set(all_remaining_atom, JSValue::new_int(remaining));
                                if remaining <= 0 {
                                    let results = target.get(results_slot).unwrap_or(JSValue::undefined());
                                    fulfill_promise(ctx, target, results);
                                }
                            }
                        } else if target.get(any_remaining_atom).is_some() {
                            if reaction.is_reject {
                                let remaining = target.get(any_remaining_atom).unwrap().get_int() - 1;
                                target.set(any_remaining_atom, JSValue::new_int(remaining));
                                if remaining <= 0 {
                                    let mut err = JSObject::new();
                                    err.set(intern_str(ctx, "name"), JSValue::new_string(ctx.intern("AggregateError")));
                                    err.set(intern_str(ctx, "message"), JSValue::new_string(ctx.intern("All promises were rejected")));
                                    if let Some(proto) = ctx.get_error_prototype() {
                                        err.prototype = Some(proto);
                                    }
                                    let err_ptr = Box::into_raw(Box::new(err)) as usize;
                                    ctx.runtime_mut().gc_heap_mut().track(err_ptr);
                                    reject_promise(ctx, target, JSValue::new_object(err_ptr));
                                }
                            } else {
                                fulfill_promise(ctx, target, argument);
                            }
                        } else {
                            if reaction.is_reject {
                                reject_promise(ctx, target, argument);
                            } else {
                                fulfill_promise(ctx, target, argument);
                            }
                        }
                    }
                    continue;
                }
                if handler.is_function() {
                    let result = vm.call_function(ctx, handler, &[argument]);
                    if reaction.target_promise.is_object() {
                        let target = reaction.target_promise.as_object_mut();
                        if target.is_promise() {
                            if reaction.is_finally {
                                match result {
                                    Ok(_) => {
                                        if reaction.is_reject {
                                            reject_promise(ctx, target, argument);
                                        } else {
                                            fulfill_promise(ctx, target, argument);
                                        }
                                    }
                                    Err(e) => {
                                        let msg = JSValue::new_string(ctx.intern(&e));
                                        reject_promise(ctx, target, msg);
                                    }
                                }
                            } else {
                                match result {
                                    Ok(val) => fulfill_promise(ctx, target, val),
                                    Err(_) => {
                                        reject_promise(ctx, target, JSValue::undefined());
                                    }
                                }
                            }
                        }
                    }
                }
            }
            Microtask::UserCallback(callback, args) => {
                if callback.is_function() {
                    let _result = vm.call_function(ctx, callback, &args);
                }
            }
        }
    }
}

fn handle_all_settled_reaction(ctx: &mut JSContext, vm: &mut crate::runtime::vm::VM, reaction: &Reaction, argument: JSValue) {
    let idx = reaction.handler.get_int() as usize;
    let target = reaction.target_promise;
    if !target.is_object() {
        return;
    }

    let mut result_obj = JSObject::new();
    let status_atom = ctx.intern("status");
    if reaction.is_reject {
        result_obj.set(status_atom, JSValue::new_string(ctx.intern("rejected")));
        result_obj.set(ctx.intern("reason"), argument);
    } else {
        result_obj.set(status_atom, JSValue::new_string(ctx.intern("fulfilled")));
        result_obj.set(ctx.intern("value"), argument);
    }
    let r_ptr = Box::into_raw(Box::new(result_obj)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(r_ptr);
    let result_val = JSValue::new_object(r_ptr);

    let target_obj = target.as_object_mut();
    let results_slot = ctx.intern("__allSettled_results__");
    if let Some(results_val) = target_obj.get(results_slot) {
        if results_val.is_object() {
            let results_arr = results_val.as_object_mut();
            let idx_atom = ctx.intern(&idx.to_string());
            results_arr.set(idx_atom, result_val);
        }
    }

    let remaining_atom = ctx.intern("__allSettled_remaining__");
    if let Some(rem_val) = target_obj.get(remaining_atom) {
        let remaining = rem_val.get_int() - 1;
        target_obj.set(remaining_atom, JSValue::new_int(remaining));
        if remaining <= 0 {
            let results = target_obj.get(results_slot).unwrap_or(JSValue::undefined());
            fulfill_promise(ctx, target_obj, results);
        }
    }
}

pub fn register_builtins(ctx: &mut JSContext) {
    ctx.register_builtin(
        "promise_executor",
        HostFunction::ctor("executor", 1, promise_executor),
    );
    ctx.register_builtin(
        "promise_internal_resolve",
        HostFunction::new("resolve", 1, promise_internal_resolve),
    );
    ctx.register_builtin(
        "promise_internal_reject",
        HostFunction::new("reject", 1, promise_internal_reject),
    );
    ctx.register_builtin(
        "promise_resolve",
        HostFunction::new("resolve", 1, promise_resolve),
    );
    ctx.register_builtin(
        "promise_reject",
        HostFunction::new("reject", 1, promise_reject),
    );
    ctx.register_builtin(
        "promise_then",
        HostFunction::method("then", 2, promise_then),
    );
    ctx.register_builtin(
        "promise_catch",
        HostFunction::method("catch", 2, promise_catch),
    );
    ctx.register_builtin(
        "promise_finally",
        HostFunction::method("finally", 2, promise_finally),
    );
    ctx.register_builtin("promise_all", HostFunction::new("all", 1, promise_all));
    ctx.register_builtin("promise_race", HostFunction::new("race", 1, promise_race));
    ctx.register_builtin(
        "promise_allSettled",
        HostFunction::new("allSettled", 1, promise_all_settled),
    );
    ctx.register_builtin("promise_any", HostFunction::new("any", 1, promise_any));
    ctx.register_builtin(
        "promise_with_resolvers",
        HostFunction::new("withResolvers", 0, promise_with_resolvers),
    );
}
