#pragma once

#include <atomic>
#include <cstdint>

namespace ta::foundation::diagnostics
{
    class Counter final
    {
    public:
        void increment() noexcept { value_.fetch_add(1, std::memory_order_relaxed); }
        void reset() noexcept { value_.store(0, std::memory_order_release); }
        [[nodiscard]] std::uint64_t value() const noexcept
        { return value_.load(std::memory_order_acquire); }

    private:
        std::atomic<std::uint64_t> value_{0};
    };

    class Flag final
    {
    public:
        void set(bool value) noexcept { value_.store(value, std::memory_order_release); }
        void reset() noexcept { set(false); }
        [[nodiscard]] bool value() const noexcept
        { return value_.load(std::memory_order_acquire); }

    private:
        std::atomic_bool value_{false};
    };


    struct TimingMetricSnapshot final
    {
        double latest_ms = 0.0;
        double average_ms = 0.0;
        double max_ms = 0.0;
        std::uint64_t samples = 0;
    };

    class TimingMetric final
    {
    public:
        void reset() noexcept
        {
            latest_ns_.store(0, std::memory_order_release);
            total_ns_.store(0, std::memory_order_release);
            max_ns_.store(0, std::memory_order_release);
            samples_.store(0, std::memory_order_release);
        }

        void record(const std::uint64_t nanoseconds) noexcept
        {
            latest_ns_.store(nanoseconds, std::memory_order_release);
            total_ns_.fetch_add(nanoseconds, std::memory_order_relaxed);
            samples_.fetch_add(1, std::memory_order_relaxed);
            std::uint64_t previous = max_ns_.load(std::memory_order_relaxed);
            while (previous < nanoseconds &&
                   !max_ns_.compare_exchange_weak(previous, nanoseconds,
                       std::memory_order_relaxed, std::memory_order_relaxed))
            {
            }
        }

        [[nodiscard]] TimingMetricSnapshot snapshot() const noexcept
        {
            const std::uint64_t sample_count = samples_.load(std::memory_order_acquire);
            const std::uint64_t latest = latest_ns_.load(std::memory_order_acquire);
            const std::uint64_t total = total_ns_.load(std::memory_order_acquire);
            const std::uint64_t maximum = max_ns_.load(std::memory_order_acquire);
            constexpr double ns_to_ms = 1.0 / 1'000'000.0;
            return {
                static_cast<double>(latest) * ns_to_ms,
                sample_count == 0 ? 0.0 : static_cast<double>(total) * ns_to_ms / static_cast<double>(sample_count),
                static_cast<double>(maximum) * ns_to_ms,
                sample_count
            };
        }

    private:
        std::atomic<std::uint64_t> latest_ns_{0};
        std::atomic<std::uint64_t> total_ns_{0};
        std::atomic<std::uint64_t> max_ns_{0};
        std::atomic<std::uint64_t> samples_{0};
    };

    struct RequestConsumeSnapshot final
    {
        std::uint64_t requested = 0;
        std::uint64_t consumed = 0;
    };

    class RequestConsumeMetrics final
    {
    public:
        void mark_requested() noexcept { requested_.increment(); }
        void mark_consumed() noexcept { consumed_.increment(); }
        void reset() noexcept { requested_.reset(); consumed_.reset(); }
        [[nodiscard]] RequestConsumeSnapshot snapshot() const noexcept
        { return {requested_.value(), consumed_.value()}; }

    private:
        Counter requested_;
        Counter consumed_;
    };
}
