BareGit
#pragma once

#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <stop_token>
#include <string>
#include <unordered_map>
#include <vector>

#include "configuration.h"
#include "data_source_interface.h"
#include "service.h"
#include "thread_pool.h"

/// Dispatch recurring endpoint tasks to a fixed-capacity worker pool.
class Scheduler
{
public:
    /// Monotonic clock used for probe scheduling.
    using Clock = std::chrono::steady_clock;

    /// Factory called by worker tasks to create their probes. Custom factories
    /// must support concurrent calls for different services.
    using ProbeFactory = std::function<
        mw::E<std::unique_ptr<ProbeInterface>>(const Service&)>;

    /// Copy configured services and create workers. The data source must
    /// outlive the scheduler and support concurrent saves. Duplicate IDs and
    /// invalid settings return errors.
    static mw::E<std::unique_ptr<Scheduler>> create(
        const Configuration& configuration, DataSourceInterface& data_source,
        ProbeFactory probe_factory = &Service::createProbe);

    /// Find a service by ID, or return nullptr. The pointer stays valid until
    /// scheduler destruction; its configuration is immutable after creation.
    const Service* findService(const std::string& service_id) const;

    /// Release workers. run() must have returned before destruction.
    ~Scheduler() = default;

    /// Run once on the calling thread, initially making every task due.
    /// Skip occurrences while busy and preserve each task's interval cadence.
    /// Stop on cancellation or execution error, then finish accepted work.
    mw::E<void> run(std::stop_token stop_token);

    /// Find the next interval boundary strictly after now, without catching up.
    static mw::E<Clock::time_point> nextDueTime(
        Clock::time_point previous, Clock::duration interval,
        Clock::time_point now);

private:
    struct Task
    {
        Scheduler& scheduler;
        Service& service;
        std::unique_ptr<ProbeInterface> probe;

        void operator()();
    };

    struct ScheduledTask
    {
        Service* service;
        Clock::time_point due;
    };

    struct Later
    {
        bool operator()(const ScheduledTask& left,
                        const ScheduledTask& right) const;
    };

    Scheduler(DataSourceInterface& data_source, ProbeFactory probe_factory);
    mw::E<void> runLoop(std::stop_token stop_token);
    mw::E<void> dispatch(Service& service);
    void execute(Task& task);
    void reportError(const std::string& service_id, mw::Error error);
    bool hasError() const;

    std::mutex mutex;
    std::condition_variable_any wake;
    bool started = false;
    std::optional<mw::Error> error;
    std::priority_queue<ScheduledTask, std::vector<ScheduledTask>, Later> queue;
    std::unordered_map<std::string, std::unique_ptr<Service>> services;
    DataSourceInterface& data_source;
    ProbeFactory probe_factory;
    // Destroy workers before the task state they reference.
    std::unique_ptr<ThreadPool> pool;
};