recommendedNextStep function

  1. @riverpod
Future<RecommendedStep?> recommendedNextStep(
  1. Ref<Object?> ref
)

Implementation

@riverpod
Future<RecommendedStep?> recommendedNextStep(Ref ref) async {
  // Check due review cards first.
  final dueCards = await ref.watch(dueCardsProvider.future);
  if (dueCards.isNotEmpty) {
    return RecommendedStep(
      title: 'Review ${dueCards.length} card${dueCards.length == 1 ? '' : 's'}',
      subtitle: 'Spaced repetition keeps knowledge fresh',
      route: '/review',
      icon: Icons.refresh_rounded,
      color: const Color(0xFFFFB74D), // DuTaToTheme.warning
    );
  }

  // Check progress across all domains.
  final allProgress = await ref.watch(allProgressProvider.future);
  final domains = await ref.watch(domainsProvider.future);

  // Build mastery map: topicId -> mastery.
  final masteryMap = <String, String>{};
  for (final p in allProgress) {
    masteryMap[p.topicId] = p.mastery;
  }

  // Fetch active goals to prioritize goal-linked domains.
  final goals = await ref.watch(goalsProvider.future);
  final goalDomainIds = goals
      .where((g) => g.status == 'active' && g.domainId != null)
      .map((g) => g.domainId!)
      .toSet();

  // Categorize content topics: goal domains first, then others.
  final goalTopics = <String>[];
  final otherTopics = <String>[];
  for (final domain in domains) {
    final ids = await ref.watch(topicIdsWithContentProvider(domain.id).future);
    if (goalDomainIds.contains(domain.id)) {
      goalTopics.addAll(ids);
    } else {
      otherTopics.addAll(ids);
    }
  }

  // Find best topic: goal domains first, prefer advancing started topics.
  final bestTopicId =
      _findBestTopic(goalTopics, masteryMap) ??
      _findBestTopic(otherTopics, masteryMap);

  if (bestTopicId == null) {
    return null; // All caught up.
  }

  final bestMastery = masteryMap[bestTopicId] ?? 'unstarted';

  // Resolve topic name.
  final ds = ref.watch(supabaseDatasourceProvider);
  final topic = await ds.getTopicById(bestTopicId);
  final topicTitle = topic?.title ?? 'a topic';
  final encodedTitle = Uri.encodeComponent(topicTitle);

  final domainId = topic?.domainId;

  // Resolve domain slug for human-readable URLs.
  String? domainSlug;
  if (domainId != null) {
    final domain = domains.where((d) => d.id == domainId).firstOrNull;
    domainSlug = domain?.slug;
  }

  String lessonRoute(String slug) =>
      '/domains/$slug/topics/$bestTopicId/lesson?title=$encodedTitle';

  return switch (bestMastery) {
    'unstarted' => RecommendedStep(
      title: 'Read: $topicTitle',
      subtitle: 'Start your journey with this topic',
      route: domainSlug != null ? lessonRoute(domainSlug) : '/domains',
      icon: Icons.menu_book_rounded,
      color: const Color(0xFF42A5F5), // blue
    ),
    'awareness' => RecommendedStep(
      title: 'Socratic Dialogue: $topicTitle',
      subtitle: 'Deepen understanding through guided questions',
      route:
          '/tutor/chat?mode=socratic&topicId=$bestTopicId&topicTitle=$encodedTitle'
          '${domainSlug != null ? '&from=${Uri.encodeComponent(lessonRoute(domainSlug))}' : ''}',
      icon: Icons.question_answer_rounded,
      color: const Color(0xFF6C63FF), // purple
    ),
    'understanding' => RecommendedStep(
      title: 'Quiz: $topicTitle',
      subtitle: 'Test your knowledge to advance to Application',
      route:
          '/tutor/chat?mode=quiz&topicId=$bestTopicId&topicTitle=$encodedTitle'
          '${domainSlug != null ? '&from=${Uri.encodeComponent(lessonRoute(domainSlug))}' : ''}',
      icon: Icons.quiz_rounded,
      color: const Color(0xFFFFB74D), // amber
    ),
    'application' => RecommendedStep(
      title: 'Code Review: $topicTitle',
      subtitle: 'Code Review or Confused Student to reach Evaluation',
      route:
          '/tutor/chat?mode=code_review&topicId=$bestTopicId&topicTitle=$encodedTitle'
          '${domainSlug != null ? '&from=${Uri.encodeComponent(lessonRoute(domainSlug))}' : ''}',
      icon: Icons.code_rounded,
      color: const Color(0xFF66BB6A), // green
    ),
    _ => null,
  };
}