BareGit
#pragma once

#include <coroutine>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
#include <utility>

#include <mw/task.hpp>

namespace agent_smith::internal
{

/// A closeable queue that resumes consumers when another thread pushes data.
template <typename T> class AsyncQueue
{
public:
    /// Add an item unless the queue has already been closed.
    void push(T value)
    {
        std::shared_ptr<PopState> waiter;
        std::coroutine_handle<> continuation;
        {
            std::lock_guard lock(mutex_);
            if(closed_)
            {
                return;
            }

            if(waiters_.empty())
            {
                values_.push_back(std::move(value));
                return;
            }

            waiter = std::move(waiters_.front());
            waiters_.pop_front();
            waiter->value.emplace(std::move(value));
            continuation = waiter->continuation;
        }

        if(continuation != nullptr)
        {
            continuation.resume();
        }
    }

    /// Wait for one item, or nullopt after closure and queue drainage.
    mw::Task<std::optional<T>> pop()
    {
        co_return co_await PopAwaiter(*this);
    }

    /// Prevent further pushes and wake consumers once buffered items are read.
    void close()
    {
        std::deque<std::shared_ptr<PopState>> waiters;
        {
            std::lock_guard lock(mutex_);
            if(closed_)
            {
                return;
            }

            closed_ = true;
            if(!values_.empty())
            {
                return;
            }

            waiters.swap(waiters_);
        }

        for(const auto& waiter : waiters)
        {
            if(waiter->continuation != nullptr)
            {
                waiter->continuation.resume();
            }
        }
    }

private:
    struct PopState
    {
        std::optional<T> value;
        std::coroutine_handle<> continuation = nullptr;
    };

    class PopAwaiter
    {
    public:
        explicit PopAwaiter(AsyncQueue& queue)
            : queue_(&queue), state_(std::make_shared<PopState>())
        {
        }

        bool await_ready()
        {
            std::lock_guard lock(queue_->mutex_);
            return queue_->take(*state_);
        }

        bool await_suspend(std::coroutine_handle<> continuation)
        {
            std::lock_guard lock(queue_->mutex_);
            if(queue_->take(*state_))
            {
                return false;
            }

            state_->continuation = continuation;
            queue_->waiters_.push_back(state_);
            return true;
        }

        std::optional<T> await_resume()
        {
            return std::move(state_->value);
        }

    private:
        AsyncQueue* queue_;
        std::shared_ptr<PopState> state_;
    };

    bool take(PopState& state)
    {
        if(!values_.empty())
        {
            state.value.emplace(std::move(values_.front()));
            values_.pop_front();
            return true;
        }

        return closed_;
    }

    std::mutex mutex_;
    std::deque<T> values_;
    std::deque<std::shared_ptr<PopState>> waiters_;
    bool closed_ = false;
};

} // namespace agent_smith::internal