namespace QuestionsApi.Services.Flashcards; // Small pure helpers pulled out of FlashcardStudyService/FlashcardScheduleStore so the // daily-limit and sibling-bury rules can be unit tested without a live Mongo instance. public static class FlashcardScheduling { // A card reviewed on a given local study day buries its siblings until the next local day. public static DateTime NextStudyDayStart(DateTime studyDayStartUtc) => studyDayStartUtc.Date.AddDays(1); // Effective per-day budget: deck override wins over the global default, minus what's // already been consumed on the client's current local study day. public static int ResolveDailyBudget(int globalLimit, int? deckOverride, int consumedToday) => Math.Max(0, (deckOverride ?? globalLimit) - consumedToday); // A streak counts consecutive calendar days with at least one review, ending either today or // yesterday (so the streak doesn't visibly reset to 0 the moment a new day starts before // today's first review) - the same semantics as most habit-streak trackers. public static int ComputeStreakDays(IReadOnlyCollection reviewTimestamps, DateTime todayUtc) { var reviewDays = reviewTimestamps.Select(v => v.Date).ToHashSet(); var cursor = todayUtc.Date; if (!reviewDays.Contains(cursor)) { cursor = cursor.AddDays(-1); if (!reviewDays.Contains(cursor)) return 0; } var streak = 0; while (reviewDays.Contains(cursor)) { streak++; cursor = cursor.AddDays(-1); } return streak; } }