#include "schedule/schedule_engine.h" namespace radiantwave::schedule { namespace { const char* dayKey(Weekday day) { switch (day) { case Weekday::Mon: return "mon"; case Weekday::Tue: return "tue"; case Weekday::Wed: return "wed"; case Weekday::Thu: return "thu"; case Weekday::Fri: return "fri"; case Weekday::Sat: return "sat"; case Weekday::Sun: return "sun"; } return "mon"; } bool blockIsValid(const api::TargetBlock& block) { return !block.proclamation_ids.empty() && block.start_minutes >= 0 && block.start_minutes <= 1440 && block.end_minutes >= 0 && block.end_minutes <= 1440 && block.start_minutes != block.end_minutes; } void warnInvalid(const api::TargetBlock& block, const std::function& warn) { if (warn) { warn("Skipping invalid schedule block id=" + block.id); } } // Find a matching block among `candidates` at `minutes`, skipping invalid ones. bool match(const std::vector& candidates, int minutes, api::TargetBlock& out, const std::function& warn) { for (const auto& block : candidates) { if (!blockIsValid(block)) { warnInvalid(block, warn); continue; } if (minuteInBlock(minutes, block.start_minutes, block.end_minutes)) { out = block; return true; } } return false; } } // namespace bool minuteInBlock(int minutes, int startMinutes, int endMinutes) { if (startMinutes < endMinutes) { return minutes >= startMinutes && minutes < endMinutes; } // Midnight-crossing: [start, 1440) plus [0, end). return minutes >= startMinutes || minutes < endMinutes; } Weekday weekdayFromIndex(int index) { if (index < 0) index = 0; return static_cast(index % 7); } Decision evaluate(const api::SchedulePayload& payload, int minutes, Weekday weekday, std::function warn) { Decision decision; if (minutes < 0) minutes = 0; minutes %= 1440; if (payload.type == "daily") { decision.hasBlock = match(payload.daily, minutes, decision.block, warn); return decision; } if (payload.type == "weekly") { // Only blocks for today... std::vector today; const std::string todayKey = dayKey(weekday); for (const auto& block : payload.weekly) { if (block.day == todayKey) today.push_back(block); } if (match(today, minutes, decision.block, warn)) { decision.hasBlock = true; return decision; } // ...or a crossing Block from yesterday that spills into today's early // hours. (Sunday's spill reaches Monday via the day rollover.) const int prevIndex = (static_cast(weekday) + 6) % 7; const std::string prevKey = dayKey(static_cast(prevIndex)); for (const auto& block : payload.weekly) { if (block.day != prevKey) continue; if (block.end_minutes >= block.start_minutes) continue; // not crossing if (!blockIsValid(block)) { warnInvalid(block, warn); continue; } if (minutes < block.end_minutes) { decision.block = block; decision.hasBlock = true; return decision; } } return decision; } return decision; // type null / unknown -> no active block } } // namespace radiantwave::schedule