import 'dart:async'; import '../../../../core/cache/cache_entry.dart'; import '../../../../core/cache/cache_policy.dart'; import '../../../../core/cache/hybrid_cache_store.dart'; import '../models/schedule_item_model.dart'; class CalendarRepository { final HybridCacheStore store; final CachePolicy policy; final DateTime Function() now; final Future> Function(DateTime date) loadDayFromRemote; final Future> Function(DateTime start, DateTime end) loadMonthFromRemote; final Map> _refreshInFlight = >{}; CalendarRepository({ required this.store, required this.loadDayFromRemote, required this.loadMonthFromRemote, CachePolicy? policy, DateTime Function()? now, }) : policy = policy ?? const CachePolicy( softTtl: Duration(minutes: 2), hardTtl: Duration(minutes: 30), minRefreshInterval: Duration(minutes: 1), ), now = now ?? DateTime.now; static String dayKey(DateTime date) { final day = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; return 'calendar:day:$day'; } static String monthKey(DateTime date) { return 'calendar:month:${date.year}-${date.month.toString().padLeft(2, '0')}'; } Future> getDayEvents( DateTime date, { bool forceRefresh = false, }) async { final key = dayKey(date); if (forceRefresh) { return _refreshDayAndRead(date, key); } final cached = await store.read>>(key); if (cached == null) { return _refreshDayAndRead(date, key); } final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt); if (decision.shouldRefreshInBackground) { _refreshDayInBackground(date, key); } if (decision.mustBlockForNetwork || !decision.canUseCached) { return _refreshDayAndRead(date, key); } return cached.value; } Future> getMonthEvents( DateTime monthStart, { bool forceRefresh = false, }) async { final key = monthKey(monthStart); if (forceRefresh) { return _refreshMonthAndRead(monthStart, key); } final cached = await store.read>>(key); if (cached == null) { return _refreshMonthAndRead(monthStart, key); } final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt); if (decision.shouldRefreshInBackground) { _refreshMonthInBackground(monthStart, key); } if (decision.mustBlockForNetwork || !decision.canUseCached) { return _refreshMonthAndRead(monthStart, key); } return cached.value; } Future> _refreshDayAndRead( DateTime date, String key, ) async { await _refreshDay(date, key); final cached = await store.read>>(key); return cached?.value ?? const []; } Future> _refreshMonthAndRead( DateTime monthStart, String key, ) async { await _refreshMonth(monthStart, key); final cached = await store.read>>(key); return cached?.value ?? const []; } Future _refreshDay(DateTime date, String key) async { final remote = await loadDayFromRemote(date); await store.write>>( key, CacheEntry>(value: remote, fetchedAt: now()), ); } Future _refreshMonth(DateTime monthStart, String key) async { final start = DateTime(monthStart.year, monthStart.month, 1); final end = DateTime(monthStart.year, monthStart.month + 1, 0, 23, 59, 59); final remote = await loadMonthFromRemote(start, end); await store.write>>( key, CacheEntry>(value: remote, fetchedAt: now()), ); } void _refreshDayInBackground(DateTime date, String key) { _refreshInBackground(key, () => _refreshDay(date, key)); } void _refreshMonthInBackground(DateTime monthStart, String key) { _refreshInBackground(key, () => _refreshMonth(monthStart, key)); } void _refreshInBackground(String key, Future Function() taskFactory) { if (_refreshInFlight.containsKey(key)) { return; } final task = taskFactory().whenComplete(() { _refreshInFlight.remove(key); }); _refreshInFlight[key] = task; unawaited(task); } }