#include "root.h"
#include "_NativeModule.h"

#include "ExceptionOr.h"
#include "JavaScriptCore/CallData.h"
#include "JavaScriptCore/ArgList.h"
#include "JavaScriptCore/ExceptionScope.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "JavaScriptCore/JSGlobalObject.h"
#include "JavaScriptCore/JSNativeStdFunction.h"
#include "MessagePort.h"
#include "SerializedScriptValue.h"
#include <JavaScriptCore/APICast.h>
#include <JavaScriptCore/AggregateError.h>
#include <JavaScriptCore/BytecodeIndex.h>
#include <JavaScriptCore/CallFrameInlines.h>
#include <JavaScriptCore/ClassInfo.h>
#include <JavaScriptCore/CodeBlock.h>
#include <JavaScriptCore/Completion.h>
#include <JavaScriptCore/DeferTermination.h>
#include <JavaScriptCore/Error.h>
#include <JavaScriptCore/ErrorInstance.h>
#include <JavaScriptCore/HeapSnapshotBuilder.h>
#include <JavaScriptCore/JIT.h>
#include <JavaScriptCore/JSBasePrivate.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSONObject.h>
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/SamplingProfiler.h>
#include <JavaScriptCore/TestRunnerUtils.h>
#include <JavaScriptCore/VMTrapsInlines.h>
#include <algorithm>
#include <cstddef>
#include <wtf/FileSystem.h>
#include <wtf/MemoryFootprint.h>
#include <wtf/text/WTFString.h>

#include "BunProcess.h"
#include <JavaScriptCore/SourceProviderCache.h>
#if ENABLE(REMOTE_INSPECTOR)
#include <JavaScriptCore/RemoteInspectorServer.h>
#endif

#include "JSDOMConvertBase.h"
#include "ZigSourceProvider.h"
#include "mimalloc.h"

#include <JavaScriptCore/ControlFlowProfiler.h>

#if OS(DARWIN)
#if ASSERT_ENABLED
#if !__has_feature(address_sanitizer)
#include <malloc/malloc.h>
#define IS_MALLOC_DEBUGGING_ENABLED 1
#endif
#endif
#endif

using namespace JSC;
using namespace WTF;
using namespace WebCore;

JSC_DECLARE_HOST_FUNCTION(functionStartRemoteDebugger);
JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
#if ENABLE(REMOTE_INSPECTOR)
    static const char* defaultHost = "127.0.0.1\0";
    static uint16_t defaultPort = 9230; // node + 1

    auto& vm = JSC::getVM(globalObject);
    auto scope = DECLARE_THROW_SCOPE(vm);

    JSC::JSValue hostValue = callFrame->argument(0);
    JSC::JSValue portValue = callFrame->argument(1);
    const char* host = defaultHost;
    if (hostValue.isString()) {

        auto str = hostValue.toWTFString(globalObject);
        if (!str.isEmpty())
            host = toCString(str).data();
    } else if (!hostValue.isUndefined()) {
        throwVMError(globalObject, scope,
            createTypeError(globalObject, "host must be a string"_s));
        return JSC::JSValue::encode(JSC::jsUndefined());
    }

    uint16_t port = defaultPort;
    if (portValue.isNumber()) {
        auto port_int = portValue.toUInt32(globalObject);
        if (!(port_int > 0 && port_int < 65536)) {
            throwVMError(
                globalObject, scope,
                createRangeError(globalObject, "port must be between 0 and 65535"_s));
            return JSC::JSValue::encode(JSC::jsUndefined());
        }
        port = port_int;
    } else if (!portValue.isUndefined()) {
        throwVMError(
            globalObject, scope,
            createTypeError(globalObject,
                "port must be a number between 0 and 65535"_s));
        return JSC::JSValue::encode(JSC::jsUndefined());
    }

    globalObject->setInspectable(true);
    auto& server = Inspector::RemoteInspectorServer::singleton();
    if (!server.start(reinterpret_cast<const char*>(host), port)) {
        throwVMError(
            globalObject, scope,
            createError(globalObject,
                makeString("Failed to start server \""_s,
                    reinterpret_cast<const unsigned char*>(host),
                    ":"_s, port, "\". Is port already in use?"_s)));
        return JSC::JSValue::encode(JSC::jsUndefined());
    }

    RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsUndefined()));
#else
    auto& vm = JSC::getVM(globalObject);
    auto scope = DECLARE_THROW_SCOPE(vm);
    throwVMError(globalObject, scope,
        createTypeError(
            globalObject,
            "Remote inspector is not enabled in this build of Bun"_s));
    return JSC::JSValue::encode(JSC::jsUndefined());
#endif
}

JSC_DECLARE_HOST_FUNCTION(functionDescribe);
JSC_DEFINE_HOST_FUNCTION(functionDescribe, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();
    if (callFrame->argumentCount() < 1)
        return JSValue::encode(jsUndefined());
    return JSValue::encode(jsString(vm, toString(callFrame->argument(0))));
}

JSC_DECLARE_HOST_FUNCTION(functionDescribeArray);
JSC_DEFINE_HOST_FUNCTION(functionDescribeArray, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    if (callFrame->argumentCount() < 1)
        return JSValue::encode(jsUndefined());
    VM& vm = globalObject->vm();
    JSObject* object = jsDynamicCast<JSObject*>(callFrame->argument(0));
    if (!object)
        return JSValue::encode(jsNontrivialString(vm, "<not object>"_s));
    return JSValue::encode(jsNontrivialString(
        vm, toString("<Butterfly: ", RawPointer(object->butterfly()), "; public length: ", object->getArrayLength(), "; vector length: ", object->getVectorLength(), ">")));
}

JSC_DECLARE_HOST_FUNCTION(functionGCAndSweep);
JSC_DEFINE_HOST_FUNCTION(functionGCAndSweep,
    (JSGlobalObject * globalObject, CallFrame*))
{
    VM& vm = globalObject->vm();
    JSLockHolder lock(vm);
    vm.heap.collectNow(Sync, CollectionScope::Full);
    return JSValue::encode(jsNumber(vm.heap.sizeAfterLastFullCollection()));
}

JSC_DECLARE_HOST_FUNCTION(functionFullGC);
JSC_DEFINE_HOST_FUNCTION(functionFullGC,
    (JSGlobalObject * globalObject, CallFrame*))
{
    VM& vm = globalObject->vm();
    JSLockHolder lock(vm);
    vm.heap.collectSync(CollectionScope::Full);
    return JSValue::encode(jsNumber(vm.heap.sizeAfterLastFullCollection()));
}

JSC_DECLARE_HOST_FUNCTION(functionEdenGC);
JSC_DEFINE_HOST_FUNCTION(functionEdenGC,
    (JSGlobalObject * globalObject, CallFrame*))
{
    VM& vm = globalObject->vm();
    JSLockHolder lock(vm);
    vm.heap.collectSync(CollectionScope::Eden);
    return JSValue::encode(jsNumber(vm.heap.sizeAfterLastEdenCollection()));
}

JSC_DECLARE_HOST_FUNCTION(functionHeapSize);
JSC_DEFINE_HOST_FUNCTION(functionHeapSize,
    (JSGlobalObject * globalObject, CallFrame*))
{
    VM& vm = globalObject->vm();
    JSLockHolder lock(vm);
    return JSValue::encode(jsNumber(vm.heap.size()));
}

JSC::Structure*
createMemoryFootprintStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{

    JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype(
        globalObject, globalObject->objectPrototype(), 5);
    JSC::PropertyOffset offset;

    structure = structure->addPropertyTransition(
        vm, structure, Identifier::fromString(vm, "current"_s), 0, offset);
    structure = structure->addPropertyTransition(
        vm, structure, Identifier::fromString(vm, "peak"_s), 0, offset);
    structure = structure->addPropertyTransition(
        vm, structure, Identifier::fromString(vm, "currentCommit"_s), 0, offset);
    structure = structure->addPropertyTransition(
        vm, structure, Identifier::fromString(vm, "peakCommit"_s), 0, offset);
    structure = structure->addPropertyTransition(
        vm, structure, Identifier::fromString(vm, "pageFaults"_s), 0, offset);

    return structure;
}

JSC_DECLARE_HOST_FUNCTION(functionMemoryUsageStatistics);
JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics,
    (JSGlobalObject * globalObject, CallFrame*))
{

    auto& vm = JSC::getVM(globalObject);

    if (vm.heap.size() == 0) {
        vm.heap.collectNow(Sync, CollectionScope::Full);
    }

    const auto createdSortedTypeCounts =
        [&](JSC::TypeCountSet* typeCounts) -> JSC::JSValue {
        WTF::Vector<std::pair<Identifier, unsigned>> counts;
        counts.reserveInitialCapacity(typeCounts->size());
        for (auto& it : *typeCounts) {
            if (it.value > 0)
                counts.append(
                    std::make_pair(Identifier::fromString(vm, it.key), it.value));
        }

        // Sort by count first, then by name.
        std::sort(counts.begin(), counts.end(),
            [](const std::pair<Identifier, unsigned>& a,
                const std::pair<Identifier, unsigned>& b) {
                if (a.second == b.second) {
                    WTF::StringView left = a.first.string();
                    WTF::StringView right = b.first.string();
                    unsigned originalLeftLength = left.length();
                    unsigned originalRightLength = right.length();
                    unsigned size = std::min(left.length(), right.length());
                    left = left.substring(0, size);
                    right = right.substring(0, size);
                    std::strong_ordering result = WTF::codePointCompare(right, left);
                    if (result == std::strong_ordering::equal) {
                        return originalLeftLength > originalRightLength;
                    }

                    return result == std::strong_ordering::greater;
                }

                return a.second > b.second;
            });

        auto* objectTypeCounts = constructEmptyObject(globalObject);
        for (auto& it : counts) {
            objectTypeCounts->putDirect(vm, it.first, jsNumber(it.second));
        }
        return objectTypeCounts;
    };

    JSValue objectTypeCounts = createdSortedTypeCounts(vm.heap.objectTypeCounts().get());
    JSValue protectedCounts = createdSortedTypeCounts(vm.heap.protectedObjectTypeCounts().get());

    JSObject* object = constructEmptyObject(globalObject);
    object->putDirect(vm, Identifier::fromString(vm, "objectTypeCounts"_s),
        objectTypeCounts);

    object->putDirect(vm,
        Identifier::fromString(vm, "protectedObjectTypeCounts"_s),
        protectedCounts);
    object->putDirect(vm, Identifier::fromString(vm, "heapSize"_s),
        jsNumber(vm.heap.size()));
    object->putDirect(vm, Identifier::fromString(vm, "heapCapacity"_s),
        jsNumber(vm.heap.capacity()));
    object->putDirect(vm, Identifier::fromString(vm, "extraMemorySize"_s),
        jsNumber(vm.heap.extraMemorySize()));
    object->putDirect(vm, Identifier::fromString(vm, "objectCount"_s),
        jsNumber(vm.heap.objectCount()));
    object->putDirect(vm, Identifier::fromString(vm, "protectedObjectCount"_s),
        jsNumber(vm.heap.protectedObjectCount()));
    object->putDirect(vm, Identifier::fromString(vm, "globalObjectCount"_s),
        jsNumber(vm.heap.globalObjectCount()));
    object->putDirect(vm,
        Identifier::fromString(vm, "protectedGlobalObjectCount"_s),
        jsNumber(vm.heap.protectedGlobalObjectCount()));

#if IS_MALLOC_DEBUGGING_ENABLED
#if OS(DARWIN)
    {
        vm_address_t* zones;
        unsigned count;

        // Zero out the structures in case a zone is missing
        malloc_statistics_t zone_stats;
        zone_stats.blocks_in_use = 0;
        zone_stats.size_in_use = 0;
        zone_stats.max_size_in_use = 0;
        zone_stats.size_allocated = 0;

        malloc_zone_pressure_relief(nullptr, 0);
        malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
        Vector<std::pair<Identifier, size_t>> zoneSizes;
        zoneSizes.reserveInitialCapacity(count);
        for (unsigned i = 0; i < count; i++) {
            auto zone = reinterpret_cast<malloc_zone_t*>(zones[i]);
            if (const char* name = malloc_get_zone_name(zone)) {
                malloc_zone_statistics(reinterpret_cast<malloc_zone_t*>(zones[i]),
                    &zone_stats);
                zoneSizes.append(
                    std::make_pair(Identifier::fromString(vm, String::fromUTF8(name)),
                        zone_stats.size_in_use));
            }
        }

        std::sort(zoneSizes.begin(), zoneSizes.end(),
            [](const std::pair<Identifier, size_t>& a,
                const std::pair<Identifier, size_t>& b) {
                // Sort by name if the sizes are the same.
                if (a.second == b.second) {
                    WTF::StringView left = a.first.string();
                    WTF::StringView right = b.first.string();
                    unsigned originalLeftLength = left.length();
                    unsigned originalRightLength = right.length();
                    unsigned size = std::min(left.length(), right.length());
                    left = left.substring(0, size);
                    right = right.substring(0, size);
                    int result = WTF::codePointCompare(right, left);
                    if (result == 0) {
                        return originalLeftLength > originalRightLength;
                    }

                    return result > 0;
                }

                return a.second > b.second;
            });

        auto* zoneSizesObject = constructEmptyObject(globalObject);
        for (auto& it : zoneSizes) {
            zoneSizesObject->putDirect(vm, it.first, jsDoubleNumber(it.second));
        }

        object->putDirect(vm, Identifier::fromString(vm, "zones"_s),
            zoneSizesObject);
    }
#endif
#endif

    return JSValue::encode(object);
}

JSC_DECLARE_HOST_FUNCTION(functionCreateMemoryFootprint);
JSC_DEFINE_HOST_FUNCTION(functionCreateMemoryFootprint,
    (JSGlobalObject * globalObject, CallFrame*))
{

    size_t elapsed_msecs = 0;
    size_t user_msecs = 0;
    size_t system_msecs = 0;
    size_t current_rss = 0;
    size_t peak_rss = 0;
    size_t current_commit = 0;
    size_t peak_commit = 0;
    size_t page_faults = 0;

    mi_process_info(&elapsed_msecs, &user_msecs, &system_msecs, &current_rss,
        &peak_rss, &current_commit, &peak_commit, &page_faults);

    // mi_process_info produces incorrect rss size on linux.
    Bun::getRSS(&current_rss);

    VM& vm = globalObject->vm();
    JSC::JSObject* object = JSC::constructEmptyObject(
        vm, JSC::jsCast<Zig::GlobalObject*>(globalObject)->memoryFootprintStructure());

    object->putDirectOffset(vm, 0, jsNumber(current_rss));
    object->putDirectOffset(vm, 1, jsNumber(peak_rss));
    object->putDirectOffset(vm, 2, jsNumber(current_commit));
    object->putDirectOffset(vm, 3, jsNumber(peak_commit));
    object->putDirectOffset(vm, 4, jsNumber(page_faults));

    return JSValue::encode(object);
}

JSC_DECLARE_HOST_FUNCTION(functionNeverInlineFunction);
JSC_DEFINE_HOST_FUNCTION(functionNeverInlineFunction,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    return JSValue::encode(setNeverInline(globalObject, callFrame));
}

extern "C" bool Bun__mkdirp(JSC::JSGlobalObject*, const char*);

JSC_DECLARE_HOST_FUNCTION(functionStartSamplingProfiler);
JSC_DEFINE_HOST_FUNCTION(functionStartSamplingProfiler,
    (JSC::JSGlobalObject * globalObject,
        JSC::CallFrame* callFrame))
{
    auto& vm = JSC::getVM(globalObject);
    JSC::SamplingProfiler& samplingProfiler = vm.ensureSamplingProfiler(WTF::Stopwatch::create());

    JSC::JSValue directoryValue = callFrame->argument(0);
    JSC::JSValue sampleValue = callFrame->argument(1);

    auto scope = DECLARE_THROW_SCOPE(vm);
    if (directoryValue.isString()) {
        auto path = directoryValue.toWTFString(globalObject);
        if (!path.isEmpty()) {
            StringPrintStream pathOut;
            auto pathCString = toCString(String(path));
            if (!Bun__mkdirp(globalObject, pathCString.data())) {
                throwVMError(
                    globalObject, scope,
                    createTypeError(globalObject, "directory couldn't be created"_s));
                return {};
            }

            Options::samplingProfilerPath() = pathCString.data();
            samplingProfiler.registerForReportAtExit();
        }
    }
    if (sampleValue.isNumber()) {
        unsigned sampleInterval = sampleValue.toUInt32(globalObject);
        samplingProfiler.setTimingInterval(
            Seconds::fromMicroseconds(sampleInterval));
    }

    samplingProfiler.noticeCurrentThreadAsJSCExecutionThread();
    samplingProfiler.start();
    return JSC::JSValue::encode(jsUndefined());
}

JSC_DECLARE_HOST_FUNCTION(functionSamplingProfilerStackTraces);
JSC_DEFINE_HOST_FUNCTION(functionSamplingProfilerStackTraces,
    (JSC::JSGlobalObject * globalObject,
        JSC::CallFrame*))
{
    auto& vm = JSC::getVM(globalObject);
    JSC::DeferTermination deferScope(vm);
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (!vm.samplingProfiler())
        return JSC::JSValue::encode(throwException(
            globalObject, scope,
            createError(globalObject, "Sampling profiler was never started"_s)));

    WTF::String jsonString = vm.samplingProfiler()->stackTracesAsJSON()->toJSONString();
    JSC::EncodedJSValue result = JSC::JSValue::encode(JSONParse(globalObject, jsonString));
    scope.releaseAssertNoException();
    return result;
}

JSC_DECLARE_HOST_FUNCTION(functionGetRandomSeed);
JSC_DEFINE_HOST_FUNCTION(functionGetRandomSeed,
    (JSGlobalObject * globalObject, CallFrame*))
{
    return JSValue::encode(jsNumber(globalObject->weakRandom().seed()));
}

JSC_DECLARE_HOST_FUNCTION(functionSetRandomSeed);
JSC_DEFINE_HOST_FUNCTION(functionSetRandomSeed, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    unsigned seed = callFrame->argument(0).toUInt32(globalObject);
    RETURN_IF_EXCEPTION(scope, {});
    globalObject->weakRandom().setSeed(seed);
    return JSValue::encode(jsUndefined());
}

JSC_DECLARE_HOST_FUNCTION(functionIsRope);
JSC_DEFINE_HOST_FUNCTION(functionIsRope,
    (JSGlobalObject*, CallFrame* callFrame))
{
    JSValue argument = callFrame->argument(0);
    if (!argument.isString())
        return JSValue::encode(jsBoolean(false));
    const StringImpl* impl = asString(argument)->tryGetValueImpl();
    return JSValue::encode(jsBoolean(!impl));
}

JSC_DECLARE_HOST_FUNCTION(functionCallerSourceOrigin);
JSC_DEFINE_HOST_FUNCTION(functionCallerSourceOrigin,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    VM& vm = globalObject->vm();
    SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
    if (sourceOrigin.url().isNull())
        return JSValue::encode(jsNull());
    return JSValue::encode(jsString(vm, sourceOrigin.string()));
}

JSC_DECLARE_HOST_FUNCTION(functionNoFTL);
JSC_DEFINE_HOST_FUNCTION(functionNoFTL,
    (JSGlobalObject*, CallFrame* callFrame))
{
    if (callFrame->argumentCount()) {
        FunctionExecutable* executable = getExecutableForFunction(callFrame->argument(0));
        if (executable)
            executable->setNeverFTLOptimize(true);
    }
    return JSValue::encode(jsUndefined());
}

JSC_DECLARE_HOST_FUNCTION(functionNoOSRExitFuzzing);
JSC_DEFINE_HOST_FUNCTION(functionNoOSRExitFuzzing,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    return JSValue::encode(setCannotUseOSRExitFuzzing(globalObject, callFrame));
}

JSC_DECLARE_HOST_FUNCTION(functionOptimizeNextInvocation);
JSC_DEFINE_HOST_FUNCTION(functionOptimizeNextInvocation,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    return JSValue::encode(optimizeNextInvocation(globalObject, callFrame));
}

JSC_DECLARE_HOST_FUNCTION(functionNumberOfDFGCompiles);
JSC_DEFINE_HOST_FUNCTION(functionNumberOfDFGCompiles,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    return JSValue::encode(numberOfDFGCompiles(globalObject, callFrame));
}

JSC_DECLARE_HOST_FUNCTION(functionReleaseWeakRefs);
JSC_DEFINE_HOST_FUNCTION(functionReleaseWeakRefs,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    globalObject->vm().finalizeSynchronousJSExecution();
    return JSValue::encode(jsUndefined());
}

JSC_DECLARE_HOST_FUNCTION(functionTotalCompileTime);
JSC_DEFINE_HOST_FUNCTION(functionTotalCompileTime,
    (JSGlobalObject*, CallFrame*))
{
    return JSValue::encode(jsNumber(JIT::totalCompileTime().milliseconds()));
}

JSC_DECLARE_HOST_FUNCTION(functionGetProtectedObjects);
JSC_DEFINE_HOST_FUNCTION(functionGetProtectedObjects,
    (JSGlobalObject * globalObject, CallFrame*))
{
    MarkedArgumentBuffer list;
    globalObject->vm().heap.forEachProtectedCell(
        [&](JSCell* cell) { list.append(cell); });
    RELEASE_ASSERT(!list.hasOverflowed());
    return JSC::JSValue::encode(constructArray(
        globalObject, static_cast<JSC::ArrayAllocationProfile*>(nullptr), list));
}

JSC_DECLARE_HOST_FUNCTION(functionReoptimizationRetryCount);
JSC_DEFINE_HOST_FUNCTION(functionReoptimizationRetryCount,
    (JSGlobalObject*, CallFrame* callFrame))
{
    if (callFrame->argumentCount() < 1)
        return JSValue::encode(jsUndefined());

    CodeBlock* block = getSomeBaselineCodeBlockForFunction(callFrame->argument(0));
    if (!block)
        return JSValue::encode(jsNumber(0));

    return JSValue::encode(jsNumber(block->reoptimizationRetryCounter()));
}

extern "C" void Bun__drainMicrotasks();

JSC_DECLARE_HOST_FUNCTION(functionDrainMicrotasks);
JSC_DEFINE_HOST_FUNCTION(functionDrainMicrotasks,
    (JSGlobalObject * globalObject, CallFrame*))
{
    VM& vm = globalObject->vm();
    vm.drainMicrotasks();
    Bun__drainMicrotasks();
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(functionSetTimeZone, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (callFrame->argumentCount() < 1) {
        throwTypeError(globalObject, scope,
            "setTimeZone requires a timezone string"_s);
        return {};
    }

    if (!callFrame->argument(0).isString()) {
        throwTypeError(globalObject, scope,
            "setTimeZone requires a timezone string"_s);
        return {};
    }

    String timeZoneName = callFrame->argument(0).toWTFString(globalObject);
    RETURN_IF_EXCEPTION(scope, {});

    if (!WTF::setTimeZoneOverride(timeZoneName)) {
        throwTypeError(globalObject, scope,
            makeString("Invalid timezone: \""_s, timeZoneName, "\""_s));
        return {};
    }
    vm.dateCache.resetIfNecessarySlow();
    WTF::Vector<UChar, 32> buffer;
    WTF::getTimeZoneOverride(buffer);
    WTF::String timeZoneString({ buffer.data(), buffer.size() });
    return JSValue::encode(jsString(vm, timeZoneString));
}

JSC_DEFINE_HOST_FUNCTION(functionRunProfiler, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    auto& vm = JSC::getVM(globalObject);
    JSC::SamplingProfiler& samplingProfiler = vm.ensureSamplingProfiler(WTF::Stopwatch::create());

    JSC::JSValue callbackValue = callFrame->argument(0);
    JSC::JSValue sampleValue = callFrame->argument(1);

    MarkedArgumentBuffer args;

    if (callFrame->argumentCount() > 2) {
        size_t count = callFrame->argumentCount();
        args.ensureCapacity(count - 2);
        for (size_t i = 2; i < count; i++) {
            args.append(callFrame->argument(i));
        }
    }

    auto throwScope = DECLARE_THROW_SCOPE(vm);
    if (callbackValue.isUndefinedOrNull() || !callbackValue.isCallable()) {
        throwException(
            globalObject, throwScope,
            createTypeError(globalObject, "First argument must be a function."_s));
        return JSValue::encode(JSValue {});
    }

    JSC::JSFunction* function = jsCast<JSC::JSFunction*>(callbackValue);

    if (sampleValue.isNumber()) {
        unsigned sampleInterval = sampleValue.toUInt32(globalObject);
        samplingProfiler.setTimingInterval(
            Seconds::fromMicroseconds(sampleInterval));
    }

    const auto report = [](JSC::VM& vm,
                            JSC::JSGlobalObject* globalObject) -> JSC::JSValue {
        auto throwScope = DECLARE_THROW_SCOPE(vm);

        auto& samplingProfiler = *vm.samplingProfiler();
        StringPrintStream topFunctions;
        samplingProfiler.reportTopFunctions(topFunctions);

        StringPrintStream byteCodes;
        samplingProfiler.reportTopBytecodes(byteCodes);

        JSValue stackTraces = JSONParse(
            globalObject, samplingProfiler.stackTracesAsJSON()->toJSONString());

        samplingProfiler.shutdown();
        RETURN_IF_EXCEPTION(throwScope, {});

        JSObject* result = constructEmptyObject(globalObject, globalObject->objectPrototype(), 3);
        result->putDirect(vm, Identifier::fromString(vm, "functions"_s),
            jsString(vm, topFunctions.toString()));
        result->putDirect(vm, Identifier::fromString(vm, "bytecodes"_s),
            jsString(vm, byteCodes.toString()));
        result->putDirect(vm, Identifier::fromString(vm, "stackTraces"_s),
            stackTraces);

        return result;
    };
    const auto reportFailure = [](JSC::VM& vm) -> JSC::JSValue {
        if (auto* samplingProfiler = vm.samplingProfiler()) {
            samplingProfiler->pause();
            samplingProfiler->shutdown();
            samplingProfiler->clearData();
        }

        return {};
    };

    JSC::CallData callData = JSC::getCallData(function);

    samplingProfiler.noticeCurrentThreadAsJSCExecutionThread();
    samplingProfiler.start();
    JSValue returnValue = JSC::profiledCall(globalObject, ProfilingReason::API, function, callData, JSC::jsUndefined(), args);

    if (returnValue.isEmpty() || throwScope.exception()) {
        return JSValue::encode(reportFailure(vm));
    }

    if (auto* promise = jsDynamicCast<JSPromise*>(returnValue)) {
        auto afterOngoingPromiseCapability = JSC::JSPromise::create(vm, globalObject->promiseStructure());
        RETURN_IF_EXCEPTION(throwScope, {});

        JSNativeStdFunction* resolve = JSNativeStdFunction::create(
            vm, globalObject, 0, "resolve"_s,
            [report](JSGlobalObject* globalObject, CallFrame* callFrame) {
                return JSValue::encode(JSPromise::resolvedPromise(
                    globalObject, report(globalObject->vm(), globalObject)));
            });
        JSNativeStdFunction* reject = JSNativeStdFunction::create(
            vm, globalObject, 0, "reject"_s,
            [reportFailure](JSGlobalObject* globalObject, CallFrame* callFrame) {
                EnsureStillAliveScope error = callFrame->argument(0);
                auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
                reportFailure(globalObject->vm());
                throwException(globalObject, scope, error.value());
                return JSValue::encode({});
            });
        promise->performPromiseThen(globalObject, resolve, reject,
            afterOngoingPromiseCapability);
        return JSValue::encode(afterOngoingPromiseCapability);
    }

    return JSValue::encode(report(vm, globalObject));
}

JSC_DECLARE_HOST_FUNCTION(functionGenerateHeapSnapshotForDebugging);
JSC_DEFINE_HOST_FUNCTION(functionGenerateHeapSnapshotForDebugging,
    (JSGlobalObject * globalObject, CallFrame*))
{
    VM& vm = globalObject->vm();
    JSLockHolder lock(vm);
    DeferTermination deferScope(vm);
    auto scope = DECLARE_THROW_SCOPE(vm);
    String jsonString;
    {
        DeferGCForAWhile deferGC(vm); // Prevent concurrent GC from interfering with
                                      // the full GC that the snapshot does.

        HeapSnapshotBuilder snapshotBuilder(
            vm.ensureHeapProfiler(),
            HeapSnapshotBuilder::SnapshotType::GCDebuggingSnapshot);
        snapshotBuilder.buildSnapshot();

        jsonString = snapshotBuilder.json();
    }
    scope.releaseAssertNoException();

    return JSValue::encode(JSONParse(globalObject, WTFMove(jsonString)));
}

JSC_DEFINE_HOST_FUNCTION(functionSerialize,
    (JSGlobalObject * lexicalGlobalObject,
        CallFrame* callFrame))
{
    auto* globalObject = jsCast<JSDOMGlobalObject*>(lexicalGlobalObject);
    auto& vm = JSC::getVM(globalObject);
    auto throwScope = DECLARE_THROW_SCOPE(vm);

    JSValue value = callFrame->argument(0);
    JSValue optionsObject = callFrame->argument(1);
    bool asNodeBuffer = false;
    if (optionsObject.isObject()) {
        JSC::JSObject* options = optionsObject.getObject();
        if (JSC::JSValue binaryTypeValue = options->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "binaryType"_s))) {
            if (!binaryTypeValue.isString()) {
                throwTypeError(globalObject, throwScope, "binaryType must be a string"_s);
                return {};
            }

            asNodeBuffer = binaryTypeValue.toWTFString(globalObject) == "nodebuffer"_s;
            RETURN_IF_EXCEPTION(throwScope, {});
        }
    }

    Vector<JSC::Strong<JSC::JSObject>> transferList;
    Vector<RefPtr<MessagePort>> dummyPorts;
    ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList), dummyPorts);

    if (serialized.hasException()) {
        WebCore::propagateException(*globalObject, throwScope,
            serialized.releaseException());
        return JSValue::encode(jsUndefined());
    }

    auto serializedValue = serialized.releaseReturnValue();
    auto arrayBuffer = serializedValue->toArrayBuffer();

    if (asNodeBuffer) {
        size_t byteLength = arrayBuffer->byteLength();
        auto* subclassStructure = globalObject->JSBufferSubclassStructure();
        JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(arrayBuffer), 0, byteLength);
        return JSValue::encode(uint8Array);
    }

    if (arrayBuffer->isShared()) {
        return JSValue::encode(
            JSArrayBuffer::create(vm,
                globalObject->arrayBufferStructureWithSharingMode<
                    ArrayBufferSharingMode::Shared>(),
                WTFMove(arrayBuffer)));
    }

    return JSValue::encode(JSArrayBuffer::create(
        vm, globalObject->arrayBufferStructure(), WTFMove(arrayBuffer)));
}
JSC_DEFINE_HOST_FUNCTION(functionDeserialize, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    auto& vm = JSC::getVM(globalObject);
    auto throwScope = DECLARE_THROW_SCOPE(vm);
    JSValue value = callFrame->argument(0);

    JSValue result;

    if (auto* jsArrayBuffer = jsDynamicCast<JSArrayBuffer*>(value)) {
        result = SerializedScriptValue::fromArrayBuffer(
            *globalObject, globalObject, jsArrayBuffer->impl(), 0,
            jsArrayBuffer->impl()->byteLength());
    } else if (auto* view = jsDynamicCast<JSArrayBufferView*>(value)) {
        auto arrayBuffer = view->possiblySharedImpl()->possiblySharedBuffer();
        result = SerializedScriptValue::fromArrayBuffer(
            *globalObject, globalObject, arrayBuffer.get(), view->byteOffset(),
            view->byteLength());
    } else {
        throwTypeError(globalObject, throwScope,
            "First argument must be an ArrayBuffer"_s);
        return {};
    }

    RETURN_IF_EXCEPTION(throwScope, {});
    RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
}

extern "C" JSC::EncodedJSValue ByteRangeMapping__findExecutedLines(
    JSC::JSGlobalObject*, BunString sourceURL, BasicBlockRange* ranges,
    size_t len, size_t functionOffset, bool ignoreSourceMap);

JSC_DEFINE_HOST_FUNCTION(functionCodeCoverageForFile,
    (JSGlobalObject * globalObject,
        CallFrame* callFrame))
{
    VM& vm = globalObject->vm();
    auto throwScope = DECLARE_THROW_SCOPE(vm);

    String fileName = callFrame->argument(0).toWTFString(globalObject);
    RETURN_IF_EXCEPTION(throwScope, {});
    bool ignoreSourceMap = callFrame->argument(1).toBoolean(globalObject);

    auto sourceID = Zig::sourceIDForSourceURL(fileName);
    if (!sourceID) {
        throwException(globalObject, throwScope,
            createError(globalObject, "No source for file"_s));
        return {};
    }

    auto basicBlocks = vm.controlFlowProfiler()->getBasicBlocksForSourceIDWithoutFunctionRange(
        sourceID, vm);

    if (basicBlocks.isEmpty()) {
        return JSC::JSValue::encode(
            JSC::constructEmptyArray(globalObject, nullptr, 0));
    }

    size_t functionStartOffset = basicBlocks.size();

    const Vector<std::tuple<bool, unsigned, unsigned>>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID);

    basicBlocks.reserveCapacity(functionRanges.size() + basicBlocks.size());

    for (const auto& functionRange : functionRanges) {
        BasicBlockRange range;
        range.m_hasExecuted = std::get<0>(functionRange);
        range.m_startOffset = static_cast<int>(std::get<1>(functionRange));
        range.m_endOffset = static_cast<int>(std::get<2>(functionRange));
        range.m_executionCount = range.m_hasExecuted
            ? 1
            : 0; // This is a hack. We don't actually count this.
        basicBlocks.append(range);
    }

    return ByteRangeMapping__findExecutedLines(
        globalObject, Bun::toString(fileName), basicBlocks.data(),
        basicBlocks.size(), functionStartOffset, ignoreSourceMap);
}

JSC_DEFINE_HOST_FUNCTION(functionEstimateDirectMemoryUsageOf, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
    JSValue value = callFrame->argument(0);
    if (value.isCell()) {
        auto& vm = JSC::getVM(globalObject);
        EnsureStillAliveScope alive = value;
        return JSValue::encode(jsDoubleNumber(alive.value().asCell()->estimatedSizeInBytes(vm)));
    }

    return JSValue::encode(jsNumber(0));
}

#if USE(BMALLOC_MEMORY_FOOTPRINT_API)

#include <bmalloc/bmalloc.h>

JSC_DEFINE_HOST_FUNCTION(functionPercentAvailableMemoryInUse, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    return JSValue::encode(jsDoubleNumber(bmalloc::api::percentAvailableMemoryInUse()));
}

#else

JSC_DEFINE_HOST_FUNCTION(functionPercentAvailableMemoryInUse, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
    return JSValue::encode(jsNull());
}

#endif

// clang-format off
/* Source for BunJSCModuleTable.lut.h
@begin BunJSCModuleTable
    callerSourceOrigin                  functionCallerSourceOrigin                  Function    0                                              
    jscDescribe                         functionDescribe                            Function    0                            
    jscDescribeArray                    functionDescribeArray                       Function    0                                         
    drainMicrotasks                     functionDrainMicrotasks                     Function    0                                       
    edenGC                              functionEdenGC                              Function    0                      
    fullGC                              functionFullGC                              Function    0                      
    gcAndSweep                          functionGCAndSweep                          Function    0                              
    getRandomSeed                       functionGetRandomSeed                       Function    0                                     
    heapSize                            functionHeapSize                            Function    0                            
    heapStats                           functionMemoryUsageStatistics               Function    0                                         
    startSamplingProfiler               functionStartSamplingProfiler               Function    0                                                     
    samplingProfilerStackTraces         functionSamplingProfilerStackTraces         Function    0                                                               
    noInline                            functionNeverInlineFunction                 Function    0                                       
    isRope                              functionIsRope                              Function    0                      
    memoryUsage                         functionCreateMemoryFootprint               Function    0                                         
    noFTL                               functionNoFTL                               Function    0                     
    noOSRExitFuzzing                    functionNoOSRExitFuzzing                    Function    0                                            
    numberOfDFGCompiles                 functionNumberOfDFGCompiles                 Function    0                                               
    optimizeNextInvocation              functionOptimizeNextInvocation              Function    0                                                      
    releaseWeakRefs                     functionReleaseWeakRefs                     Function    0                                       
    reoptimizationRetryCount            functionReoptimizationRetryCount            Function    0                                                            
    setRandomSeed                       functionSetRandomSeed                       Function    0                                     
    startRemoteDebugger                 functionStartRemoteDebugger                 Function    0                                               
    totalCompileTime                    functionTotalCompileTime                    Function    0                                            
    getProtectedObjects                 functionGetProtectedObjects                 Function    0                                               
    generateHeapSnapshotForDebugging    functionGenerateHeapSnapshotForDebugging    Function    0                                                                            
    profile                              functionRunProfiler                          Function    0                           
    setTimeZone                         functionSetTimeZone                         Function    0                               
    serialize                           functionSerialize                           Function    0                             
    deserialize                         functionDeserialize                         Function    0   
    estimateShallowMemoryUsageOf         functionEstimateDirectMemoryUsageOf         Function    1
    percentAvailableMemoryInUse         functionPercentAvailableMemoryInUse         Function    0
@end
*/

namespace Zig {
DEFINE_NATIVE_MODULE(BunJSC)
{
    INIT_NATIVE_MODULE(36);

    putNativeFn(Identifier::fromString(vm, "callerSourceOrigin"_s), functionCallerSourceOrigin);
    putNativeFn(Identifier::fromString(vm, "jscDescribe"_s), functionDescribe);
    putNativeFn(Identifier::fromString(vm, "jscDescribeArray"_s), functionDescribeArray);
    putNativeFn(Identifier::fromString(vm, "drainMicrotasks"_s), functionDrainMicrotasks);
    putNativeFn(Identifier::fromString(vm, "edenGC"_s), functionEdenGC);
    putNativeFn(Identifier::fromString(vm, "fullGC"_s), functionFullGC);
    putNativeFn(Identifier::fromString(vm, "gcAndSweep"_s), functionGCAndSweep);
    putNativeFn(Identifier::fromString(vm, "getRandomSeed"_s), functionGetRandomSeed);
    putNativeFn(Identifier::fromString(vm, "heapSize"_s), functionHeapSize);
    putNativeFn(Identifier::fromString(vm, "heapStats"_s), functionMemoryUsageStatistics);
    putNativeFn(Identifier::fromString(vm, "startSamplingProfiler"_s), functionStartSamplingProfiler);
    putNativeFn(Identifier::fromString(vm, "samplingProfilerStackTraces"_s), functionSamplingProfilerStackTraces);
    putNativeFn(Identifier::fromString(vm, "noInline"_s), functionNeverInlineFunction);
    putNativeFn(Identifier::fromString(vm, "isRope"_s), functionIsRope);
    putNativeFn(Identifier::fromString(vm, "memoryUsage"_s), functionCreateMemoryFootprint);
    putNativeFn(Identifier::fromString(vm, "noFTL"_s), functionNoFTL);
    putNativeFn(Identifier::fromString(vm, "noOSRExitFuzzing"_s), functionNoOSRExitFuzzing);
    putNativeFn(Identifier::fromString(vm, "numberOfDFGCompiles"_s), functionNumberOfDFGCompiles);
    putNativeFn(Identifier::fromString(vm, "optimizeNextInvocation"_s), functionOptimizeNextInvocation);
    putNativeFn(Identifier::fromString(vm, "releaseWeakRefs"_s), functionReleaseWeakRefs);
    putNativeFn(Identifier::fromString(vm, "reoptimizationRetryCount"_s), functionReoptimizationRetryCount);
    putNativeFn(Identifier::fromString(vm, "setRandomSeed"_s), functionSetRandomSeed);
    putNativeFn(Identifier::fromString(vm, "startRemoteDebugger"_s), functionStartRemoteDebugger);
    putNativeFn(Identifier::fromString(vm, "totalCompileTime"_s), functionTotalCompileTime);
    putNativeFn(Identifier::fromString(vm, "getProtectedObjects"_s), functionGetProtectedObjects);
    putNativeFn(Identifier::fromString(vm, "generateHeapSnapshotForDebugging"_s), functionGenerateHeapSnapshotForDebugging);
    putNativeFn(Identifier::fromString(vm, "profile"_s), functionRunProfiler);
    putNativeFn(Identifier::fromString(vm, "codeCoverageForFile"_s),  functionCodeCoverageForFile);
    putNativeFn(Identifier::fromString(vm, "setTimeZone"_s), functionSetTimeZone);
    putNativeFn(Identifier::fromString(vm, "serialize"_s), functionSerialize);
    putNativeFn(Identifier::fromString(vm, "deserialize"_s), functionDeserialize);
    putNativeFn(Identifier::fromString(vm, "estimateShallowMemoryUsageOf"_s), functionEstimateDirectMemoryUsageOf);
    putNativeFn(Identifier::fromString(vm, "percentAvailableMemoryInUse"_s), functionPercentAvailableMemoryInUse);

    // Deprecated
    putNativeFn(Identifier::fromString(vm, "describe"_s), functionDescribe);
    putNativeFn(Identifier::fromString(vm, "describeArray"_s), functionDescribeArray);
    putNativeFn(Identifier::fromString(vm, "setTimezone"_s), functionSetTimeZone);

    RETURN_NATIVE_MODULE();
}

} // namespace Zig
