#pragma once

#include <atomic>
#include <condition_variable>
#include <chrono>
#include <cstdint>
#include <mutex>

namespace ta::application::runtime
{
    class CallbackBarrier final
    {
    public:
        void open() noexcept
        {
            accepting_feature_work_.store(true, std::memory_order_release);
        }

        void begin_shutdown() noexcept
        {
            accepting_feature_work_.store(false, std::memory_order_release);
        }

        [[nodiscard]] bool accepts_feature_work() const noexcept
        {
            return accepting_feature_work_.load(std::memory_order_acquire);
        }

        void enter() noexcept
        {
            active_.fetch_add(1, std::memory_order_acq_rel);
        }

        void leave() noexcept
        {
            if (active_.fetch_sub(1, std::memory_order_acq_rel) == 1)
            {
                std::lock_guard lock(wait_mutex_);
                wait_cv_.notify_all();
            }
        }

        [[nodiscard]] bool wait_for_idle(
            const std::chrono::milliseconds timeout = std::chrono::seconds(5)) noexcept
        {
            std::unique_lock lock(wait_mutex_);
            return wait_cv_.wait_for(lock, timeout, [this]
            {
                return active_.load(std::memory_order_acquire) == 0;
            });
        }


    private:
        std::atomic<bool> accepting_feature_work_{false};
        std::atomic<std::uint32_t> active_{0};
        std::mutex wait_mutex_;
        std::condition_variable wait_cv_;
    };

    inline CallbackBarrier& callback_barrier() noexcept
    {
        static CallbackBarrier instance;
        return instance;
    }

    class CallbackGuard final
    {
    public:
        CallbackGuard() noexcept
        {
            auto& barrier = callback_barrier();
            barrier.enter();
            entered_ = true;
            accepted_ = barrier.accepts_feature_work();
        }

        ~CallbackGuard() noexcept
        {
            if (entered_)
                callback_barrier().leave();
        }

        [[nodiscard]] explicit operator bool() const noexcept
        {
            return accepted_;
        }

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

    private:
        bool entered_ = false;
        bool accepted_ = false;
    };
}
