#pragma once #include "CivilTime.h" #include "Model.h" #include "SessionClock.h" #include namespace timbre_core { struct ScheduleDecision { bool due = false; bool firstObservation = false; bool alreadyProcessed = false; bool blockedByHoliday = false; bool invalidTime = false; ScheduleId scheduleId = kInvalidId; PatternId patternId = PatternId::A; std::uint64_t missedMinutes = 0; constexpr explicit operator bool() const noexcept { return due; } constexpr bool triggered() const noexcept { return due; } }; using ScheduleMatch = ScheduleDecision; class Scheduler { public: Scheduler() = default; // Only the currently observed local minute is considered. If the source // jumped from minute N to N+K, the intermediate minutes are intentionally // discarded rather than replayed. ScheduleDecision poll(const AppState& state, const CivilDateTime& localTime, bool firstObservation = false); ScheduleDecision process(const AppState& state, const CivilDateTime& localTime, bool firstObservation = false) { return poll(state, localTime, firstObservation); } // Convenience integration with SessionClock. The caller supplies the // current wall value; the injected monotonic source remains authoritative // inside SessionClock. ScheduleDecision poll(const AppState& state, SessionClock& clock, std::int64_t utcEpochSeconds); ScheduleDecision poll(const AppState& state, const SessionClock::Observation& observation) { if (!observation.valid) { ScheduleDecision decision; decision.invalidTime = true; return decision; } return poll(state, observation.local, observation.firstObservation); } ScheduleDecision process(const AppState& state, SessionClock& clock, std::int64_t utcEpochSeconds) { return poll(state, clock, utcEpochSeconds); } ScheduleDecision tick(const AppState& state, const CivilDateTime& localTime, bool firstObservation = false) { return poll(state, localTime, firstObservation); } ScheduleDecision evaluate(const AppState& state, const CivilDateTime& localTime, bool firstObservation = false) { return poll(state, localTime, firstObservation); } bool hasLastKey() const noexcept { return hasLastKey_; } const LocalMinuteKey& lastKey() const noexcept { return lastKey_; } const LocalMinuteKey& key() const noexcept { return lastKey_; } void reset() noexcept; static bool isHoliday(const AppState& state, std::uint8_t month, std::uint8_t day) noexcept; static bool scheduleMatches(const Schedule& schedule, const AppState& state, const CivilDateTime& localTime) noexcept; private: bool hasLastKey_ = false; LocalMinuteKey lastKey_{}; }; } // namespace timbre_core