BareGit
#pragma once

#include <concepts>
#include <condition_variable>
#include <coroutine>
#include <cstddef>
#include <exception>
#include <expected>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>

#include <mw/error.hpp>

namespace agent_smith::internal
{

template <typename T> struct ExpectedResultType
{
    using Type = mw::E<T>;
};

template <typename T> struct ExpectedResultType<mw::E<T>>
{
    using Type = mw::E<T>;
};

/// The flattened expected result produced by a blocking callable.
template <typename T>
using ExpectedResult =
    typename ExpectedResultType<std::remove_cvref_t<T>>::Type;

/// Private FIFO executor for work that cannot run on the agent coroutine.
class BlockingPool
{
public:
    explicit BlockingPool(std::size_t size);
    ~BlockingPool();

    BlockingPool(const BlockingPool&) = delete;
    BlockingPool& operator=(const BlockingPool&) = delete;

    template <typename F> auto run(F&& function);

private:
    struct WorkItem
    {
        std::function<void()> run;
        std::function<void()> cancel;
    };

    bool enqueue(WorkItem item);
    void worker(std::stop_token stop);

    mutable std::mutex mutex_;
    std::condition_variable condition_;
    std::queue<WorkItem> pending_;
    bool stopping_ = false;
    std::vector<std::jthread> workers_;
};

template <typename Result, typename Function> class BlockingWork
{
public:
    explicit BlockingWork(Function&& function) : function_(std::move(function))
    {
    }

    void run()
    {
        Result result = invoke();
        complete(std::move(result));
    }

    void cancel()
    {
        complete(std::unexpected(mw::runtimeError("blocking pool stopped")));
    }

    bool complete() const
    {
        std::lock_guard lock(mutex_);
        return complete_;
    }

    bool suspend(std::coroutine_handle<> continuation)
    {
        std::lock_guard lock(mutex_);
        if(complete_)
        {
            return false;
        }

        continuation_ = continuation;
        return true;
    }

    Result result()
    {
        std::lock_guard lock(mutex_);
        return std::move(*result_);
    }

private:
    Result invoke()
    {
        try
        {
            using RawResult = std::invoke_result_t<Function&>;
            if constexpr(std::is_void_v<RawResult>)
            {
                function_();
                return {};
            }
            else if constexpr(std::same_as<std::remove_cvref_t<RawResult>,
                                           Result>)
            {
                return function_();
            }
            else
            {
                return Result(function_());
            }
        }
        catch(const std::exception& error)
        {
            return std::unexpected(mw::runtimeError(error.what()));
        }
        catch(...)
        {
            return std::unexpected(mw::runtimeError(
                "blocking callable threw an unknown exception"));
        }
    }

    void complete(Result result)
    {
        std::coroutine_handle<> continuation;
        {
            std::lock_guard lock(mutex_);
            if(complete_)
            {
                return;
            }

            result_.emplace(std::move(result));
            complete_ = true;
            continuation = continuation_;
        }

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

    mutable std::mutex mutex_;
    Function function_;
    std::optional<Result> result_;
    std::coroutine_handle<> continuation_ = nullptr;
    bool complete_ = false;
};

template <typename Result, typename Function> class BlockingAwaiter
{
public:
    explicit BlockingAwaiter(
        std::shared_ptr<BlockingWork<Result, Function>> work)
        : work_(std::move(work))
    {
    }

    bool await_ready() const
    {
        return work_->complete();
    }

    bool await_suspend(std::coroutine_handle<> continuation)
    {
        return work_->suspend(continuation);
    }

    Result await_resume()
    {
        return work_->result();
    }

private:
    std::shared_ptr<BlockingWork<Result, Function>> work_;
};

template <typename F> auto BlockingPool::run(F&& function)
{
    using Function = std::decay_t<F>;
    using Result = ExpectedResult<std::invoke_result_t<Function&>>;
    using Work = BlockingWork<Result, Function>;

    auto work = std::make_shared<Work>(std::forward<F>(function));
    WorkItem item{
        .run = [work]() { work->run(); },
        .cancel = [work]() { work->cancel(); },
    };

    if(!enqueue(std::move(item)))
    {
        work->cancel();
    }

    return BlockingAwaiter<Result, Function>(std::move(work));
}

} // namespace agent_smith::internal