import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/utils/date_time_utils.dart'; import '../../../../core/utils/uuid.dart'; import '../../data/repositories/in_memory_exercise_repository.dart'; import '../../domain/entities/exercise_entry.dart'; import '../../domain/entities/muscle_group.dart'; import '../../domain/repositories/exercise_repository.dart'; /// Application-wide exercise repository. /// /// Overridable in widget/unit tests with a fake implementation. final exerciseRepositoryProvider = Provider( (ref) => InMemoryExerciseRepository(), ); /// Immutable snapshot of the resistance-training log. class ExerciseState { const ExerciseState({ required this.entries, required this.weekStart, this.errorMessage, }); /// All logged entries, newest first. final List entries; /// First day (Monday) of the current ISO week at local start-of-day. final DateTime weekStart; /// Last persistence failure message, cleared on the next success. final String? errorMessage; /// The seven days of the current window, Monday through Sunday. List get weekDays => List.unmodifiable( List.generate( 7, (index) => DateTime( weekStart.year, weekStart.month, weekStart.day + index, ), ), ); /// Entries logged inside the current ISO week, newest first. List get entriesThisWeek => entries .where( (entry) => !entry.timestamp.isBefore(weekStart) && entry.timestamp.isBefore(weekStart.add(const Duration(days: 7))), ) .toList(growable: false); /// Number of distinct calendar days with at least one logged session in /// the current week — the weekly strength-workout frequency used for the /// lean-mass-preservation card. int get weeklyWorkoutCount { final days = {}; for (final entry in entriesThisWeek) { days.add(startOfDay(entry.timestamp)); } return days.length; } ExerciseState copyWith({ List? entries, DateTime? weekStart, Object? errorMessage = _keep, }) => ExerciseState( entries: entries ?? this.entries, weekStart: weekStart ?? this.weekStart, errorMessage: identical(errorMessage, _keep) ? this.errorMessage : errorMessage as String?, ); /// Sentinel allowing `copyWith` to explicitly reset a field to `null`. static const Object _keep = Object(); } /// Orchestrates the resistance-training log: persists working sets, derives /// the weekly strength-session frequency (target: 3 sessions/week for lean /// mass preservation during GLP-1 therapy), and exposes delete/error flows. class ExerciseController extends Notifier { /// Recommended weekly strength-session frequency for lean mass /// preservation on GLP-1/GIP agonist therapy. static const int recommendedWeeklySessions = 3; @override ExerciseState build() => ExerciseState( entries: const [], weekStart: startOfWeek(DateTime.now()), ); /// Persists a working set and refreshes the snapshot. /// /// Returns whether the write succeeded. Future logExercise({ required MuscleGroup muscleGroup, required String exerciseName, required int sets, required int reps, double weightLbs = 0, DateTime? timestamp, }) async { final trimmed = exerciseName.trim(); if (trimmed.isEmpty || sets <= 0 || reps <= 0 || weightLbs < 0) { state = state.copyWith(errorMessage: 'Check the entered values.'); return false; } try { await ref.read(exerciseRepositoryProvider).saveEntry( ExerciseEntry( id: generateUuid(), timestamp: timestamp ?? DateTime.now(), muscleGroup: muscleGroup, exerciseName: trimmed, sets: sets, reps: reps, weightLbs: weightLbs, ), ); await _refresh(); return true; } catch (error, stackTrace) { debugPrintStack(stackTrace: stackTrace, label: 'saveEntry failed'); state = state.copyWith(errorMessage: 'Could not log the exercise.'); return false; } } /// Removes an entry by id. Future deleteEntry(String id) async { try { await ref.read(exerciseRepositoryProvider).deleteEntry(id); await _refresh(); return true; } catch (error, stackTrace) { debugPrintStack(stackTrace: stackTrace, label: 'deleteEntry failed'); state = state.copyWith(errorMessage: 'Could not delete the entry.'); return false; } } /// Clears the transient error banner. void clearError() { if (state.errorMessage != null) { state = state.copyWith(errorMessage: null); } } /// Reloads the full log, rolling the week window forward when the ISO week /// changed since the last snapshot. Future _refresh() async { try { final entries = await ref.read(exerciseRepositoryProvider).getEntries(); state = state.copyWith( entries: entries, weekStart: startOfWeek(DateTime.now()), errorMessage: null, ); } catch (error, stackTrace) { debugPrintStack(stackTrace: stackTrace, label: 'exercise refresh failed'); state = state.copyWith(errorMessage: 'Could not load the exercise log.'); } } } /// Exposes the [ExerciseController] to the widget tree. final exerciseControllerProvider = NotifierProvider( ExerciseController.new, );