/*
 * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
 * Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibGC/Root.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/AggregateErrorConstructor.h>
#include <LibJS/Runtime/AggregateErrorPrototype.h>
#include <LibJS/Runtime/ArrayBufferConstructor.h>
#include <LibJS/Runtime/ArrayBufferPrototype.h>
#include <LibJS/Runtime/ArrayConstructor.h>
#include <LibJS/Runtime/ArrayIteratorPrototype.h>
#include <LibJS/Runtime/ArrayPrototype.h>
#include <LibJS/Runtime/AsyncDisposableStackConstructor.h>
#include <LibJS/Runtime/AsyncDisposableStackPrototype.h>
#include <LibJS/Runtime/AsyncFromSyncIteratorPrototype.h>
#include <LibJS/Runtime/AsyncFunctionConstructor.h>
#include <LibJS/Runtime/AsyncFunctionPrototype.h>
#include <LibJS/Runtime/AsyncGeneratorFunctionConstructor.h>
#include <LibJS/Runtime/AsyncGeneratorFunctionPrototype.h>
#include <LibJS/Runtime/AsyncGeneratorPrototype.h>
#include <LibJS/Runtime/AsyncIteratorPrototype.h>
#include <LibJS/Runtime/AtomicsObject.h>
#include <LibJS/Runtime/BigIntConstructor.h>
#include <LibJS/Runtime/BigIntPrototype.h>
#include <LibJS/Runtime/BooleanConstructor.h>
#include <LibJS/Runtime/BooleanPrototype.h>
#include <LibJS/Runtime/ConsoleObject.h>
#include <LibJS/Runtime/DataViewConstructor.h>
#include <LibJS/Runtime/DataViewPrototype.h>
#include <LibJS/Runtime/DateConstructor.h>
#include <LibJS/Runtime/DatePrototype.h>
#include <LibJS/Runtime/DisposableStackConstructor.h>
#include <LibJS/Runtime/DisposableStackPrototype.h>
#include <LibJS/Runtime/ErrorConstructor.h>
#include <LibJS/Runtime/ErrorPrototype.h>
#include <LibJS/Runtime/FinalizationRegistryConstructor.h>
#include <LibJS/Runtime/FinalizationRegistryPrototype.h>
#include <LibJS/Runtime/FunctionConstructor.h>
#include <LibJS/Runtime/FunctionPrototype.h>
#include <LibJS/Runtime/GeneratorFunctionConstructor.h>
#include <LibJS/Runtime/GeneratorFunctionPrototype.h>
#include <LibJS/Runtime/GeneratorPrototype.h>
#include <LibJS/Runtime/Intl/Collator.h>
#include <LibJS/Runtime/Intl/CollatorConstructor.h>
#include <LibJS/Runtime/Intl/CollatorPrototype.h>
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
#include <LibJS/Runtime/Intl/DateTimeFormatPrototype.h>
#include <LibJS/Runtime/Intl/DisplayNamesConstructor.h>
#include <LibJS/Runtime/Intl/DisplayNamesPrototype.h>
#include <LibJS/Runtime/Intl/DurationFormatConstructor.h>
#include <LibJS/Runtime/Intl/DurationFormatPrototype.h>
#include <LibJS/Runtime/Intl/Intl.h>
#include <LibJS/Runtime/Intl/ListFormatConstructor.h>
#include <LibJS/Runtime/Intl/ListFormatPrototype.h>
#include <LibJS/Runtime/Intl/LocaleConstructor.h>
#include <LibJS/Runtime/Intl/LocalePrototype.h>
#include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
#include <LibJS/Runtime/Intl/NumberFormatPrototype.h>
#include <LibJS/Runtime/Intl/PluralRulesConstructor.h>
#include <LibJS/Runtime/Intl/PluralRulesPrototype.h>
#include <LibJS/Runtime/Intl/RelativeTimeFormatConstructor.h>
#include <LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h>
#include <LibJS/Runtime/Intl/SegmentIteratorPrototype.h>
#include <LibJS/Runtime/Intl/SegmenterConstructor.h>
#include <LibJS/Runtime/Intl/SegmenterPrototype.h>
#include <LibJS/Runtime/Intl/SegmentsPrototype.h>
#include <LibJS/Runtime/Intrinsics.h>
#include <LibJS/Runtime/IteratorConstructor.h>
#include <LibJS/Runtime/IteratorHelperPrototype.h>
#include <LibJS/Runtime/IteratorPrototype.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/MapConstructor.h>
#include <LibJS/Runtime/MapIteratorPrototype.h>
#include <LibJS/Runtime/MapPrototype.h>
#include <LibJS/Runtime/MathObject.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/NumberConstructor.h>
#include <LibJS/Runtime/NumberPrototype.h>
#include <LibJS/Runtime/ObjectConstructor.h>
#include <LibJS/Runtime/ObjectPrototype.h>
#include <LibJS/Runtime/PromiseConstructor.h>
#include <LibJS/Runtime/PromisePrototype.h>
#include <LibJS/Runtime/ProxyConstructor.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/ReflectObject.h>
#include <LibJS/Runtime/RegExpConstructor.h>
#include <LibJS/Runtime/RegExpPrototype.h>
#include <LibJS/Runtime/RegExpStringIteratorPrototype.h>
#include <LibJS/Runtime/SetConstructor.h>
#include <LibJS/Runtime/SetIteratorPrototype.h>
#include <LibJS/Runtime/SetPrototype.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/SharedArrayBufferPrototype.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/Runtime/StringConstructor.h>
#include <LibJS/Runtime/StringIteratorPrototype.h>
#include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/SuppressedErrorConstructor.h>
#include <LibJS/Runtime/SuppressedErrorPrototype.h>
#include <LibJS/Runtime/SymbolConstructor.h>
#include <LibJS/Runtime/SymbolPrototype.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
#include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTimePrototype.h>
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
#include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainTimePrototype.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
#include <LibJS/Runtime/Temporal/Temporal.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimePrototype.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/TypedArrayConstructor.h>
#include <LibJS/Runtime/TypedArrayPrototype.h>
#include <LibJS/Runtime/WeakMapConstructor.h>
#include <LibJS/Runtime/WeakMapPrototype.h>
#include <LibJS/Runtime/WeakRefConstructor.h>
#include <LibJS/Runtime/WeakRefPrototype.h>
#include <LibJS/Runtime/WeakSetConstructor.h>
#include <LibJS/Runtime/WeakSetPrototype.h>
#include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
#include <LibJS/RustIntegration.h>

// FIXME: Remove this asm hack when we upgrade to GCC 15.
#define INCLUDE_FILE_WITH_ASSEMBLY(name, file_path) \
    asm(".global " #name "\n" #name ":\n"           \
        ".incbin " #file_path "\n"                  \
        ".global " #name "_END\n" #name "_END:\n"   \
        ".byte 0\n");                               \
    extern unsigned char const name[];

#if defined(AK_COMPILER_CLANG) || (defined(AK_COMPILER_GCC) && (__GNUC__ > 14))
static constexpr unsigned char ABSTRACT_OPERATIONS[] = {
#    embed "JavaScriptImplementations/AbstractOperations.js" suffix(, )
    0 // null terminator
};
#else
INCLUDE_FILE_WITH_ASSEMBLY(ABSTRACT_OPERATIONS, "LibJS/Runtime/JavaScriptImplementations/AbstractOperations.js")
#endif

#if defined(AK_COMPILER_CLANG) || (defined(AK_COMPILER_GCC) && (__GNUC__ > 14))
static constexpr unsigned char ARRAY_CONSTRUCTOR[] = {
#    embed "JavaScriptImplementations/ArrayConstructor.js" suffix(, )
    0 // null terminator
};
#else
INCLUDE_FILE_WITH_ASSEMBLY(ARRAY_CONSTRUCTOR, "LibJS/Runtime/JavaScriptImplementations/ArrayConstructor.js")
#endif

namespace JS {

GC_DEFINE_ALLOCATOR(Intrinsics);

static void initialize_constructor(VM& vm, PropertyKey const& property_key, Object& constructor, Object* prototype, PropertyAttributes constructor_property_attributes = Attribute::Writable | Attribute::Configurable)
{
    constructor.define_direct_property(vm.names.name, PrimitiveString::create(vm, property_key.as_string()), Attribute::Configurable);
    if (prototype)
        prototype->define_direct_property(vm.names.constructor, &constructor, constructor_property_attributes);
}

// 9.3.2 CreateIntrinsics ( realmRec ), https://tc39.es/ecma262/#sec-createintrinsics
GC::Ref<Intrinsics> Intrinsics::create(Realm& realm)
{
    auto& vm = realm.vm();

    // 1. Set realmRec.[[Intrinsics]] to a new Record.
    auto intrinsics = vm.heap().allocate<Intrinsics>(realm);
    realm.set_intrinsics({}, intrinsics);

    // 2. Set fields of realmRec.[[Intrinsics]] with the values listed in Table 6.
    //    The field names are the names listed in column one of the table.
    //    The value of each field is a new object value fully and recursively populated
    //    with property values as defined by the specification of each object in
    //    clauses 19 through 28. All object property values are newly created object
    //    values. All values that are built-in function objects are created by performing
    //    CreateBuiltinFunction(steps, length, name, slots, realmRec, prototype)
    //    where steps is the definition of that function provided by this specification,
    //    name is the initial value of the function's "name" property, length is the
    //    initial value of the function's "length" property, slots is a list of the
    //    names, if any, of the function's specified internal slots, and prototype
    //    is the specified value of the function's [[Prototype]] internal slot. The
    //    creation of the intrinsics and their properties must be ordered to avoid
    //    any dependencies upon objects that have not yet been created.
    intrinsics->initialize_intrinsics(realm);

    // 3. Perform AddRestrictedFunctionProperties(realmRec.[[Intrinsics]].[[%Function.prototype%]], realmRec).
    add_restricted_function_properties(static_cast<FunctionObject&>(*realm.intrinsics().function_prototype()), realm);

    // 4. Return unused.
    return *intrinsics;
}

static Vector<GC::Root<SharedFunctionInstanceData>> parse_builtin_file(unsigned char const* script_text, VM& vm)
{
    auto rust_compilation = RustIntegration::compile_builtin_file(script_text, vm);
    VERIFY(rust_compilation.has_value());
    return move(rust_compilation.value());
}

void Intrinsics::initialize_intrinsics(Realm& realm)
{
    auto& vm = this->vm();

    // These are done first since other prototypes depend on their presence.
    m_empty_object_shape = heap().allocate<Shape>(realm);
    m_object_prototype = heap().allocate<ObjectPrototype>(realm);
    m_object_prototype->convert_to_prototype_if_needed();
    m_function_prototype = heap().allocate<FunctionPrototype>(realm);
    m_function_prototype->convert_to_prototype_if_needed();

    m_new_object_shape = heap().allocate<Shape>(realm);
    m_new_object_shape->set_prototype_without_transition(m_object_prototype);

    // OPTIMIZATION: A lot of runtime algorithms create an "iterator result" object.
    //               We pre-bake a shape for these objects and remember the property offsets.
    //               This allows us to construct them very quickly.
    m_iterator_result_object_shape = heap().allocate<Shape>(realm);
    m_iterator_result_object_shape->set_prototype_without_transition(m_object_prototype);
    m_iterator_result_object_shape->add_property_without_transition(vm.names.value, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
    m_iterator_result_object_shape->add_property_without_transition(vm.names.done, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
    m_iterator_result_object_value_offset = m_iterator_result_object_shape->lookup(vm.names.value).value().offset;
    m_iterator_result_object_done_offset = m_iterator_result_object_shape->lookup(vm.names.done).value().offset;

    m_normal_function_prototype_shape = heap().allocate<Shape>(realm);
    m_normal_function_prototype_shape->set_prototype_without_transition(m_object_prototype);
    m_normal_function_prototype_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable);
    m_normal_function_prototype_constructor_offset = m_normal_function_prototype_shape->lookup(vm.names.constructor).value().offset;

    m_normal_function_shape = heap().allocate<Shape>(realm);
    m_normal_function_shape->set_prototype_without_transition(m_function_prototype);
    m_normal_function_shape->add_property_without_transition(vm.names.length, Attribute::Configurable);
    m_normal_function_shape->add_property_without_transition(vm.names.name, Attribute::Configurable);
    m_normal_function_length_offset = m_normal_function_shape->lookup(vm.names.length).value().offset;
    m_normal_function_name_offset = m_normal_function_shape->lookup(vm.names.name).value().offset;

    m_native_function_shape = heap().allocate<Shape>(realm);
    m_native_function_shape->set_prototype_without_transition(m_function_prototype);
    m_native_function_shape->add_property_without_transition(vm.names.length, Attribute::Configurable);
    m_native_function_shape->add_property_without_transition(vm.names.name, Attribute::Configurable);
    m_native_function_length_offset = m_native_function_shape->lookup(vm.names.length).value().offset;
    m_native_function_name_offset = m_native_function_shape->lookup(vm.names.name).value().offset;

    m_unmapped_arguments_object_shape = heap().allocate<Shape>(realm);
    m_unmapped_arguments_object_shape->set_prototype_without_transition(m_object_prototype);
    m_unmapped_arguments_object_shape->set_has_parameter_map();
    m_unmapped_arguments_object_shape->add_property_without_transition(vm.names.length, Attribute::Writable | Attribute::Configurable);
    m_unmapped_arguments_object_shape->add_property_without_transition(vm.well_known_symbol_iterator(), Attribute::Writable | Attribute::Configurable);
    m_unmapped_arguments_object_shape->add_property_without_transition(vm.names.callee, 0);
    m_unmapped_arguments_object_length_offset = m_unmapped_arguments_object_shape->lookup(vm.names.length).value().offset;
    m_unmapped_arguments_object_well_known_symbol_iterator_offset = m_unmapped_arguments_object_shape->lookup(vm.well_known_symbol_iterator()).value().offset;
    m_unmapped_arguments_object_callee_offset = m_unmapped_arguments_object_shape->lookup(vm.names.callee).value().offset;

    m_mapped_arguments_object_shape = heap().allocate<Shape>(realm);
    m_mapped_arguments_object_shape->set_prototype_without_transition(m_object_prototype);
    m_mapped_arguments_object_shape->set_has_parameter_map();
    m_mapped_arguments_object_shape->add_property_without_transition(vm.names.length, Attribute::Writable | Attribute::Configurable);
    m_mapped_arguments_object_shape->add_property_without_transition(vm.well_known_symbol_iterator(), Attribute::Writable | Attribute::Configurable);
    m_mapped_arguments_object_shape->add_property_without_transition(vm.names.callee, Attribute::Writable | Attribute::Configurable);
    m_mapped_arguments_object_length_offset = m_mapped_arguments_object_shape->lookup(vm.names.length).value().offset;
    m_mapped_arguments_object_well_known_symbol_iterator_offset = m_mapped_arguments_object_shape->lookup(vm.well_known_symbol_iterator()).value().offset;
    m_mapped_arguments_object_callee_offset = m_mapped_arguments_object_shape->lookup(vm.names.callee).value().offset;

    // Normally Realm::create() takes care of this, but these are allocated via Heap::allocate().
    m_function_prototype->initialize(realm);
    m_object_prototype->initialize(realm);

#define __JS_ENUMERATE(ClassName, snake_name) \
    VERIFY(!m_##snake_name##_prototype);      \
    m_##snake_name##_prototype = realm.create<ClassName##Prototype>(realm);
    JS_ENUMERATE_ITERATOR_PROTOTYPES
#undef __JS_ENUMERATE

    // These must be initialized separately as they have no companion constructor
    m_async_from_sync_iterator_prototype = realm.create<AsyncFromSyncIteratorPrototype>(realm);
    m_async_generator_prototype = realm.create<AsyncGeneratorPrototype>(realm);
    m_generator_prototype = realm.create<GeneratorPrototype>(realm);
    m_intl_segments_prototype = realm.create<Intl::SegmentsPrototype>(realm);
    m_wrap_for_valid_iterator_prototype = realm.create<WrapForValidIteratorPrototype>(realm);

    // These must be initialized before allocating...
    // - AggregateErrorPrototype, which uses ErrorPrototype as its prototype
    // - AggregateErrorConstructor, which uses ErrorConstructor as its prototype
    // - AsyncFunctionConstructor, which uses FunctionConstructor as its prototype
    m_error_prototype = realm.create<ErrorPrototype>(realm);
    m_error_constructor = realm.create<ErrorConstructor>(realm);
    m_function_constructor = realm.create<FunctionConstructor>(realm);

    // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
    m_proxy_constructor = realm.create<ProxyConstructor>(realm);

    // Global object functions
    m_eval_function = NativeFunction::create(realm, GlobalObject::eval, 1, vm.names.eval, &realm);
    m_is_finite_function = NativeFunction::create(realm, GlobalObject::is_finite, 1, vm.names.isFinite, &realm);
    m_is_nan_function = NativeFunction::create(realm, GlobalObject::is_nan, 1, vm.names.isNaN, &realm);
    m_parse_float_function = NativeFunction::create(realm, GlobalObject::parse_float, 1, vm.names.parseFloat, &realm);
    m_parse_int_function = NativeFunction::create(realm, GlobalObject::parse_int, 2, vm.names.parseInt, &realm);
    m_decode_uri_function = NativeFunction::create(realm, GlobalObject::decode_uri, 1, vm.names.decodeURI, &realm);
    m_decode_uri_component_function = NativeFunction::create(realm, GlobalObject::decode_uri_component, 1, vm.names.decodeURIComponent, &realm);
    m_encode_uri_function = NativeFunction::create(realm, GlobalObject::encode_uri, 1, vm.names.encodeURI, &realm);
    m_encode_uri_component_function = NativeFunction::create(realm, GlobalObject::encode_uri_component, 1, vm.names.encodeURIComponent, &realm);
    m_escape_function = NativeFunction::create(realm, GlobalObject::escape, 1, vm.names.escape, &realm);
    m_unescape_function = NativeFunction::create(realm, GlobalObject::unescape, 1, vm.names.unescape, &realm);

    m_object_constructor = realm.create<ObjectConstructor>(realm);

    // 10.2.4.1 %ThrowTypeError% ( ), https://tc39.es/ecma262/#sec-%throwtypeerror%
    m_throw_type_error_function = NativeFunction::create(
        realm, [](VM& vm) {
            return vm.throw_completion<TypeError>(ErrorType::RestrictedFunctionPropertiesAccess);
        },
        0, Utf16FlyString {}, &realm);
    m_throw_type_error_function->define_direct_property(vm.names.length, Value(0), 0);
    m_throw_type_error_function->define_direct_property(vm.names.name, PrimitiveString::create(vm, String {}), 0);
    MUST(m_throw_type_error_function->internal_prevent_extensions());

    m_throw_type_error_accessor = Accessor::create(
        vm,
        m_throw_type_error_function,
        m_throw_type_error_function);

    initialize_constructor(vm, vm.names.Error, *m_error_constructor, m_error_prototype);
    initialize_constructor(vm, vm.names.Function, *m_function_constructor, m_function_prototype);
    initialize_constructor(vm, vm.names.Object, *m_object_constructor, m_object_prototype);
    initialize_constructor(vm, vm.names.Proxy, *m_proxy_constructor, nullptr);

    initialize_constructor(vm, vm.names.GeneratorFunction, *generator_function_constructor(), generator_function_prototype(), Attribute::Configurable);
    initialize_constructor(vm, vm.names.AsyncGeneratorFunction, *async_generator_function_constructor(), async_generator_function_prototype(), Attribute::Configurable);
    initialize_constructor(vm, vm.names.AsyncFunction, *async_function_constructor(), async_function_prototype(), Attribute::Configurable);

    // 27.5.1.1 Generator.prototype.constructor, https://tc39.es/ecma262/#sec-generator.prototype.constructor
    m_generator_prototype->define_direct_property(vm.names.constructor, m_generator_function_prototype, Attribute::Configurable);

    // 27.6.1.1 AsyncGenerator.prototype.constructor, https://tc39.es/ecma262/#sec-asyncgenerator-prototype-constructor
    m_async_generator_prototype->define_direct_property(vm.names.constructor, m_async_generator_function_prototype, Attribute::Configurable);

    m_array_prototype_values_function = &array_prototype()->get_without_side_effects(vm.names.values).as_function();
    m_date_constructor_now_function = &date_constructor()->get_without_side_effects(vm.names.now).as_function();
    m_json_parse_function = &json_object()->get_without_side_effects(vm.names.parse).as_function();
    m_json_stringify_function = &json_object()->get_without_side_effects(vm.names.stringify).as_function();
    m_object_prototype_to_string_function = &object_prototype()->get_without_side_effects(vm.names.toString).as_function();

    array_prototype()->convert_to_prototype_if_needed();
    m_default_array_prototype_shape = array_prototype()->shape();
    m_default_object_prototype_shape = object_prototype()->shape();

    VERIFY(array_prototype()->indexed_array_like_size() == 0);
    VERIFY(object_prototype()->indexed_array_like_size() == 0);

    m_regexp_builtin_exec_array_shape = heap().allocate<Shape>(realm);
    m_regexp_builtin_exec_array_shape->set_prototype_without_transition(realm.intrinsics().array_prototype());
    m_regexp_builtin_exec_array_shape->add_property_without_transition(vm.names.index, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
    m_regexp_builtin_exec_array_shape->add_property_without_transition(vm.names.input, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
    m_regexp_builtin_exec_array_shape->add_property_without_transition(vm.names.groups, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
    m_regexp_builtin_exec_array_index_offset = m_regexp_builtin_exec_array_shape->lookup(vm.names.index).value().offset;
    m_regexp_builtin_exec_array_input_offset = m_regexp_builtin_exec_array_shape->lookup(vm.names.input).value().offset;
    m_regexp_builtin_exec_array_groups_offset = m_regexp_builtin_exec_array_shape->lookup(vm.names.groups).value().offset;
}

template<typename T>
constexpr inline bool IsTypedArrayConstructor = false;

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
    template<>                                                                           \
    constexpr inline bool IsTypedArrayConstructor<ConstructorName> = true;
JS_ENUMERATE_TYPED_ARRAYS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE_INNER(ClassName, snake_name, PrototypeName, ConstructorName, Namespace, snake_namespace)                                          \
    void Intrinsics::initialize_##snake_namespace##snake_name()                                                                                          \
    {                                                                                                                                                    \
        auto& vm = this->vm();                                                                                                                           \
                                                                                                                                                         \
        VERIFY(!m_##snake_namespace##snake_name##_prototype);                                                                                            \
        VERIFY(!m_##snake_namespace##snake_name##_constructor);                                                                                          \
        if constexpr (IsTypedArrayConstructor<Namespace::ConstructorName>) {                                                                             \
            m_##snake_namespace##snake_name##_prototype = m_realm->create<Namespace::PrototypeName>(*typed_array_prototype());                           \
            m_##snake_namespace##snake_name##_constructor = m_realm->create<Namespace::ConstructorName>(m_realm, *typed_array_constructor());            \
        } else {                                                                                                                                         \
            m_##snake_namespace##snake_name##_prototype = m_realm->create<Namespace::PrototypeName>(m_realm);                                            \
            m_##snake_namespace##snake_name##_constructor = m_realm->create<Namespace::ConstructorName>(m_realm);                                        \
        }                                                                                                                                                \
                                                                                                                                                         \
        /* FIXME: Add these special cases to JS_ENUMERATE_NATIVE_OBJECTS */                                                                              \
        if constexpr (IsSame<Namespace::ConstructorName, BigIntConstructor>)                                                                             \
            initialize_constructor(vm, vm.names.BigInt, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);    \
        else if constexpr (IsSame<Namespace::ConstructorName, BooleanConstructor>)                                                                       \
            initialize_constructor(vm, vm.names.Boolean, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);   \
        else if constexpr (IsSame<Namespace::ConstructorName, FunctionConstructor>)                                                                      \
            initialize_constructor(vm, vm.names.Function, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);  \
        else if constexpr (IsSame<Namespace::ConstructorName, IteratorConstructor>)                                                                      \
            initialize_constructor(vm, vm.names.Iterator, *m_##snake_namespace##snake_name##_constructor, nullptr);                                      \
        else if constexpr (IsSame<Namespace::ConstructorName, NumberConstructor>)                                                                        \
            initialize_constructor(vm, vm.names.Number, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);    \
        else if constexpr (IsSame<Namespace::ConstructorName, RegExpConstructor>)                                                                        \
            initialize_constructor(vm, vm.names.RegExp, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);    \
        else if constexpr (IsSame<Namespace::ConstructorName, StringConstructor>)                                                                        \
            initialize_constructor(vm, vm.names.String, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);    \
        else if constexpr (IsSame<Namespace::ConstructorName, SymbolConstructor>)                                                                        \
            initialize_constructor(vm, vm.names.Symbol, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);    \
        else                                                                                                                                             \
            initialize_constructor(vm, vm.names.ClassName, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype); \
    }                                                                                                                                                    \
                                                                                                                                                         \
    GC::Ref<Namespace::ConstructorName> Intrinsics::snake_namespace##snake_name##_constructor()                                                          \
    {                                                                                                                                                    \
        if (!m_##snake_namespace##snake_name##_constructor)                                                                                              \
            initialize_##snake_namespace##snake_name();                                                                                                  \
        return *m_##snake_namespace##snake_name##_constructor;                                                                                           \
    }                                                                                                                                                    \
                                                                                                                                                         \
    GC::Ref<Object> Intrinsics::snake_namespace##snake_name##_prototype()                                                                                \
    {                                                                                                                                                    \
        if (!m_##snake_namespace##snake_name##_prototype)                                                                                                \
            initialize_##snake_namespace##snake_name();                                                                                                  \
        return *m_##snake_namespace##snake_name##_prototype;                                                                                             \
    }

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
    __JS_ENUMERATE_INNER(ClassName, snake_name, PrototypeName, ConstructorName, JS, )
JS_ENUMERATE_BUILTIN_TYPES
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \
    __JS_ENUMERATE_INNER(ClassName, snake_name, PrototypeName, ConstructorName, JS::Intl, intl_)
JS_ENUMERATE_INTL_OBJECTS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \
    __JS_ENUMERATE_INNER(ClassName, snake_name, PrototypeName, ConstructorName, JS::Temporal, temporal_)
JS_ENUMERATE_TEMPORAL_OBJECTS
#undef __JS_ENUMERATE

#undef __JS_ENUMERATE_INNER

#define __JS_ENUMERATE(ClassName, snake_name)                              \
    GC::Ref<ClassName> Intrinsics::snake_name##_object()                   \
    {                                                                      \
        if (!m_##snake_name##_object)                                      \
            m_##snake_name##_object = m_realm->create<ClassName>(m_realm); \
        return *m_##snake_name##_object;                                   \
    }
JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS
#undef __JS_ENUMERATE

void Intrinsics::visit_edges(Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_realm);
    visitor.visit(m_empty_object_shape);
    visitor.visit(m_new_object_shape);
    visitor.visit(m_iterator_result_object_shape);
    visitor.visit(m_normal_function_prototype_shape);
    visitor.visit(m_normal_function_shape);
    visitor.visit(m_native_function_shape);
    visitor.visit(m_unmapped_arguments_object_shape);
    visitor.visit(m_mapped_arguments_object_shape);
    visitor.visit(m_regexp_builtin_exec_array_shape);
    visitor.visit(m_default_array_prototype_shape);
    visitor.visit(m_default_object_prototype_shape);
    visitor.visit(m_proxy_constructor);
    visitor.visit(m_async_from_sync_iterator_prototype);
    visitor.visit(m_async_generator_prototype);
    visitor.visit(m_generator_prototype);
    visitor.visit(m_intl_segments_prototype);
    visitor.visit(m_wrap_for_valid_iterator_prototype);
    visitor.visit(m_eval_function);
    visitor.visit(m_is_finite_function);
    visitor.visit(m_is_nan_function);
    visitor.visit(m_parse_float_function);
    visitor.visit(m_parse_int_function);
    visitor.visit(m_decode_uri_function);
    visitor.visit(m_decode_uri_component_function);
    visitor.visit(m_encode_uri_function);
    visitor.visit(m_encode_uri_component_function);
    visitor.visit(m_escape_function);
    visitor.visit(m_unescape_function);
    visitor.visit(m_array_prototype_values_function);
    visitor.visit(m_date_constructor_now_function);
    visitor.visit(m_eval_function);
    visitor.visit(m_json_parse_function);
    visitor.visit(m_json_stringify_function);
    visitor.visit(m_object_prototype_to_string_function);
    visitor.visit(m_throw_type_error_function);
    visitor.visit(m_throw_type_error_accessor);

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
    visitor.visit(m_##snake_name##_constructor);                                         \
    visitor.visit(m_##snake_name##_prototype);
    JS_ENUMERATE_BUILTIN_TYPES
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \
    visitor.visit(m_intl_##snake_name##_constructor);                         \
    visitor.visit(m_intl_##snake_name##_prototype);
    JS_ENUMERATE_INTL_OBJECTS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \
    visitor.visit(m_temporal_##snake_name##_constructor);                     \
    visitor.visit(m_temporal_##snake_name##_prototype);
    JS_ENUMERATE_TEMPORAL_OBJECTS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(ClassName, snake_name) \
    visitor.visit(m_##snake_name##_object);
    JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(ClassName, snake_name) \
    visitor.visit(m_##snake_name##_prototype);
    JS_ENUMERATE_ITERATOR_PROTOTYPES
#undef __JS_ENUMERATE

    visitor.visit(m_default_collator);

#define __JS_ENUMERATE(snake_name, functionName, length) \
    visitor.visit(m_##snake_name##_abstract_operation_function);
    JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(snake_name, functionName, length) \
    visitor.visit(m_##snake_name##_array_constructor_function);
    JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS
#undef __JS_ENUMERATE
}

GC::Ref<Intl::Collator> Intrinsics::default_collator()
{
    if (!m_default_collator) {
        m_default_collator = as<Intl::Collator>(*MUST(construct(this->vm(), intl_collator_constructor(), js_undefined(), js_undefined())));
    }
    return *m_default_collator;
}

#define __JS_ENUMERATE(snake_name, functionName, length)                                                                                                                                                        \
    GC::Ref<NativeJavaScriptBackedFunction> Intrinsics::snake_name##_abstract_operation_function()                                                                                                              \
    {                                                                                                                                                                                                           \
        if (!m_##snake_name##_abstract_operation_function) {                                                                                                                                                    \
            auto shared_data_list = parse_builtin_file(ABSTRACT_OPERATIONS, m_realm->vm());                                                                                                                     \
            auto it = shared_data_list.find_if([](auto const& shared_data) {                                                                                                                                    \
                return shared_data->m_name == #functionName##sv;                                                                                                                                                \
            });                                                                                                                                                                                                 \
            VERIFY(!it.is_end());                                                                                                                                                                               \
            m_##snake_name##_abstract_operation_function = NativeJavaScriptBackedFunction::create(m_realm, **it, PropertyKey { #functionName##_utf16_fly_string, PropertyKey::StringMayBeNumber::No }, length); \
        }                                                                                                                                                                                                       \
        return *m_##snake_name##_abstract_operation_function;                                                                                                                                                   \
    }
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE

#define __JS_ENUMERATE(snake_name, functionName, length)                                                                                                                                                       \
    GC::Ref<NativeJavaScriptBackedFunction> Intrinsics::snake_name##_array_constructor_function()                                                                                                              \
    {                                                                                                                                                                                                          \
        if (!m_##snake_name##_array_constructor_function) {                                                                                                                                                    \
            auto shared_data_list = parse_builtin_file(ARRAY_CONSTRUCTOR, m_realm->vm());                                                                                                                      \
            auto it = shared_data_list.find_if([](auto const& shared_data) {                                                                                                                                   \
                return shared_data->m_name == #functionName##sv;                                                                                                                                               \
            });                                                                                                                                                                                                \
            VERIFY(!it.is_end());                                                                                                                                                                              \
            m_##snake_name##_array_constructor_function = NativeJavaScriptBackedFunction::create(m_realm, **it, PropertyKey { #functionName##_utf16_fly_string, PropertyKey::StringMayBeNumber::No }, length); \
        }                                                                                                                                                                                                      \
        return *m_##snake_name##_array_constructor_function;                                                                                                                                                   \
    }
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS
#undef __JS_ENUMERATE

// 10.2.4 AddRestrictedFunctionProperties ( F, realm ), https://tc39.es/ecma262/#sec-addrestrictedfunctionproperties
void add_restricted_function_properties(FunctionObject& function, Realm& realm)
{
    auto& vm = realm.vm();

    // 1. Assert: realm.[[Intrinsics]].[[%ThrowTypeError%]] exists and has been initialized.
    // NOTE: This is ensured by dereferencing the GCPtr in the getter.

    // 2. Let thrower be realm.[[Intrinsics]].[[%ThrowTypeError%]].
    auto thrower = realm.intrinsics().throw_type_error_function();

    // 3. Perform ! DefinePropertyOrThrow(F, "caller", PropertyDescriptor { [[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: true }).
    function.define_direct_accessor(vm.names.caller, thrower, thrower, Attribute::Configurable);

    // 4. Perform ! DefinePropertyOrThrow(F, "arguments", PropertyDescriptor { [[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: true }).
    function.define_direct_accessor(vm.names.arguments, thrower, thrower, Attribute::Configurable);

    // 5. Return unused.
}

}
