#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;
};