/*
 * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
 * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/Variant.h>
#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>

namespace JS {

// 27.6.2 Properties of AsyncGenerator Instances, https://tc39.es/ecma262/#sec-properties-of-asyncgenerator-intances
class AsyncGenerator final : public Object {
    JS_OBJECT(AsyncGenerator, Object);
    GC_DECLARE_ALLOCATOR(AsyncGenerator);

public:
    enum class State {
        SuspendedStart,
        SuspendedYield,
        Executing,
        AwaitingReturn,
        Completed,
    };

    static GC::Ref<AsyncGenerator> create(Realm&, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>>, NonnullOwnPtr<ExecutionContext>);

    virtual ~AsyncGenerator() override;

    void async_generator_enqueue(Completion, GC::Ref<PromiseCapability>);
    ThrowCompletionOr<void> resume(VM&, Completion completion);
    void await_return();
    void complete_step(Completion, bool done, Realm* realm = nullptr);
    void drain_queue();

    State async_generator_state() const { return m_async_generator_state; }
    void set_async_generator_state(Badge<AsyncGeneratorPrototype>, State value);

    Optional<String> const& generator_brand() const { return m_generator_brand; }

    void set_pending_completion(Completion const& completion)
    {
        m_pending_completion_value = completion.value();
        m_pending_completion_type = completion.type();
    }
    Value pending_completion_value() const { return m_pending_completion_value; }
    Completion::Type pending_completion_type() const { return m_pending_completion_type; }
    void set_pending_completion_type(Completion::Type completion_type) { m_pending_completion_type = completion_type; }
    void clear_pending_completion()
    {
        m_pending_completion_value = js_undefined();
        m_pending_completion_type = Completion::Type::Normal;
    }

private:
    AsyncGenerator(Realm&, Object* prototype, NonnullOwnPtr<ExecutionContext>, GC::Ref<Bytecode::Executable>);

    virtual void visit_edges(Cell::Visitor&) override;

    void execute(VM&, Completion completion);
    ThrowCompletionOr<void> await(Value);

    // At the time of constructing an AsyncGenerator, we still need to point to an
    // execution context on the stack, but later need to 'adopt' it.
    State m_async_generator_state { State::SuspendedStart };   // [[AsyncGeneratorState]]
    NonnullOwnPtr<ExecutionContext> m_async_generator_context; // [[AsyncGeneratorContext]]
    Vector<AsyncGeneratorRequest> m_async_generator_queue;     // [[AsyncGeneratorQueue]]
    Optional<String> m_generator_brand;                        // [[GeneratorBrand]]

    GC::Ref<Bytecode::Executable> m_generating_executable;
    u32 m_yield_continuation { ExecutionContext::no_yield_continuation };
    GC::Ptr<Promise> m_current_promise;
    Value m_pending_completion_value { js_undefined() };
    Completion::Type m_pending_completion_type { Completion::Type::Normal };
};

}
