use super::atom::{Atom, AtomTable};
use super::event_loop::EventLoop;
use super::extension::MacroTaskExtension;
use super::runtime::JSRuntime;
use crate::builtins;
use crate::builtins::promise::{Microtask, MicrotaskQueue};
use crate::compiler::codegen::OptLevel;
use crate::host::HostFunction;
use crate::object::JSObject;
use crate::object::ShapeCache;
use crate::util::FxHashMap;
use crate::value::JSValue;

#[allow(non_snake_case)]
pub struct CommonAtoms {
    pub length: Atom,
    pub prototype: Atom,
    pub constructor: Atom,
    pub call: Atom,
    pub apply: Atom,
    pub bind: Atom,
    pub to_string: Atom,
    pub value_of: Atom,
    pub push: Atom,
    pub pop: Atom,
    pub shift: Atom,
    pub unshift: Atom,
    pub slice: Atom,
    pub splice: Atom,
    pub join: Atom,
    pub map: Atom,
    pub for_each: Atom,
    pub filter: Atom,
    pub reduce: Atom,
    pub index_of: Atom,
    pub last_index_of: Atom,
    pub includes: Atom,
    pub concat: Atom,
    pub reverse: Atom,
    pub sort: Atom,
    pub flat: Atom,
    pub flat_map: Atom,
    pub find: Atom,
    pub find_index: Atom,
    pub every: Atom,
    pub some: Atom,
    pub fill: Atom,
    pub keys: Atom,
    pub values: Atom,
    pub entries: Atom,
    pub has_own_property: Atom,
    pub is_prototype_of: Atom,
    pub property_is_enumerable: Atom,
    pub to_locale_string: Atom,
    pub split: Atom,
    pub replace: Atom,
    pub match_atom: Atom,
    pub search: Atom,
    pub test: Atom,
    pub exec: Atom,
    pub then: Atom,
    pub catch: Atom,
    pub finally: Atom,
    pub message: Atom,
    pub name: Atom,
    pub stack: Atom,
    pub __proto__: Atom,
    pub __super__: Atom,
    pub __boundFn: Atom,
    pub __boundArgs: Atom,
    pub __boundThis: Atom,
    pub __value__: Atom,
    pub __dateValue__: Atom,
    pub __pattern__: Atom,
    pub __flags__: Atom,
    pub source: Atom,
    pub global: Atom,
    pub ignore_case: Atom,
    pub multiline: Atom,
    pub sticky: Atom,
    pub unicode: Atom,
    pub undefined: Atom,
    pub null: Atom,
    pub object: Atom,
    pub function: Atom,
    pub number: Atom,
    pub string: Atom,
    pub boolean: Atom,
    pub symbol: Atom,
    pub bigint: Atom,
    pub math: Atom,
    pub json: Atom,
    pub array: Atom,
    pub regexp: Atom,
    pub error: Atom,
    pub date: Atom,
    pub promise: Atom,
    pub map_ctor: Atom,
    pub set_ctor: Atom,
    pub weak_map_ctor: Atom,
    pub weak_set_ctor: Atom,
    pub proxy: Atom,
    pub reflect: Atom,
    pub console: Atom,
    pub index: Atom,
    pub input: Atom,
    pub empty: Atom,
    pub n0: Atom,
    pub n1: Atom,

    pub typeof_undefined: Atom,
    pub typeof_object: Atom,
    pub typeof_boolean: Atom,
    pub typeof_number: Atom,
    pub typeof_bigint: Atom,
    pub typeof_symbol: Atom,
    pub typeof_string: Atom,
    pub typeof_function: Atom,

    pub dot_all: Atom,
    pub last_index: Atom,
    pub callee: Atom,
    pub default_: Atom,
    pub url: Atom,
    pub __promise_state__: Atom,
    pub __promise_result__: Atom,
    pub __done__: Atom,
    pub __iter_arr__: Atom,
    pub __iter_idx__: Atom,
    pub to_string_tag: Atom,
}

impl CommonAtoms {
    pub fn new(tbl: &mut AtomTable) -> Self {
        CommonAtoms {
            length: tbl.intern("length"),
            prototype: tbl.intern("prototype"),
            constructor: tbl.intern("constructor"),
            call: tbl.intern("call"),
            apply: tbl.intern("apply"),
            bind: tbl.intern("bind"),
            to_string: tbl.intern("toString"),
            value_of: tbl.intern("valueOf"),
            push: tbl.intern("push"),
            pop: tbl.intern("pop"),
            shift: tbl.intern("shift"),
            unshift: tbl.intern("unshift"),
            slice: tbl.intern("slice"),
            splice: tbl.intern("splice"),
            join: tbl.intern("join"),
            map: tbl.intern("map"),
            for_each: tbl.intern("forEach"),
            filter: tbl.intern("filter"),
            reduce: tbl.intern("reduce"),
            index_of: tbl.intern("indexOf"),
            last_index_of: tbl.intern("lastIndexOf"),
            includes: tbl.intern("includes"),
            concat: tbl.intern("concat"),
            reverse: tbl.intern("reverse"),
            sort: tbl.intern("sort"),
            flat: tbl.intern("flat"),
            flat_map: tbl.intern("flatMap"),
            find: tbl.intern("find"),
            find_index: tbl.intern("findIndex"),
            every: tbl.intern("every"),
            some: tbl.intern("some"),
            fill: tbl.intern("fill"),
            keys: tbl.intern("keys"),
            values: tbl.intern("values"),
            entries: tbl.intern("entries"),
            has_own_property: tbl.intern("hasOwnProperty"),
            is_prototype_of: tbl.intern("isPrototypeOf"),
            property_is_enumerable: tbl.intern("propertyIsEnumerable"),
            to_locale_string: tbl.intern("toLocaleString"),
            split: tbl.intern("split"),
            replace: tbl.intern("replace"),
            match_atom: tbl.intern("match"),
            search: tbl.intern("search"),
            test: tbl.intern("test"),
            exec: tbl.intern("exec"),
            then: tbl.intern("then"),
            catch: tbl.intern("catch"),
            finally: tbl.intern("finally"),
            message: tbl.intern("message"),
            name: tbl.intern("name"),
            stack: tbl.intern("stack"),
            __proto__: tbl.intern("__proto__"),
            __super__: tbl.intern("__super__"),
            __boundFn: tbl.intern("__boundFn"),
            __boundArgs: tbl.intern("__boundArgs"),
            __boundThis: tbl.intern("__boundThis"),
            __value__: tbl.intern("__value__"),
            __dateValue__: tbl.intern("__dateValue__"),
            __pattern__: tbl.intern("__pattern__"),
            __flags__: tbl.intern("__flags__"),
            source: tbl.intern("source"),
            global: tbl.intern("global"),
            ignore_case: tbl.intern("ignoreCase"),
            multiline: tbl.intern("multiline"),
            sticky: tbl.intern("sticky"),
            unicode: tbl.intern("unicode"),
            undefined: tbl.intern("undefined"),
            null: tbl.intern("null"),
            object: tbl.intern("Object"),
            function: tbl.intern("Function"),
            number: tbl.intern("Number"),
            string: tbl.intern("String"),
            boolean: tbl.intern("Boolean"),
            symbol: tbl.intern("Symbol"),
            bigint: tbl.intern("BigInt"),
            math: tbl.intern("Math"),
            json: tbl.intern("JSON"),
            array: tbl.intern("Array"),
            regexp: tbl.intern("RegExp"),
            error: tbl.intern("Error"),
            date: tbl.intern("Date"),
            promise: tbl.intern("Promise"),
            map_ctor: tbl.intern("Map"),
            set_ctor: tbl.intern("Set"),
            weak_map_ctor: tbl.intern("WeakMap"),
            weak_set_ctor: tbl.intern("WeakSet"),
            proxy: tbl.intern("Proxy"),
            reflect: tbl.intern("Reflect"),
            console: tbl.intern("console"),
            index: tbl.intern("index"),
            input: tbl.intern("input"),
            empty: tbl.intern(""),
            n0: tbl.intern("0"),
            n1: tbl.intern("1"),
            typeof_undefined: tbl.intern("undefined"),
            typeof_object: tbl.intern("object"),
            typeof_boolean: tbl.intern("boolean"),
            typeof_number: tbl.intern("number"),
            typeof_bigint: tbl.intern("bigint"),
            typeof_symbol: tbl.intern("symbol"),
            typeof_string: tbl.intern("string"),
            typeof_function: tbl.intern("function"),
            dot_all: tbl.intern("dotAll"),
            last_index: tbl.intern("lastIndex"),
            callee: tbl.intern("callee"),
            default_: tbl.intern("default"),
            url: tbl.intern("url"),
            __promise_state__: tbl.intern("__promise_state__"),
            __promise_result__: tbl.intern("__promise_result__"),
            __done__: tbl.intern("__done__"),
            __iter_arr__: tbl.intern("__iter_arr__"),
            __iter_idx__: tbl.intern("__iter_idx__"),
            to_string_tag: tbl.intern("Symbol.toStringTag"),
        }
    }
}

pub struct JSContext {
    runtime: *mut JSRuntime,
    global_object: JSValue,
    interrupt_counter: i32,
    builtin_functions: FxHashMap<String, HostFunction>,

    string_prototype: Option<usize>,
    number_prototype: Option<usize>,
    array_prototype: Option<usize>,
    array_iterator_prototype: Option<usize>,
    map_iterator_prototype: Option<usize>,
    set_iterator_prototype: Option<usize>,
    regexp_prototype: Option<usize>,
    date_prototype: Option<usize>,
    object_prototype: Option<usize>,
    function_prototype: Option<usize>,
    map_prototype: Option<usize>,
    set_prototype: Option<usize>,
    weakmap_prototype: Option<usize>,
    weakset_prototype: Option<usize>,
    dataview_prototype: Option<usize>,
    typed_array_prototype: Option<usize>,
    error_prototype: Option<usize>,
    type_error_prototype: Option<usize>,
    reference_error_prototype: Option<usize>,
    syntax_error_prototype: Option<usize>,
    range_error_prototype: Option<usize>,
    uri_error_prototype: Option<usize>,
    eval_error_prototype: Option<usize>,
    symbol_prototype: Option<usize>,
    bool_prototype: Option<usize>,
    bigint_prototype: Option<usize>,
    weakref_prototype: Option<usize>,
    finalization_registry_prototype: Option<usize>,
    pub finalization_registries: std::cell::RefCell<Vec<usize>>,
    generator_prototype: Option<usize>,
    async_generator_prototype: Option<usize>,
    promise_prototype: Option<usize>,

    register_vm_ptr: Option<usize>,
    compiler_opt_level: OptLevel,

    pub pending_exception: Option<JSValue>,

    pending_callback: Option<PendingCallback>,

    microtask_queue: MicrotaskQueue,

    current_module_specifier: Option<String>,
    import_meta_object: Option<usize>,

    event_loop: EventLoop,

    running_event_loop: Option<*mut EventLoop>,

    shape_cache: ShapeCache,

    pub common_atoms: CommonAtoms,

    cached_int_atoms: [Atom; 100],

    args_length_shape: Option<std::ptr::NonNull<crate::object::shape::Shape>>,
}

#[derive(Clone)]
pub struct PendingCallback {
    pub func: JSValue,

    pub args: Vec<JSValue>,

    pub is_method_call: bool,
}

impl JSContext {
    const INTERRUPT_THRESHOLD: i32 = 1000;

    pub fn new(runtime: &mut JSRuntime) -> Self {
        let global = JSObject::new_global();
        let global_ptr = Box::into_raw(Box::new(global)) as usize;
        runtime.gc_heap_mut().track(global_ptr);
        let global_value = JSValue::new_object(global_ptr);

        let mut ctx = JSContext {
            runtime: runtime as *mut JSRuntime,
            global_object: global_value,
            interrupt_counter: Self::INTERRUPT_THRESHOLD,
            builtin_functions: FxHashMap::default(),
            string_prototype: None,
            number_prototype: None,
            array_prototype: None,
            array_iterator_prototype: None,
            map_iterator_prototype: None,
            set_iterator_prototype: None,
            regexp_prototype: None,
            date_prototype: None,
            object_prototype: None,
            function_prototype: None,
            map_prototype: None,
            set_prototype: None,
            weakmap_prototype: None,
            weakset_prototype: None,
            dataview_prototype: None,
            typed_array_prototype: None,
            error_prototype: None,
            type_error_prototype: None,
            reference_error_prototype: None,
            syntax_error_prototype: None,
            range_error_prototype: None,
            uri_error_prototype: None,
            eval_error_prototype: None,
            symbol_prototype: None,
            bool_prototype: None,
            bigint_prototype: None,
            weakref_prototype: None,
            finalization_registry_prototype: None,
            finalization_registries: std::cell::RefCell::new(Vec::new()),
            generator_prototype: None,
            async_generator_prototype: None,
            promise_prototype: None,
            register_vm_ptr: None,
            compiler_opt_level: OptLevel::default(),
            pending_exception: None,
            pending_callback: None,
            microtask_queue: MicrotaskQueue::new(),
            current_module_specifier: None,
            import_meta_object: None,
            event_loop: EventLoop::new(),
            running_event_loop: None,
            shape_cache: ShapeCache::new(),
            common_atoms: CommonAtoms::new(runtime.atom_table_mut()),
            cached_int_atoms: {
                let mut arr = [Atom::empty(); 100];
                for i in 0..100 {
                    arr[i] = runtime.atom_table_mut().intern(&i.to_string());
                }
                arr
            },
            args_length_shape: None,
        };

        builtins::init_globals(&mut ctx);

        ctx
    }

    #[inline(always)]
    pub fn check_interrupt(&mut self) -> Result<(), String> {
        self.interrupt_counter -= 1;
        if self.interrupt_counter <= 0 {
            self.interrupt_counter = Self::INTERRUPT_THRESHOLD;
            if self.runtime_mut().is_interrupted() {
                return Err("interrupted".to_string());
            }
        }
        Ok(())
    }

    #[inline(always)]
    pub fn reset_interrupt_counter(&mut self) {
        self.interrupt_counter = 0;
    }

    pub fn runtime(&self) -> &JSRuntime {
        unsafe { &*self.runtime }
    }

    pub fn runtime_mut(&mut self) -> &mut JSRuntime {
        unsafe { &mut *self.runtime }
    }

    pub fn atom_table(&self) -> &AtomTable {
        self.runtime().atom_table()
    }

    pub fn atom_table_mut(&mut self) -> &mut AtomTable {
        self.runtime_mut().atom_table_mut()
    }

    pub fn shape_cache(&self) -> &ShapeCache {
        &self.shape_cache
    }

    pub fn shape_cache_mut(&mut self) -> &mut ShapeCache {
        &mut self.shape_cache
    }

    #[inline]
    pub fn get_or_create_args_length_shape(
        &mut self,
    ) -> std::ptr::NonNull<crate::object::shape::Shape> {
        if let Some(s) = self.args_length_shape {
            return s;
        }
        let root = self.shape_cache.root_shape();
        let s = self.shape_cache.transition(root, self.common_atoms.length);
        self.args_length_shape = Some(s);
        s
    }

    pub fn global(&self) -> JSValue {
        self.global_object
    }

    pub fn set_global(&mut self, value: JSValue) {
        self.global_object = value;
    }

    pub fn intern(&mut self, s: &str) -> Atom {
        self.atom_table_mut().intern(s)
    }

    #[inline]
    pub fn intern_concat(&mut self, a: &str, b: &str) -> Atom {
        self.atom_table_mut().intern_concat(a, b)
    }

    #[inline]
    pub fn intern_concat_atoms(&mut self, a: Atom, b: Atom) -> Atom {
        self.atom_table_mut().intern_concat_atoms(a, b)
    }

    #[inline]
    pub fn int_atom(&self, n: usize) -> Atom {
        if n < 100 {
            unsafe { *self.cached_int_atoms.get_unchecked(n) }
        } else {
            self.common_atoms.empty
        }
    }

    #[inline]
    pub fn int_atom_mut(&mut self, n: usize) -> Atom {
        if n < 100 {
            unsafe { *self.cached_int_atoms.get_unchecked(n) }
        } else {
            self.atom_table_mut().intern(&n.to_string())
        }
    }

    pub fn intern_fast(&mut self, s: &str) -> Atom {
        self.atom_table_mut().intern_fast(s)
    }

    pub fn lookup_atom(&self, s: &str) -> Option<Atom> {
        self.atom_table().lookup(s)
    }

    pub fn get_atom_str(&self, atom: Atom) -> &str {
        self.atom_table().get(atom)
    }

    #[inline]
    pub fn string_char_count(&self, atom: Atom) -> usize {
        self.atom_table().char_count(atom)
    }

    #[inline]
    pub fn string_char_code_at(&self, atom: Atom, index: usize) -> Option<u32> {
        self.atom_table().char_code_at(atom, index)
    }

    pub fn mark_symbol_atom(&mut self, atom: Atom) {
        self.atom_table_mut().mark_symbol_atom(atom.index());
    }

    pub fn is_symbol_atom(&self, atom: Atom) -> bool {
        self.atom_table().is_symbol_atom(atom.index())
    }

    pub fn register_builtin(&mut self, name: &str, func: HostFunction) {
        self.builtin_functions.insert(name.to_string(), func);
    }

    pub fn register_global_builtin(
        &mut self,
        name: &'static str,
        arity: u32,
        func: crate::host::HostFunc,
    ) {
        self.register_builtin(name, HostFunction::new(name, arity, func));
        let mut f = crate::object::function::JSFunction::new_builtin(self.intern(name), arity);
        f.set_builtin_marker(self, name);
        let ptr = Box::into_raw(Box::new(f)) as usize;
        self.runtime_mut().gc_heap_mut().track_function(ptr);
        self.global()
            .as_object_mut()
            .set(self.intern(name), crate::value::JSValue::new_function(ptr));
    }

    pub fn call_builtin(&mut self, name: &str, args: &[JSValue]) -> JSValue {
        let func = self.builtin_functions.get(name).cloned();
        if let Some(f) = func {
            f.call(self, args)
        } else {
            JSValue::undefined()
        }
    }

    pub fn get_builtin_func(&self, name: &str) -> Option<crate::host::func::HostFunc> {
        self.builtin_functions.get(name).map(|f| f.func)
    }

    pub fn get_builtin_name(&self, name: &str) -> Option<&'static str> {
        self.builtin_functions.get(name).map(|f| f.name)
    }

    pub fn builtin_needs_this(&self, name: &str) -> bool {
        self.builtin_functions
            .get(name)
            .map(|f| f.needs_this)
            .unwrap_or(false)
    }

    pub fn builtin_is_constructor(&self, name: &str) -> bool {
        self.builtin_functions
            .get(name)
            .map(|f| f.is_constructor)
            .unwrap_or(false)
    }

    pub fn get_builtin_arity(&self, name: &str) -> Option<u32> {
        self.builtin_functions.get(name).map(|f| f.arity)
    }

    pub fn call_builtin_direct(
        &mut self,
        func: crate::host::func::HostFunc,
        args: &[JSValue],
    ) -> JSValue {
        func(self, args)
    }

    pub fn set_string_prototype(&mut self, ptr: usize) {
        self.string_prototype = Some(ptr);
    }

    pub fn set_number_prototype(&mut self, ptr: usize) {
        self.number_prototype = Some(ptr);
    }

    pub fn set_array_prototype(&mut self, ptr: usize) {
        self.array_prototype = Some(ptr);
    }

    pub fn set_array_iterator_prototype(&mut self, ptr: usize) {
        self.array_iterator_prototype = Some(ptr);
    }

    pub fn get_array_iterator_prototype(&self) -> Option<usize> {
        self.array_iterator_prototype
    }

    pub fn set_map_iterator_prototype(&mut self, ptr: usize) {
        self.map_iterator_prototype = Some(ptr);
    }

    pub fn get_map_iterator_prototype(&self) -> Option<usize> {
        self.map_iterator_prototype
    }

    pub fn set_set_iterator_prototype(&mut self, ptr: usize) {
        self.set_iterator_prototype = Some(ptr);
    }

    pub fn get_set_iterator_prototype(&self) -> Option<usize> {
        self.set_iterator_prototype
    }

    pub fn set_regexp_prototype(&mut self, ptr: usize) {
        self.regexp_prototype = Some(ptr);
    }

    pub fn set_object_prototype(&mut self, ptr: usize) {
        self.object_prototype = Some(ptr);
    }

    pub fn set_function_prototype(&mut self, ptr: usize) {
        self.function_prototype = Some(ptr);
    }

    pub fn set_map_prototype(&mut self, ptr: usize) {
        self.map_prototype = Some(ptr);
    }

    pub fn set_set_prototype(&mut self, ptr: usize) {
        self.set_prototype = Some(ptr);
    }

    pub fn get_string_prototype(&self) -> Option<*mut JSObject> {
        self.string_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_number_prototype(&self) -> Option<*mut JSObject> {
        self.number_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_bool_prototype(&self) -> Option<*mut JSObject> {
        self.bool_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_bool_prototype(&mut self, ptr: usize) {
        self.bool_prototype = Some(ptr);
    }

    pub fn get_bigint_prototype(&self) -> Option<*mut JSObject> {
        self.bigint_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_bigint_prototype(&mut self, ptr: usize) {
        self.bigint_prototype = Some(ptr);
    }

    pub fn get_array_prototype(&self) -> Option<*mut JSObject> {
        self.array_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_regexp_prototype(&self) -> Option<*mut JSObject> {
        self.regexp_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_object_prototype(&self) -> Option<*mut JSObject> {
        self.object_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_date_prototype(&mut self, ptr: usize) {
        self.date_prototype = Some(ptr);
    }

    pub fn get_date_prototype(&self) -> Option<*mut JSObject> {
        self.date_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_function_prototype(&self) -> Option<*mut JSObject> {
        self.function_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_map_prototype(&self) -> Option<*mut JSObject> {
        self.map_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_set_prototype(&self) -> Option<*mut JSObject> {
        self.set_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_weakmap_prototype(&mut self, ptr: usize) {
        self.weakmap_prototype = Some(ptr);
    }

    pub fn get_weakmap_prototype(&self) -> Option<*mut JSObject> {
        self.weakmap_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_weakset_prototype(&mut self, ptr: usize) {
        self.weakset_prototype = Some(ptr);
    }

    pub fn set_dataview_prototype(&mut self, ptr: usize) {
        self.dataview_prototype = Some(ptr);
    }

    pub fn set_typed_array_prototype(&mut self, ptr: usize) {
        self.typed_array_prototype = Some(ptr);
    }

    pub fn get_weakset_prototype(&self) -> Option<*mut JSObject> {
        self.weakset_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_dataview_prototype(&self) -> Option<*mut JSObject> {
        self.dataview_prototype.map(|p| p as *mut JSObject)
    }

    pub fn get_typed_array_prototype(&self) -> Option<*mut JSObject> {
        self.typed_array_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_error_prototype(&mut self, ptr: usize) {
        self.error_prototype = Some(ptr);
    }

    pub fn get_error_prototype(&self) -> Option<*mut JSObject> {
        self.error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_type_error_prototype(&mut self, ptr: usize) {
        self.type_error_prototype = Some(ptr);
    }

    pub fn get_type_error_prototype(&self) -> Option<*mut JSObject> {
        self.type_error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_reference_error_prototype(&mut self, ptr: usize) {
        self.reference_error_prototype = Some(ptr);
    }

    pub fn get_reference_error_prototype(&self) -> Option<*mut JSObject> {
        self.reference_error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_range_error_prototype(&mut self, ptr: usize) {
        self.range_error_prototype = Some(ptr);
    }

    pub fn get_range_error_prototype(&self) -> Option<*mut JSObject> {
        self.range_error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_uri_error_prototype(&mut self, ptr: usize) {
        self.uri_error_prototype = Some(ptr);
    }

    pub fn get_uri_error_prototype(&self) -> Option<*mut JSObject> {
        self.uri_error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_syntax_error_prototype(&mut self, ptr: usize) {
        self.syntax_error_prototype = Some(ptr);
    }

    pub fn get_syntax_error_prototype(&self) -> Option<*mut JSObject> {
        self.syntax_error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_eval_error_prototype(&mut self, ptr: usize) {
        self.eval_error_prototype = Some(ptr);
    }

    pub fn get_eval_error_prototype(&self) -> Option<*mut JSObject> {
        self.eval_error_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_symbol_prototype(&mut self, ptr: usize) {
        self.symbol_prototype = Some(ptr);
    }

    pub fn get_symbol_prototype(&self) -> Option<*mut JSObject> {
        self.symbol_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_weakref_prototype(&mut self, ptr: usize) {
        self.weakref_prototype = Some(ptr);
    }

    pub fn get_weakref_prototype(&self) -> Option<*mut JSObject> {
        self.weakref_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_finalization_registry_prototype(&mut self, ptr: usize) {
        self.finalization_registry_prototype = Some(ptr);
    }

    pub fn get_finalization_registry_prototype(&self) -> Option<*mut JSObject> {
        self.finalization_registry_prototype
            .map(|p| p as *mut JSObject)
    }

    pub fn set_generator_prototype(&mut self, ptr: usize) {
        self.generator_prototype = Some(ptr);
    }

    pub fn get_generator_prototype(&self) -> Option<*mut JSObject> {
        self.generator_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_async_generator_prototype(&mut self, ptr: usize) {
        self.async_generator_prototype = Some(ptr);
    }

    pub fn get_async_generator_prototype(&self) -> Option<*mut JSObject> {
        self.async_generator_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_promise_prototype(&mut self, ptr: usize) {
        self.promise_prototype = Some(ptr);
    }

    pub fn get_promise_prototype(&self) -> Option<*mut JSObject> {
        self.promise_prototype.map(|p| p as *mut JSObject)
    }

    pub fn set_register_vm_ptr(&mut self, ptr: Option<usize>) {
        self.register_vm_ptr = ptr;
    }

    pub fn get_register_vm_ptr(&self) -> Option<usize> {
        self.register_vm_ptr
    }

    pub fn add_extension(&mut self, ext: Box<dyn MacroTaskExtension>) {
        self.event_loop_mut().extensions.push(ext);
    }

    pub fn set_compiler_opt_level(&mut self, opt_level: OptLevel) {
        self.compiler_opt_level = opt_level;
    }

    pub fn get_compiler_opt_level(&self) -> OptLevel {
        self.compiler_opt_level
    }

    pub fn set_pending_callback(&mut self, callback: PendingCallback) {
        self.pending_callback = Some(callback);
    }

    pub fn take_pending_callback(&mut self) -> Option<PendingCallback> {
        self.pending_callback.take()
    }

    pub fn has_pending_callback(&self) -> bool {
        self.pending_callback.is_some()
    }

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

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

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

    pub fn set_current_module(&mut self, specifier: Option<String>) {
        self.current_module_specifier = specifier;
    }

    pub fn get_current_module(&self) -> Option<&str> {
        self.current_module_specifier.as_deref()
    }

    pub fn get_symbol_registry_ptr(&self) -> Option<usize> {
        self.runtime().symbol_registry()
    }

    pub fn set_symbol_registry_ptr(&mut self, ptr: Option<usize>) {
        self.runtime_mut().set_symbol_registry(ptr);
    }

    pub fn set_import_meta(&mut self, ptr: Option<usize>) {
        self.import_meta_object = ptr;
    }

    pub fn get_import_meta(&self) -> Option<usize> {
        self.import_meta_object
    }

    pub fn event_loop(&self) -> &EventLoop {
        &self.event_loop
    }

    pub fn event_loop_mut(&mut self) -> &mut EventLoop {
        if let Some(ptr) = self.running_event_loop {
            unsafe { &mut *ptr }
        } else {
            &mut self.event_loop
        }
    }

    pub fn run_event_loop(&mut self) -> Result<super::event_loop::EventLoopResult, String> {
        let mut event_loop = std::mem::take(&mut self.event_loop);

        self.running_event_loop = Some(&mut event_loop as *mut EventLoop);
        let result = event_loop.run_until_complete(self, None);
        self.running_event_loop = None;
        self.event_loop = event_loop;
        result
    }

    pub fn run_event_loop_with_timeout(
        &mut self,
        timeout_ms: u64,
    ) -> Result<super::event_loop::EventLoopResult, String> {
        let mut event_loop = std::mem::take(&mut self.event_loop);

        self.running_event_loop = Some(&mut event_loop as *mut EventLoop);
        let result = event_loop.run_until_complete(self, Some(timeout_ms));
        self.running_event_loop = None;
        self.event_loop = event_loop;
        result
    }
}
