import '../data/models/schedule_item_model.dart'; class ReminderOverlapGroup { final DateTime fireAt; final List events; const ReminderOverlapGroup({required this.fireAt, required this.events}); bool get isAggregate => events.length > 1; } class ReminderOverlapPolicy { const ReminderOverlapPolicy(); List groupByMinute( Iterable events, { required DateTime now, }) { final buckets = >{}; final minuteToFireAt = {}; for (final event in events) { final fireAt = resolveFirstFireAt(event, now: now); if (fireAt == null) { continue; } final minute = DateTime( fireAt.year, fireAt.month, fireAt.day, fireAt.hour, fireAt.minute, ); final key = minute.toIso8601String(); buckets.putIfAbsent(key, () => []).add(event); minuteToFireAt[key] = minuteToFireAt[key] ?? fireAt; } final groups = buckets.entries .map( (entry) => ReminderOverlapGroup( fireAt: minuteToFireAt[entry.key]!, events: entry.value, ), ) .toList(); groups.sort((left, right) => left.fireAt.compareTo(right.fireAt)); return groups; } DateTime? resolveFirstFireAt( ScheduleItemModel event, { required DateTime now, }) { if (event.status != ScheduleStatus.active) { return null; } final reminderMinutes = event.metadata?.reminderMinutes; if (reminderMinutes == null) { return null; } final remindAt = event.startAt.subtract(Duration(minutes: reminderMinutes)); final endAt = event.endAt; if (endAt != null && !now.isBefore(endAt)) { return null; } if (now.isBefore(remindAt)) { return remindAt; } if (endAt != null && now.isBefore(endAt)) { return now.add(const Duration(seconds: 5)); } return null; } }