/*
 * Copyright (C) 2025 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include <JavaScriptCore/JSCJSValue.h>
#include <JavaScriptCore/Microtask.h>
#include <JavaScriptCore/SlotVisitorMacros.h>
#include <wtf/CompactPointerTuple.h>
#include <wtf/Compiler.h>
#include <wtf/Deque.h>
#include <wtf/Ref.h>
#include <wtf/RefCounted.h>
#include <wtf/SentinelLinkedList.h>
#include <wtf/TZoneMalloc.h>
#include <wtf/VectorTraits.h>

namespace JSC {
class JSCell;
class JSMicrotaskDispatcher;
}
WTF_ALLOW_COMPACT_POINTERS_TO_INCOMPLETE_TYPE(JSC::JSCell);

WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN

namespace JSC {

class JSGlobalObject;
class MarkedMicrotaskDeque;
class MicrotaskDispatcher;
class MicrotaskQueue;
class QueuedTask;
class TopExceptionScope;
class VM;

class MicrotaskDispatcher : public RefCounted<MicrotaskDispatcher> {
    WTF_MAKE_COMPACT_TZONE_ALLOCATED(MicrotaskDispatcher);
public:
    enum class Type : uint8_t {
        None,
        JSCDebuggable,
        // WebCoreMicrotaskDispatcher starts from here.
        WebCoreUserGestureIndicator,
        WebCoreFunction,
    };

    explicit MicrotaskDispatcher(Type type)
        : m_type(type)
    { }

    virtual ~MicrotaskDispatcher() = default;
    virtual QueuedTaskResult run(QueuedTask&) = 0;
    virtual bool isRunnable() const = 0;
    Type type() const { return m_type; }
    bool isWebCoreMicrotaskDispatcher() const { return static_cast<uint8_t>(m_type) >= static_cast<uint8_t>(Type::WebCoreUserGestureIndicator); }

private:
    Type m_type { Type::None };
};

class DebuggableMicrotaskDispatcher final : public MicrotaskDispatcher {
    WTF_MAKE_COMPACT_TZONE_ALLOCATED(DebuggableMicrotaskDispatcher);
public:
    explicit DebuggableMicrotaskDispatcher()
        : MicrotaskDispatcher(Type::JSCDebuggable)
    { }

    static Ref<DebuggableMicrotaskDispatcher> create()
    {
        return adoptRef(*new DebuggableMicrotaskDispatcher());
    }

    QueuedTaskResult run(QueuedTask&) final;
    bool isRunnable() const final;
};

class QueuedTask {
    WTF_MAKE_TZONE_ALLOCATED(QueuedTask);
    friend class MicrotaskQueue;
    friend class MarkedMicrotaskDeque;
public:
    static constexpr unsigned maxArguments = maxMicrotaskArguments;
    using Result = QueuedTaskResult;

    QueuedTask(JSMicrotaskDispatcher* dispatcher)
        : m_dispatcher(std::bit_cast<JSCell*>(std::bit_cast<uintptr_t>(dispatcher) | isJSMicrotaskDispatcherFlag), static_cast<uint16_t>(InternalMicrotask::Opaque))
    {
    }

    template<typename... Args>
    requires (sizeof...(Args) <= maxArguments) && (std::is_convertible_v<Args, JSValue> && ...)
    QueuedTask(JSMicrotaskDispatcher* dispatcher, InternalMicrotask job, uint8_t payload, JSGlobalObject* globalObject, Args&&...args)
        : m_dispatcher(
            dispatcher ? std::bit_cast<JSCell*>(std::bit_cast<uintptr_t>(dispatcher) | isJSMicrotaskDispatcherFlag) : std::bit_cast<JSCell*>(globalObject),
            static_cast<uint16_t>(job) | (static_cast<uint16_t>(payload) << 8))
        , m_arguments { std::forward<Args>(args)... }
    {
    }

    void setDispatcher(JSMicrotaskDispatcher* dispatcher)
    {
        m_dispatcher.setPointer(std::bit_cast<JSCell*>(std::bit_cast<uintptr_t>(dispatcher) | isJSMicrotaskDispatcherFlag));
    }

    bool isRunnable() const;

    // Defined in MicrotaskQueueInlines.h (requires JSType knowledge).
    JSCell* dispatcher() const;
    JSGlobalObject* globalObject() const;
    JSMicrotaskDispatcher* jsMicrotaskDispatcher() const;
    std::optional<MicrotaskIdentifier> identifier() const;
    InternalMicrotask job() const { return static_cast<InternalMicrotask>(static_cast<uint8_t>(m_dispatcher.type())); }
    // Task-specific metadata stored in upper 8 bits of type field.
    // Typically holds JSPromise::Status or a nested InternalMicrotask value.
    uint8_t payload() const { return static_cast<uint8_t>(m_dispatcher.type() >> 8); }
    std::span<const JSValue, maxArguments> arguments() const { return std::span<const JSValue, maxArguments> { m_arguments, maxArguments }; }

private:
    bool isJSMicrotaskDispatcher() const
    {
        return std::bit_cast<uintptr_t>(m_dispatcher.pointer()) & isJSMicrotaskDispatcherFlag;
    }

    static constexpr uintptr_t isJSMicrotaskDispatcherFlag = 0x1;
    CompactPointerTuple<JSCell*, uint16_t> m_dispatcher;
    JSValue m_arguments[maxArguments] { };
};
static_assert(sizeof(QueuedTask) <= 32, "Size of QueuedTask is critical for performance");
static_assert(std::is_trivially_destructible_v<QueuedTask>);

class MarkedMicrotaskDeque {
public:
    friend class MicrotaskQueue;

    MarkedMicrotaskDeque() = default;

    const QueuedTask& front() const LIFETIME_BOUND { return m_queue.first(); }

    QueuedTask dequeue()
    {
        if (m_markedBefore)
            --m_markedBefore;
        return m_queue.takeFirst();
    }

    void enqueue(QueuedTask&& task)
    {
        m_queue.append(WTF::move(task));
    }

    bool isEmpty() const
    {
        return m_queue.isEmpty();
    }

    size_t size() const { return m_queue.size(); }

    void clear()
    {
        m_queue.clear();
        m_markedBefore = 0;
    }

    void beginMarking()
    {
        m_markedBefore = 0;
    }

    void swap(MarkedMicrotaskDeque& other)
    {
        m_queue.swap(other.m_queue);
        std::swap(m_markedBefore, other.m_markedBefore);
    }

    JS_EXPORT_PRIVATE bool hasMicrotasksForFullyActiveDocument() const;

    DECLARE_VISIT_AGGREGATE;

private:
    Deque<QueuedTask> m_queue;
    size_t m_markedBefore { 0 };
};

class MicrotaskQueue : public BasicRawSentinelNode<MicrotaskQueue>, public RefCounted<MicrotaskQueue> {
    WTF_MAKE_TZONE_ALLOCATED_EXPORT(MicrotaskQueue, JS_EXPORT_PRIVATE);
    WTF_MAKE_NONCOPYABLE(MicrotaskQueue);
public:
    JS_EXPORT_PRIVATE static Ref<MicrotaskQueue> create(VM&);
    JS_EXPORT_PRIVATE virtual ~MicrotaskQueue();

    inline void enqueue(QueuedTask&&);
    JS_EXPORT_PRIVATE void enqueueSlow(QueuedTask&&);

    bool isEmpty() const
    {
        return m_queue.isEmpty();
    }

    size_t size() const { return m_queue.size(); }

    void clear()
    {
        m_queue.clear();
        m_toKeep.clear();
    }

    void beginMarking()
    {
        m_queue.beginMarking();
        m_toKeep.beginMarking();
    }

    DECLARE_VISIT_AGGREGATE;

    template<bool useCallOnEachMicrotask>
    inline void performMicrotaskCheckpoint(VM&, NOESCAPE const Invocable<void(JSGlobalObject*, JSGlobalObject*)> auto& globalObjectSwitchCallback);

    bool hasMicrotasksForFullyActiveDocument() const
    {
        return m_queue.hasMicrotasksForFullyActiveDocument();
    }

    bool isScheduledToRun() const { return m_isScheduledToRun; }
    void setIsScheduledToRun(bool value) { m_isScheduledToRun = value; }

    bool isPerformingMicrotaskCheckpoint() const { return m_isPerformingMicrotaskCheckpoint; }

protected:
    JS_EXPORT_PRIVATE MicrotaskQueue(VM&);
    virtual void scheduleToRunIfNeeded()
    {
        setIsScheduledToRun(true);
    }
    bool m_isScheduledToRun { false };
    bool m_isPerformingMicrotaskCheckpoint { false };

private:
    JS_EXPORT_PRIVATE std::pair<JSGlobalObject*, bool> drainWithUseCallOnEachMicrotask(JSGlobalObject* currentGlobalObject, VM&, TopExceptionScope&);
    JS_EXPORT_PRIVATE std::pair<JSGlobalObject*, bool> drainWithoutUseCallOnEachMicrotask(JSGlobalObject* currentGlobalObject, VM&, TopExceptionScope&);

    template<bool useCallOnEachMicrotask>
    ALWAYS_INLINE std::pair<JSGlobalObject*, bool> drain(JSGlobalObject* globalObject, VM& vm, TopExceptionScope& scope)
    {
        if constexpr (useCallOnEachMicrotask)
            return drainWithUseCallOnEachMicrotask(globalObject, vm, scope);
        else
            return drainWithoutUseCallOnEachMicrotask(globalObject, vm, scope);
    }

    template<bool useCallOnEachMicrotask>
    std::pair<JSGlobalObject*, bool> drainImpl(JSGlobalObject*, VM&, TopExceptionScope&);

    MarkedMicrotaskDeque m_queue;
    MarkedMicrotaskDeque m_toKeep;
};

JS_EXPORT_PRIVATE void runMicrotaskWithDebugger(JSGlobalObject*, VM&, QueuedTask&);

} // namespace JSC
namespace WTF {

template<> struct VectorTraits<JSC::QueuedTask> : VectorTraitsBase<true, JSC::QueuedTask> {
    static constexpr bool canCompareWithMemcmp = false;
};

} // namespace WTF

WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
