/*
 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/QuickSort.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/ModuleEnvironment.h>
#include <LibJS/SourceTextModule.h>

namespace JS {

// 16.2.1.3 Static Semantics: ModuleRequests, https://tc39.es/ecma262/#sec-static-semantics-modulerequests
static Vector<FlyString> module_requests(Program const& program)
{
    // A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module.
    // Note: The List is source text occurrence ordered!
    struct RequestedModuleAndSourceIndex {
        FlyString requested_module;
        u64 source_index;

        bool operator<(RequestedModuleAndSourceIndex const& rhs) const
        {
            return source_index < rhs.source_index;
        }
    };

    Vector<RequestedModuleAndSourceIndex> requested_modules_with_indices;

    for (auto const& import_statement : program.imports()) {
        requested_modules_with_indices.append({ import_statement.module_request().module_specifier.view(),
            import_statement.source_range().start.offset });
    }

    for (auto const& export_statement : program.exports()) {
        for (auto const& export_entry : export_statement.entries()) {
            if (export_entry.kind != ExportStatement::ExportEntry::Kind::ModuleRequest)
                continue;
            requested_modules_with_indices.append({ export_entry.module_request.module_specifier.view(),
                export_statement.source_range().start.offset });
        }
    }

    quick_sort(requested_modules_with_indices);

    Vector<FlyString> requested_modules_in_source_order;
    requested_modules_in_source_order.ensure_capacity(requested_modules_with_indices.size());
    for (auto& module : requested_modules_with_indices) {
        requested_modules_in_source_order.append(module.requested_module);
    }

    return requested_modules_in_source_order;
}

SourceTextModule::SourceTextModule(Realm& realm, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<FlyString> requested_modules,
    Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
    Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
    RefPtr<ExportStatement> default_export)
    : CyclicModule(realm, filename, has_top_level_await, move(requested_modules))
    , m_ecmascript_code(move(body))
    , m_execution_context(realm.heap())
    , m_import_entries(move(import_entries))
    , m_local_export_entries(move(local_export_entries))
    , m_indirect_export_entries(move(indirect_export_entries))
    , m_star_export_entries(move(star_export_entries))
    , m_default_export(move(default_export))
{
}

// 16.2.1.6.1 ParseModule ( sourceText, realm, hostDefined ), https://tc39.es/ecma262/#sec-parsemodule
Result<NonnullRefPtr<SourceTextModule>, Vector<Parser::Error>> SourceTextModule::parse(StringView source_text, Realm& realm, StringView filename)
{
    // 1. Let body be ParseText(sourceText, Module).
    auto parser = Parser(Lexer(source_text, filename), Program::Type::Module);
    auto body = parser.parse_program();

    // 2. If body is a List of errors, return body.
    if (parser.has_errors())
        return parser.errors();

    // 3. Let requestedModules be the ModuleRequests of body.
    auto requested_modules = module_requests(*body);

    // 4. Let importEntries be ImportEntries of body.
    Vector<ImportEntry> import_entries;
    for (auto const& import_statement : body->imports())
        import_entries.extend(import_statement.entries());

    // 5. Let importedBoundNames be ImportedLocalNames(importEntries).
    // Note: Since we have to potentially extract the import entry we just use importEntries
    //       In the future it might be an optimization to have a set/map of string to speed up the search.

    // 6. Let indirectExportEntries be a new empty List.
    Vector<ExportEntry> indirect_export_entries;

    // 7. Let localExportEntries be a new empty List.
    Vector<ExportEntry> local_export_entries;

    // 8. Let starExportEntries be a new empty List.
    Vector<ExportEntry> star_export_entries;

    // Note: Not in the spec but makes it easier to find the default.
    RefPtr<ExportStatement> default_export;

    // 9. Let exportEntries be ExportEntries of body.
    // 10. For each ExportEntry Record ee of exportEntries, do
    for (auto const& export_statement : body->exports()) {

        if (export_statement.is_default_export()) {
            VERIFY(!default_export);
            VERIFY(export_statement.entries().size() == 1);
            VERIFY(export_statement.has_statement());

            auto const& entry = export_statement.entries()[0];
            VERIFY(entry.kind == ExportStatement::ExportEntry::Kind::LocalExport);
            VERIFY(import_entries.find_if(
                                     [&](ImportEntry const& import_entry) {
                                         return import_entry.local_name == entry.local_or_import_name;
                                     })
                       .is_end());
            default_export = export_statement;
        }

        for (auto const& export_entry : export_statement.entries()) {

            // a. If ee.[[ModuleRequest]] is null, then
            if (export_entry.kind == ExportStatement::ExportEntry::Kind::LocalExport) {

                auto in_imported_bound_names = import_entries.find_if(
                    [&](ImportEntry const& import_entry) {
                        return import_entry.local_name == export_entry.local_or_import_name;
                    });

                // i. If ee.[[LocalName]] is not an element of importedBoundNames, then
                if (in_imported_bound_names.is_end()) {
                    // 1. Append ee to localExportEntries.
                    local_export_entries.empend(export_entry);
                }
                // ii. Else,
                else {
                    // 1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]].
                    auto& import_entry = *in_imported_bound_names;

                    // 2. If ie.[[ImportName]] is namespace-object, then
                    if (import_entry.is_namespace()) {
                        // a. NOTE: This is a re-export of an imported module namespace object.
                        // b. Append ee to localExportEntries.
                        local_export_entries.empend(export_entry);
                    }
                    // 3. Else,
                    else {
                        // a. NOTE: This is a re-export of a single name.
                        // b. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries.
                        indirect_export_entries.empend(import_entry.module_request(), import_entry.import_name, export_entry.export_name);
                    }
                }
            }
            // b. Else if ee.[[ImportName]] is all-but-default, then
            else if (export_entry.is_all_but_default()) {
                // i. Assert: ee.[[ExportName]] is null.
                VERIFY(export_entry.export_name.is_null());
                // ii. Append ee to starExportEntries.
                star_export_entries.empend(export_entry);
            }
            // c. Else,
            else {
                // i. Append ee to indirectExportEntries.
                indirect_export_entries.empend(export_entry);
            }
        }
    }

    // 11. Let async be body Contains await.
    bool async = body->has_top_level_await();

    // 12. Return Source Text Module Record {
    //          [[Realm]]: realm, [[Environment]]: empty, [[Namespace]]: empty, [[CycleRoot]]: empty, [[HasTLA]]: async,
    //          [[AsyncEvaluation]]: false, [[TopLevelCapability]]: empty, [[AsyncParentModules]]: « »,
    //          [[PendingAsyncDependencies]]: empty, [[Status]]: unlinked, [[EvaluationError]]: empty,
    //          [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[Context]]: empty, [[ImportMeta]]: empty,
    //          [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries,
    //          [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSIndex]]: empty, [[DFSAncestorIndex]]: empty }.
    // FIXME: Add HostDefined
    return adopt_ref(*new SourceTextModule(realm, filename, async, move(body), move(requested_modules), move(import_entries), move(local_export_entries), move(indirect_export_entries), move(star_export_entries), move(default_export)));
}

// 16.2.1.6.2 GetExportedNames ( [ exportStarSet ] ), https://tc39.es/ecma262/#sec-getexportednames
ThrowCompletionOr<Vector<FlyString>> SourceTextModule::get_exported_names(VM& vm, Vector<Module*> export_star_set)
{
    dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] get_export_names of {}", filename());
    // 1. If exportStarSet is not present, set exportStarSet to a new empty List.
    // Note: This is done by default argument

    // 2. If exportStarSet contains module, then
    if (export_star_set.contains_slow(this)) {
        // a. Assert: We've reached the starting point of an export * circularity.
        // FIXME: How do we check that?

        // b. Return a new empty List.
        return Vector<FlyString> {};
    }

    // 3. Append module to exportStarSet.
    export_star_set.append(this);

    // 4. Let exportedNames be a new empty List.
    Vector<FlyString> exported_names;

    // 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do
    for (auto& entry : m_local_export_entries) {
        // a. Assert: module provides the direct binding for this export.
        // FIXME: How do we check that?

        // b. Append e.[[ExportName]] to exportedNames.
        exported_names.empend(entry.export_name);
    }

    // 6. For each ExportEntry Record e of module.[[IndirectExportEntries]], do
    for (auto& entry : m_indirect_export_entries) {
        // a. Assert: module provides the direct binding for this export.
        // FIXME: How do we check that?

        // b. Append e.[[ExportName]] to exportedNames.
        exported_names.empend(entry.export_name);
    }

    // 7. For each ExportEntry Record e of module.[[StarExportEntries]], do
    for (auto& entry : m_star_export_entries) {
        // a. Let requestedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]).
        auto requested_module = TRY(vm.host_resolve_imported_module(this, entry.module_request));

        // b. Let starNames be ? requestedModule.GetExportedNames(exportStarSet).
        auto star_names = TRY(requested_module->get_exported_names(vm, export_star_set));

        // c. For each element n of starNames, do
        for (auto& name : star_names) {
            // i. If SameValue(n, "default") is false, then
            if (name != "default"sv) {
                // 1. If n is not an element of exportedNames, then
                if (!exported_names.contains_slow(name)) {
                    // a. Append n to exportedNames.
                    exported_names.empend(name);
                }
            }
        }
    }

    // 8. Return exportedNames.
    return exported_names;
}

// 16.2.1.6.4 InitializeEnvironment ( ), https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment
Completion SourceTextModule::initialize_environment(VM& vm)
{
    // 1. For each ExportEntry Record e of module.[[IndirectExportEntries]], do
    for (auto& entry : m_indirect_export_entries) {
        // a. Let resolution be ? module.ResolveExport(e.[[ExportName]]).
        auto resolution = TRY(resolve_export(vm, entry.export_name));
        // b. If resolution is null or ambiguous, throw a SyntaxError exception.
        if (!resolution.is_valid())
            return vm.throw_completion<SyntaxError>(realm().global_object(), ErrorType::InvalidOrAmbiguousExportEntry, entry.export_name);

        // c. Assert: resolution is a ResolvedBinding Record.
        VERIFY(resolution.is_valid());
    }

    // 2. Assert: All named exports from module are resolvable.
    // Note: We check all the indirect export entries above in step 1 and all
    // the local named exports are resolvable by construction.

    // 3. Let realm be module.[[Realm]].
    // 4. Assert: realm is not undefined.
    // Note: This must be true because we use a reference.

    auto& global_object = realm().global_object();

    // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
    auto* environment = vm.heap().allocate<ModuleEnvironment>(global_object, &realm().global_environment());

    // 6. Set module.[[Environment]] to env.
    set_environment(environment);

    // 7. For each ImportEntry Record in of module.[[ImportEntries]], do
    for (auto& import_entry : m_import_entries) {
        if (!import_entry.module_request().assertions.is_empty())
            return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "import statements with assertions");

        // a. Let importedModule be ! HostResolveImportedModule(module, in.[[ModuleRequest]]).
        auto imported_module = MUST(vm.host_resolve_imported_module(this, import_entry.module_request()));
        // b. NOTE: The above call cannot fail because imported module requests are a subset of module.[[RequestedModules]], and these have been resolved earlier in this algorithm.

        // c. If in.[[ImportName]] is namespace-object, then
        if (import_entry.is_namespace()) {
            // i. Let namespace be ? GetModuleNamespace(importedModule).
            auto* namespace_ = TRY(imported_module->get_module_namespace(vm));

            // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
            MUST(environment->create_immutable_binding(global_object, import_entry.local_name, true));

            // iii. Call env.InitializeBinding(in.[[LocalName]], namespace).
            environment->initialize_binding(global_object, import_entry.local_name, namespace_);
        }
        // d. Else,
        else {
            // i. Let resolution be ? importedModule.ResolveExport(in.[[ImportName]]).
            auto resolution = TRY(imported_module->resolve_export(vm, import_entry.import_name));

            // ii. If resolution is null or ambiguous, throw a SyntaxError exception.
            if (!resolution.is_valid())
                return vm.throw_completion<SyntaxError>(global_object, ErrorType::InvalidOrAmbiguousExportEntry, import_entry.import_name);

            // iii. If resolution.[[BindingName]] is namespace, then
            if (resolution.is_namespace()) {
                // 1. Let namespace be ? GetModuleNamespace(resolution.[[Module]]).
                auto* namespace_ = TRY(resolution.module->get_module_namespace(vm));

                // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
                MUST(environment->create_immutable_binding(global_object, import_entry.local_name, true));

                // 3. Call env.InitializeBinding(in.[[LocalName]], namespace).
                MUST(environment->initialize_binding(global_object, import_entry.local_name, namespace_));
            }
            // iv. Else,
            else {
                // 1. Call env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]).
                MUST(environment->create_import_binding(import_entry.local_name, resolution.module, resolution.export_name));
            }
        }
    }

    // 8. Let moduleContext be a new ECMAScript code execution context.
    // Note: this has already been created during the construction of this object.

    // 9. Set the Function of moduleContext to null.

    // 10. Assert: module.[[Realm]] is not undefined.
    // Note: This must be true because we use a reference.

    // 11. Set the Realm of moduleContext to module.[[Realm]].
    m_execution_context.realm = &realm();

    // 12. Set the ScriptOrModule of moduleContext to module.
    m_execution_context.script_or_module = this;

    // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]].
    m_execution_context.variable_environment = environment;

    // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
    m_execution_context.lexical_environment = environment;

    // 15. Set the PrivateEnvironment of moduleContext to null.

    // 16. Set module.[[Context]] to moduleContext.
    // Note: We're already working on that one.

    // 17. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
    vm.push_execution_context(m_execution_context, realm().global_object());

    // 18. Let code be module.[[ECMAScriptCode]].

    // 19. Let varDeclarations be the VarScopedDeclarations of code.
    // Note: We just loop through them in step 21.

    // 20. Let declaredVarNames be a new empty List.
    Vector<FlyString> declared_var_names;

    // 21. For each element d of varDeclarations, do
    // a. For each element dn of the BoundNames of d, do
    m_ecmascript_code->for_each_var_declared_name([&](auto const& name) {
        // i. If dn is not an element of declaredVarNames, then
        if (!declared_var_names.contains_slow(name)) {
            // 1. Perform ! env.CreateMutableBinding(dn, false).
            MUST(environment->create_mutable_binding(global_object, name, false));

            // 2. Call env.InitializeBinding(dn, undefined).
            MUST(environment->initialize_binding(global_object, name, js_undefined()));

            // 3. Append dn to declaredVarNames.
            declared_var_names.empend(name);
        }
    });

    // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code.
    // Note: We only loop through them in step 24.

    // 23. Let privateEnv be null.
    PrivateEnvironment* private_environment = nullptr;

    // 24. For each element d of lexDeclarations, do
    m_ecmascript_code->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
        // a. For each element dn of the BoundNames of d, do
        declaration.for_each_bound_name([&](FlyString const& name) {
            // i. If IsConstantDeclaration of d is true, then
            if (declaration.is_constant_declaration()) {
                // 1. Perform ! env.CreateImmutableBinding(dn, true).
                MUST(environment->create_immutable_binding(global_object, name, true));
            }
            // ii. Else,
            else {
                // 1. Perform ! env.CreateMutableBinding(dn, false).
                MUST(environment->create_mutable_binding(global_object, name, false));
            }

            // iii. If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
            if (declaration.is_function_declaration()) {
                VERIFY(is<FunctionDeclaration>(declaration));
                auto const& function_declaration = static_cast<FunctionDeclaration const&>(declaration);

                // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
                auto* function = ECMAScriptFunctionObject::create(global_object, function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());

                // 2. Call env.InitializeBinding(dn, fo).
                environment->initialize_binding(global_object, name, function);
            }
        });
    });

    // Note: The default export name is also part of the local lexical declarations but
    //       instead of making that a special case in the parser we just check it here.
    //       This is only needed for things which are not declarations.
    //       For more info check Parser::parse_export_statement.
    //       Furthermore, that declaration is not constant. so we take 24.a.ii
    if (m_default_export) {
        VERIFY(m_default_export->has_statement());

        auto const& statement = m_default_export->statement();
        if (!is<Declaration>(statement)) {
            auto const& name = m_default_export->entries()[0].local_or_import_name;
            dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] Adding default export to lexical declarations: local name: {}, Expression: {}", name, statement.class_name());

            // 1. Perform ! env.CreateMutableBinding(dn, false).
            MUST(environment->create_mutable_binding(global_object, name, false));

            // Note: Since this is not a function declaration 24.a.iii never applies
        }
    }

    // 25. Remove moduleContext from the execution context stack.
    vm.pop_execution_context();

    // 26. Return NormalCompletion(empty).
    return normal_completion({});
}

// 16.2.1.6.3 ResolveExport ( exportName [ , resolveSet ] ), https://tc39.es/ecma262/#sec-resolveexport
ThrowCompletionOr<ResolvedBinding> SourceTextModule::resolve_export(VM& vm, FlyString const& export_name, Vector<ResolvedBinding> resolve_set)
{
    // 1. If resolveSet is not present, set resolveSet to a new empty List.
    // Note: This is done by the default argument.

    // 2. For each Record { [[Module]], [[ExportName]] } r of resolveSet, do
    for (auto& [type, module, exported_name] : resolve_set) {
        // a. If module and r.[[Module]] are the same Module Record and SameValue(exportName, r.[[ExportName]]) is true, then
        if (module == this && exported_name == export_name) {
            // i. Assert: This is a circular import request.

            // ii. Return null.
            return ResolvedBinding::null();
        }
    }

    // 3. Append the Record { [[Module]]: module, [[ExportName]]: exportName } to resolveSet.
    resolve_set.append({ ResolvedBinding::Type::BindingName, this, export_name });

    // 4. For each ExportEntry Record e of module.[[LocalExportEntries]], do
    for (auto& entry : m_local_export_entries) {
        // a. If SameValue(exportName, e.[[ExportName]]) is true, then
        if (export_name != entry.export_name)
            continue;

        // i. Assert: module provides the direct binding for this export.
        // FIXME: What does this mean?

        // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }.
        return ResolvedBinding {
            ResolvedBinding::Type::BindingName,
            this,
            entry.local_or_import_name,
        };
    }

    // 5. For each ExportEntry Record e of module.[[IndirectExportEntries]], do
    for (auto& entry : m_indirect_export_entries) {
        // a. If SameValue(exportName, e.[[ExportName]]) is true, then
        if (export_name != entry.export_name)
            continue;

        // i. Let importedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]).
        auto imported_module = TRY(vm.host_resolve_imported_module(this, entry.module_request));

        // ii. If e.[[ImportName]] is all, then
        if (entry.is_all()) {
            // 1. Assert: module does not provide the direct binding for this export.
            // FIXME: What does this mean? / How do we check this

            // 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: namespace }.
            return ResolvedBinding {
                ResolvedBinding::Type::Namespace,
                imported_module.ptr(),
                {}
            };
        }
        // iii. Else,
        else {
            // 1. Assert: module imports a specific binding for this export.
            // FIXME: What does this mean? / How do we check this

            // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet).
            return imported_module->resolve_export(vm, entry.local_or_import_name, resolve_set);
        }
    }

    // 6. If SameValue(exportName, "default") is true, then
    if (export_name == "default"sv) {
        // a. Assert: A default export was not explicitly defined by this module.
        // FIXME: What does this mean? / How do we check this

        // b. Return null.
        return ResolvedBinding::null();
        // c. NOTE: A default export cannot be provided by an export * from "mod" declaration.
    }

    // 7. Let starResolution be null.
    ResolvedBinding star_resolution = ResolvedBinding::null();

    // 8. For each ExportEntry Record e of module.[[StarExportEntries]], do
    for (auto& entry : m_star_export_entries) {
        // a. Let importedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]).
        auto imported_module = TRY(vm.host_resolve_imported_module(this, entry.module_request));

        // b. Let resolution be ? importedModule.ResolveExport(exportName, resolveSet).
        auto resolution = TRY(imported_module->resolve_export(vm, export_name, resolve_set));

        // c. If resolution is ambiguous, return ambiguous.
        if (resolution.is_ambiguous())
            return ResolvedBinding::ambiguous();

        // d. If resolution is not null, then
        if (resolution.type == ResolvedBinding::Null)
            continue;

        // i. Assert: resolution is a ResolvedBinding Record.
        VERIFY(resolution.is_valid());

        // ii. If starResolution is null, set starResolution to resolution.
        if (star_resolution.type == ResolvedBinding::Null) {
            star_resolution = resolution;
        }
        // iii. Else,
        else {
            // 1. Assert: There is more than one * import that includes the requested name.
            // FIXME: Assert this

            // 2. If resolution.[[Module]] and starResolution.[[Module]] are not the same Module Record, return ambiguous.
            if (resolution.module != star_resolution.module)
                return ResolvedBinding::ambiguous();

            // 3. If resolution.[[BindingName]] is namespace and starResolution.[[BindingName]] is not namespace, or if resolution.[[BindingName]] is not namespace and starResolution.[[BindingName]] is namespace, return ambiguous.
            if (resolution.is_namespace() != star_resolution.is_namespace())
                return ResolvedBinding::ambiguous();

            // 4. If resolution.[[BindingName]] is a String, starResolution.[[BindingName]] is a String, and SameValue(resolution.[[BindingName]], starResolution.[[BindingName]]) is false, return ambiguous.
            if (!resolution.is_namespace() && resolution.export_name != star_resolution.export_name) {
                // Note: Because we know from the previous if that either both are namespaces or both are string we can check just one
                return ResolvedBinding::ambiguous();
            }
        }
    }

    // 9. Return starResolution.
    return star_resolution;
}

// 16.2.1.6.5 ExecuteModule ( [ capability ] ), https://tc39.es/ecma262/#sec-source-text-module-record-execute-module
Completion SourceTextModule::execute_module(VM& vm, Optional<PromiseCapability> capability)
{
    dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] SourceTextModule::execute_module({}, capability has value: {})", filename(), capability.has_value());

    // 1. Let moduleContext be a new ECMAScript code execution context.
    ExecutionContext module_context { vm.heap() };

    // Note: This is not in the spec but we require it.
    module_context.is_strict_mode = true;

    // 2. Set the Function of moduleContext to null.

    // 3. Set the Realm of moduleContext to module.[[Realm]].
    module_context.realm = &realm();

    // 4. Set the ScriptOrModule of moduleContext to module.
    module_context.script_or_module = this;

    // 5. Assert: module has been linked and declarations in its module environment have been instantiated.
    VERIFY(m_status != ModuleStatus::Unlinked && m_status != ModuleStatus::Linking && environment());

    // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]].
    module_context.variable_environment = environment();

    // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
    module_context.lexical_environment = environment();

    // 8. Suspend the currently running execution context.
    // FIXME: We don't have suspend yet

    // 9. If module.[[HasTLA]] is false, then
    if (!m_has_top_level_await) {
        // a. Assert: capability is not present.
        VERIFY(!capability.has_value());
        // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
        vm.push_execution_context(module_context, realm().global_object());

        // c. Let result be the result of evaluating module.[[ECMAScriptCode]].
        auto result = m_ecmascript_code->execute(vm.interpreter(), realm().global_object());

        // d. Suspend moduleContext and remove it from the execution context stack.
        vm.pop_execution_context();
        vm.clear_exception();

        // e. Resume the context that is now on the top of the execution context stack as the running execution context.
        // FIXME: We don't have resume yet.

        // f. Return Completion(result).
        if (result.is_error())
            return result;

        // 16.2.1.11 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-module-semantics-runtime-semantics-evaluation
        // -> Replace any empty value with undefined.

        result = result.update_empty(js_undefined());
        return *result.value();
    }
    // 10. Else,

    // a. Assert: capability is a PromiseCapability Record.
    VERIFY(capability.has_value());

    // b. Perform ! AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext).
    async_block_start(vm, m_ecmascript_code, capability.value(), module_context);

    // c. Return NormalCompletion(empty).
    return normal_completion({});
}

}