import 'models.dart'; /// Derived backlog filters for a unified backlog list. enum BacklogFilter { inbox, pushed, criticalMissed, wishlist, stale, noRewardSet, } /// Sort options for a unified backlog list. enum BacklogSortKey { priority, rewardVsEffort, age, project, timesPushed, } /// Visual age bucket for backlog display. enum BacklogStalenessMarker { green, blue, purple, } /// Configurable thresholds for backlog age markers. class BacklogStalenessSettings { const BacklogStalenessSettings({ this.greenMaxAge = const Duration(days: 7), this.blueMaxAge = const Duration(days: 30), }); final Duration greenMaxAge; final Duration blueMaxAge; BacklogStalenessMarker markerFor({ required Task task, required DateTime now, }) { final age = now.difference(task.createdAt); if (age <= greenMaxAge) { return BacklogStalenessMarker.green; } if (age <= blueMaxAge) { return BacklogStalenessMarker.blue; } return BacklogStalenessMarker.purple; } } /// Read-only backlog projection over the unified task list. class BacklogView { const BacklogView({ required this.tasks, required this.now, this.staleAfter = const Duration(days: 31), this.stalenessSettings = const BacklogStalenessSettings(), }); final List tasks; final DateTime now; final Duration staleAfter; final BacklogStalenessSettings stalenessSettings; List get backlogTasks { return tasks.where((task) => task.isBacklog).toList(growable: false); } List filter(BacklogFilter filter) { return backlogTasks.where((task) => _matchesFilter(task, filter)).toList( growable: false, ); } List sorted(BacklogSortKey sortKey) { final sortedTasks = [...backlogTasks]; sortedTasks.sort((a, b) => _compareTasks(a, b, sortKey)); return List.unmodifiable(sortedTasks); } BacklogStalenessMarker stalenessMarkerFor(Task task) { return stalenessSettings.markerFor(task: task, now: now); } bool _matchesFilter(Task task, BacklogFilter filter) { return switch (filter) { BacklogFilter.inbox => task.projectId == 'inbox', BacklogFilter.pushed => task.stats.manuallyPushedCount > 0 || task.stats.autoPushedCount > 0, BacklogFilter.criticalMissed => task.type == TaskType.critical && task.stats.missedCount > 0, BacklogFilter.wishlist => task.backlogTags.contains(BacklogTag.wishlist), BacklogFilter.stale => now.difference(task.updatedAt) >= staleAfter, BacklogFilter.noRewardSet => task.reward == RewardLevel.notSet, }; } int _compareTasks(Task a, Task b, BacklogSortKey sortKey) { return switch (sortKey) { BacklogSortKey.priority => _priorityRank(b.priority).compareTo(_priorityRank(a.priority)), BacklogSortKey.rewardVsEffort => _rewardVsEffortScore(b).compareTo(_rewardVsEffortScore(a)), BacklogSortKey.age => a.createdAt.compareTo(b.createdAt), BacklogSortKey.project => a.projectId.compareTo(b.projectId), BacklogSortKey.timesPushed => _timesPushed(b).compareTo(_timesPushed(a)), }; } } int _priorityRank(PriorityLevel? priority) { return switch (priority) { PriorityLevel.veryLow => 0, PriorityLevel.low => 1, PriorityLevel.medium || null => 2, PriorityLevel.high => 3, PriorityLevel.veryHigh => 4, }; } int _rewardRank(RewardLevel reward) { return switch (reward) { RewardLevel.notSet => 0, RewardLevel.veryLow => 1, RewardLevel.low => 2, RewardLevel.medium => 3, RewardLevel.high => 4, RewardLevel.veryHigh => 5, }; } int _difficultyRank(DifficultyLevel difficulty) { return switch (difficulty) { DifficultyLevel.notSet => 0, DifficultyLevel.veryEasy => 1, DifficultyLevel.easy => 2, DifficultyLevel.medium => 3, DifficultyLevel.hard => 4, DifficultyLevel.veryHard => 5, }; } int _rewardVsEffortScore(Task task) { return _rewardRank(task.reward) - _difficultyRank(task.difficulty); } int _timesPushed(Task task) { return task.stats.manuallyPushedCount + task.stats.autoPushedCount; }