import 'dart:typed_data';

import '../domain/domain.dart';

abstract interface class Clock {
  DateTime now();
}

final class SystemClock implements Clock {
  const SystemClock();

  @override
  DateTime now() => DateTime.now().toUtc();
}

abstract interface class IdGenerator {
  String newId();
}

final class StarterContent {
  const StarterContent({
    required this.exercises,
    required this.program,
    required this.workoutTemplate,
  });

  final List<Exercise> exercises;
  final Program program;
  final WorkoutTemplate workoutTemplate;
}

final class TagUsage {
  const TagUsage({required this.tag, required this.count});

  final String tag;
  final int count;
}

enum LocalBackupImportMode { merge, replaceAll }

enum LocalBackupValidationError {
  invalidFile,
  incompatibleFormat,
  newerVersion,
  corrupted,
  activeWorkoutInProgress,
  importFailedNoMutation,
}

final class LocalBackupException implements Exception {
  const LocalBackupException(this.error);

  final LocalBackupValidationError error;

  @override
  String toString() => error.name;
}

final class LocalBackupDocument {
  const LocalBackupDocument({required this.fileName, required this.bytes});

  final String fileName;
  final Uint8List bytes;
}

final class LocalBackupCounts {
  const LocalBackupCounts({
    required this.exercises,
    required this.programs,
    required this.workoutTemplates,
    required this.workoutHistories,
    required this.mediaAssets,
    required this.embeddedMediaFiles,
  });

  final int exercises;
  final int programs;
  final int workoutTemplates;
  final int workoutHistories;
  final int mediaAssets;
  final int embeddedMediaFiles;
}

final class LocalBackupPreview {
  const LocalBackupPreview({
    required this.exportedAt,
    required this.counts,
    required this.hasEmbeddedMedia,
    required this.missingMediaCount,
    required this.hasNameDuplicates,
  });

  final DateTime exportedAt;
  final LocalBackupCounts counts;
  final bool hasEmbeddedMedia;
  final int missingMediaCount;
  final bool hasNameDuplicates;
}

final class LocalBackupImportResult {
  const LocalBackupImportResult({
    required this.insertedCount,
    required this.updatedCount,
    required this.ignoredOlderCount,
    required this.deletedByReplaceCount,
    required this.missingMediaCount,
  });

  final int insertedCount;
  final int updatedCount;
  final int ignoredOlderCount;
  final int deletedByReplaceCount;
  final int missingMediaCount;

  LocalBackupImportResult copyWith({int? missingMediaCount}) {
    return LocalBackupImportResult(
      insertedCount: insertedCount,
      updatedCount: updatedCount,
      ignoredOlderCount: ignoredOlderCount,
      deletedByReplaceCount: deletedByReplaceCount,
      missingMediaCount: missingMediaCount ?? this.missingMediaCount,
    );
  }
}

final class LocalBackupResource {
  const LocalBackupResource({
    required this.id,
    required this.updatedAt,
    required this.payload,
  });

  final String id;
  final DateTime updatedAt;
  final Map<String, Object?> payload;

  LocalBackupResource copyWith({Map<String, Object?>? payload}) {
    return LocalBackupResource(
      id: id,
      updatedAt: updatedAt,
      payload: payload ?? this.payload,
    );
  }
}

final class LocalDataExportSnapshot {
  const LocalDataExportSnapshot({
    required this.exportedAt,
    required this.appSchemaVersion,
    required this.originDeviceId,
    required this.mediaAssets,
    required this.exercises,
    required this.programs,
    required this.workoutTemplates,
    required this.workoutHistories,
    this.mediaFiles = const [],
  });

  final DateTime exportedAt;
  final int appSchemaVersion;
  final String originDeviceId;
  final List<LocalBackupResource> mediaAssets;
  final List<LocalBackupResource> exercises;
  final List<LocalBackupResource> programs;
  final List<LocalBackupResource> workoutTemplates;
  final List<LocalBackupResource> workoutHistories;
  final List<EmbeddedBackupMediaFile> mediaFiles;

  LocalBackupCounts get counts => LocalBackupCounts(
    exercises: exercises.length,
    programs: programs.length,
    workoutTemplates: workoutTemplates.length,
    workoutHistories: workoutHistories.length,
    mediaAssets: mediaAssets.length,
    embeddedMediaFiles: mediaFiles.length,
  );

  LocalDataExportSnapshot copyWith({
    List<LocalBackupResource>? mediaAssets,
    List<EmbeddedBackupMediaFile>? mediaFiles,
  }) {
    return LocalDataExportSnapshot(
      exportedAt: exportedAt,
      appSchemaVersion: appSchemaVersion,
      originDeviceId: originDeviceId,
      mediaAssets: mediaAssets ?? this.mediaAssets,
      exercises: exercises,
      programs: programs,
      workoutTemplates: workoutTemplates,
      workoutHistories: workoutHistories,
      mediaFiles: mediaFiles ?? this.mediaFiles,
    );
  }
}

final class EmbeddedBackupMediaFile {
  const EmbeddedBackupMediaFile({
    required this.mediaAssetId,
    required this.role,
    required this.fileName,
    required this.mimeType,
    required this.sizeBytes,
    required this.sha256,
    required this.base64,
  });

  final String mediaAssetId;
  final String role;
  final String fileName;
  final String? mimeType;
  final int sizeBytes;
  final String sha256;
  final String base64;
}

final class RestoredMediaFile {
  const RestoredMediaFile({
    required this.mediaAssetId,
    required this.localUri,
    required this.sizeBytes,
  });

  final String mediaAssetId;
  final String localUri;
  final int sizeBytes;
}

abstract interface class LocalDataBackupRepository {
  Future<LocalDataExportSnapshot> readExportSnapshot(DateTime exportedAt);
  Future<bool> hasOpenActiveWorkoutSession();
  Future<bool> hasAnyUserData();
  Future<LocalBackupImportResult> applyImportSnapshot({
    required LocalDataExportSnapshot snapshot,
    required LocalBackupImportMode mode,
    required DateTime importedAt,
  });
}

abstract interface class LocalBackupMediaStore {
  Future<List<EmbeddedBackupMediaFile>> readEmbeddableFiles(
    List<LocalBackupResource> mediaAssets,
  );
  Future<Map<String, RestoredMediaFile>> restoreEmbeddedFiles(
    List<EmbeddedBackupMediaFile> files,
  );
}

enum ProgressionPeriod { fourWeeks, threeMonths, all }

enum ProgressionMeasure { manualScore, stopwatchScore, reps, time }

enum ProgressionRangeLabelKind { sinceDate, sinceFirstSession, none }

enum ProgressionSeriesState {
  ready,
  singlePoint,
  emptyForPeriod,
  emptyButHasAllTimeData,
  noGraphableMeasure,
  textScoreOnly,
}

enum ProgressionSeriesSummaryKind { bestLast, totals, none }

final class ProgressionDateRange {
  const ProgressionDateRange({required this.startedAt, required this.endedAt});

  final DateTime? startedAt;
  final DateTime endedAt;
}

final class ProgressionGlobalStatsData {
  const ProgressionGlobalStatsData({
    required this.completedSessionCount,
    required this.totalActiveMs,
    required this.activeWeekStarts,
    required this.hasAnyCompletedHistory,
    this.firstCompletedSessionAt,
  });

  final int completedSessionCount;
  final int totalActiveMs;
  final List<DateTime> activeWeekStarts;
  final bool hasAnyCompletedHistory;
  final DateTime? firstCompletedSessionAt;
}

final class ProgressionExerciseOptionData {
  const ProgressionExerciseOptionData({
    required this.exerciseKey,
    required this.nameSnapshot,
    required this.isArchived,
    required this.lastPerformedAt,
  });

  final String exerciseKey;
  final String nameSnapshot;
  final bool isArchived;
  final DateTime lastPerformedAt;
}

final class ProgressionMeasureOptionData {
  const ProgressionMeasureOptionData({
    required this.measure,
    required this.label,
    this.scoreLabel,
    this.scoreUnit,
    required this.lowerIsBetter,
  });

  final ProgressionMeasure measure;
  final String label;
  final String? scoreLabel;
  final String? scoreUnit;
  final bool lowerIsBetter;
}

final class ProgressionExerciseSeriesData {
  const ProgressionExerciseSeriesData({
    required this.exerciseKey,
    required this.exerciseNameSnapshot,
    required this.measure,
    required this.points,
    required this.hasAnyAllTimeData,
    required this.hasAnyCompletedExerciseResult,
  });

  final String exerciseKey;
  final String exerciseNameSnapshot;
  final ProgressionMeasureOptionData measure;
  final List<ProgressionPointData> points;
  final bool hasAnyAllTimeData;
  final bool hasAnyCompletedExerciseResult;
}

final class ProgressionPointData {
  const ProgressionPointData({
    required this.workoutHistoryId,
    required this.workoutNameSnapshot,
    required this.startedAt,
    required this.value,
    required this.rawValue,
  });

  final String workoutHistoryId;
  final String workoutNameSnapshot;
  final DateTime startedAt;
  final double value;
  final Object rawValue;
}

abstract interface class ProgressionStatsRepository {
  Future<ProgressionGlobalStatsData> readGlobalStats(
    ProgressionDateRange range,
  );
  Future<List<ProgressionExerciseOptionData>> listExerciseOptions(
    ProgressionDateRange range,
  );
  Future<List<ProgressionMeasureOptionData>> listMeasureOptions({
    required ProgressionDateRange range,
    required String exerciseKey,
  });
  Future<ProgressionExerciseSeriesData> readExerciseSeries({
    required ProgressionDateRange range,
    required String exerciseKey,
    required ProgressionMeasure measure,
  });
}

final class ProgressionOverview {
  const ProgressionOverview({
    required this.period,
    required this.rangeLabelKind,
    required this.completedSessionCount,
    required this.totalActiveMs,
    required this.activeWeekCount,
    required this.totalWeekCount,
    required this.hasAnyCompletedHistory,
    required this.exerciseOptions,
  });

  final ProgressionPeriod period;
  final ProgressionRangeLabelKind rangeLabelKind;
  final int completedSessionCount;
  final int totalActiveMs;
  final int activeWeekCount;
  final int? totalWeekCount;
  final bool hasAnyCompletedHistory;
  final List<ProgressionExerciseOption> exerciseOptions;
}

final class ProgressionExerciseOption {
  const ProgressionExerciseOption({
    required this.exerciseKey,
    required this.nameSnapshot,
    required this.isArchived,
    required this.lastPerformedAt,
    required this.measures,
  });

  final String exerciseKey;
  final String nameSnapshot;
  final bool isArchived;
  final DateTime lastPerformedAt;
  final List<ProgressionMeasureOption> measures;
}

final class ProgressionMeasureOption {
  const ProgressionMeasureOption({
    required this.measure,
    required this.label,
    this.scoreLabel,
    this.scoreUnit,
    required this.lowerIsBetter,
  });

  final ProgressionMeasure measure;
  final String label;
  final String? scoreLabel;
  final String? scoreUnit;
  final bool lowerIsBetter;
}

final class ProgressionExerciseSeries {
  const ProgressionExerciseSeries({
    required this.exerciseKey,
    required this.exerciseNameSnapshot,
    required this.measure,
    required this.points,
    required this.summary,
    required this.state,
  });

  final String exerciseKey;
  final String exerciseNameSnapshot;
  final ProgressionMeasureOption measure;
  final List<ProgressionPoint> points;
  final ProgressionSeriesSummary summary;
  final ProgressionSeriesState state;
}

final class ProgressionPoint {
  const ProgressionPoint({
    required this.workoutHistoryId,
    required this.workoutNameSnapshot,
    required this.startedAt,
    required this.value,
    required this.rawValue,
  });

  final String workoutHistoryId;
  final String workoutNameSnapshot;
  final DateTime startedAt;
  final double value;
  final Object rawValue;
}

final class ProgressionSeriesSummary {
  const ProgressionSeriesSummary({
    required this.kind,
    this.best,
    this.last,
    this.recentTotal,
    this.lastSessionTotal,
  });

  final ProgressionSeriesSummaryKind kind;
  final Object? best;
  final Object? last;
  final Object? recentTotal;
  final Object? lastSessionTotal;
}

abstract interface class StarterSeedStateRepository {
  Future<int> readAppliedStarterSeedVersion();
  Future<void> writeAppliedStarterSeedVersion(int version, DateTime appliedAt);
}

abstract interface class StarterContentRepository {
  Future<bool> isLocalContentEmpty();
  Future<void> insertStarterContent(StarterContent content);
}

enum RemoteAuthFailure {
  invalidCredentials,
  emailAlreadyUsed,
  network,
  unknown,
}

final class RemoteAuthException implements Exception {
  const RemoteAuthException(this.failure, [this.message]);

  final RemoteAuthFailure failure;
  final String? message;

  @override
  String toString() => message ?? failure.name;
}

final class RemoteAuthResult {
  const RemoteAuthResult({
    required this.userId,
    required this.email,
    required this.token,
    required this.expiresAt,
  });

  final String userId;
  final String email;
  final String token;
  final DateTime expiresAt;
}

abstract interface class AuthTokenStore {
  Future<void> saveToken(String token, DateTime expiresAt);
  Future<String?> readToken();
  Future<void> clearToken();
}

abstract interface class OnlineAccountRepository {
  Future<UserAccountSession?> currentSession();
  Future<void> saveSession(UserAccountSession session);
  Future<void> clearSession();
}

abstract interface class RemoteAuthApi {
  Future<RemoteAuthResult> register({
    required String email,
    required String password,
    String? displayName,
  });

  Future<RemoteAuthResult> login({
    required String email,
    required String password,
  });

  Future<void> logout(String token);
}

enum SyncResourceType {
  exercise,
  program,
  workoutTemplate,
  workoutHistory,
  mediaAsset,
}

enum RemoteSyncPushStatus { accepted, ignoredOlder, error }

enum OnlineSyncStatus { idle, syncing, success, failure }

final class RemoteSyncPushItem {
  const RemoteSyncPushItem({
    required this.resourceType,
    required this.clientId,
    required this.schemaVersion,
    required this.clientUpdatedAt,
    required this.deletedAt,
    required this.payload,
  });

  final SyncResourceType resourceType;
  final String clientId;
  final int schemaVersion;
  final DateTime clientUpdatedAt;
  final DateTime? deletedAt;
  final Map<String, Object?> payload;
}

final class RemoteSyncPushItemResult {
  const RemoteSyncPushItemResult({
    required this.resourceType,
    required this.clientId,
    this.serverId,
    required this.status,
    this.serverUpdatedAt,
    this.errorMessage,
  });

  final SyncResourceType resourceType;
  final String clientId;
  final String? serverId;
  final RemoteSyncPushStatus status;
  final DateTime? serverUpdatedAt;
  final String? errorMessage;
}

final class RemoteSyncPushResult {
  const RemoteSyncPushResult({
    required this.serverCursor,
    required this.results,
  });

  final String? serverCursor;
  final List<RemoteSyncPushItemResult> results;
}

final class RemoteSyncedItem {
  const RemoteSyncedItem({
    required this.resourceType,
    required this.clientId,
    required this.serverId,
    required this.schemaVersion,
    required this.clientUpdatedAt,
    required this.serverUpdatedAt,
    required this.deletedAt,
    required this.payload,
  });

  final SyncResourceType resourceType;
  final String clientId;
  final String serverId;
  final int schemaVersion;
  final DateTime clientUpdatedAt;
  final DateTime serverUpdatedAt;
  final DateTime? deletedAt;
  final Map<String, Object?> payload;
}

final class RemoteSyncPullResult {
  const RemoteSyncPullResult({required this.serverCursor, required this.items});

  final String? serverCursor;
  final List<RemoteSyncedItem> items;
}

abstract interface class RemoteSyncApi {
  Future<RemoteSyncPushResult> push({
    required String deviceId,
    required List<RemoteSyncPushItem> items,
    required String token,
  });

  Future<RemoteSyncPullResult> pull({
    required String? since,
    required String token,
  });
}

final class SyncMetadataSnapshot {
  const SyncMetadataSnapshot({
    this.serverCursor,
    this.lastSuccessfulSyncAt,
    this.lastAttemptAt,
    this.lastFailureAt,
    this.status = OnlineSyncStatus.idle,
    this.pendingPushCount,
  });

  final String? serverCursor;
  final DateTime? lastSuccessfulSyncAt;
  final DateTime? lastAttemptAt;
  final DateTime? lastFailureAt;
  final OnlineSyncStatus status;
  final int? pendingPushCount;

  SyncMetadataSnapshot copyWith({
    Object? serverCursor = _portsUnchanged,
    Object? lastSuccessfulSyncAt = _portsUnchanged,
    Object? lastAttemptAt = _portsUnchanged,
    Object? lastFailureAt = _portsUnchanged,
    OnlineSyncStatus? status,
    Object? pendingPushCount = _portsUnchanged,
  }) {
    return SyncMetadataSnapshot(
      serverCursor: serverCursor == _portsUnchanged
          ? this.serverCursor
          : serverCursor as String?,
      lastSuccessfulSyncAt: lastSuccessfulSyncAt == _portsUnchanged
          ? this.lastSuccessfulSyncAt
          : lastSuccessfulSyncAt as DateTime?,
      lastAttemptAt: lastAttemptAt == _portsUnchanged
          ? this.lastAttemptAt
          : lastAttemptAt as DateTime?,
      lastFailureAt: lastFailureAt == _portsUnchanged
          ? this.lastFailureAt
          : lastFailureAt as DateTime?,
      status: status ?? this.status,
      pendingPushCount: pendingPushCount == _portsUnchanged
          ? this.pendingPushCount
          : pendingPushCount as int?,
    );
  }
}

final class RemoteResourceMapping {
  const RemoteResourceMapping({
    required this.resourceType,
    required this.clientId,
    required this.serverId,
    required this.serverUpdatedAt,
  });

  final SyncResourceType resourceType;
  final String clientId;
  final String serverId;
  final DateTime serverUpdatedAt;
}

final class PendingSyncChange {
  const PendingSyncChange({required this.changeLogIds, required this.item});

  final List<String> changeLogIds;
  final RemoteSyncPushItem item;
}

abstract interface class SyncMetadataRepository {
  Future<SyncMetadataSnapshot> read();
  Future<void> save(SyncMetadataSnapshot metadata);
}

abstract interface class RemoteResourceMappingRepository {
  Future<RemoteResourceMapping?> find({
    required SyncResourceType resourceType,
    required String clientId,
  });
  Future<void> save(RemoteResourceMapping mapping);
}

abstract interface class LocalSyncChangeRepository {
  Future<List<PendingSyncChange>> listPendingChanges();
  Future<void> markChangesSynced(List<String> changeLogIds, DateTime syncedAt);
  Future<bool> applyRemoteItem(RemoteSyncedItem item);
}

final class RemoteShareSendResult {
  const RemoteShareSendResult({
    required this.shareId,
    required this.recipientUserIds,
    required this.unresolvedEmails,
  });

  final String shareId;
  final List<String> recipientUserIds;
  final List<String> unresolvedEmails;
}

enum ShareSendStatus { sent, notConnected, queued }

final class ShareSendResult {
  const ShareSendResult({
    required this.status,
    this.shareId,
    this.recipientUserIds = const [],
    this.unresolvedEmails = const [],
    this.pendingActionId,
  });

  final ShareSendStatus status;
  final String? shareId;
  final List<String> recipientUserIds;
  final List<String> unresolvedEmails;
  final String? pendingActionId;
}

abstract interface class RemoteShareApi {
  Future<RemoteShareSendResult> sendShare({
    required ShareResourceType resourceType,
    required Map<String, Object?> payload,
    required List<String> recipientEmails,
    required String token,
  });

  Future<List<ShareInboxItem>> fetchInbox(String token);
  Future<RemoteSyncedItem> acceptShare(String shareId, String token);
  Future<void> declineShare(String shareId, String token);
  Future<void> revokeShare(String shareId, String token);
}

abstract interface class ShareInboxRepository {
  Future<List<ShareInboxItem>> listAll();
  Future<ShareInboxItem?> findByShareId(String shareId);
  Future<void> upsert(ShareInboxItem item);
  Future<void> upsertAll(List<ShareInboxItem> items);
  Future<void> markStatus(
    String shareId,
    ShareInboxStatus status,
    DateTime respondedAt,
  );
}

abstract interface class PendingShareActionRepository {
  Future<List<PendingShareAction>> listPending();
  Future<void> add(PendingShareAction action);
  Future<void> markSucceeded(String id, DateTime attemptedAt);
  Future<void> markFailed(String id, DateTime attemptedAt);
}

const Object _portsUnchanged = Object();

abstract interface class ExerciseRepository {
  Future<Exercise?> findById(String id);
  Future<List<Exercise>> listActive();
  Future<bool> isReferencedByProgram(String id);
  Future<void> save(Exercise exercise);
}

final class StoredMediaFile {
  const StoredMediaFile({
    required this.localUri,
    required this.mimeType,
    required this.sizeBytes,
    this.width,
    this.height,
    this.durationMs,
  });

  final String localUri;
  final String? mimeType;
  final int sizeBytes;
  final int? width;
  final int? height;
  final int? durationMs;
}

abstract interface class LocalMediaStorage {
  Future<StoredMediaFile> importFile({
    required String sourcePath,
    required MediaKind kind,
    required String stableFileName,
  });

  Future<Set<String>> listManagedLocalUris();
  Future<void> deleteByLocalUri(String localUri);
}

abstract interface class MediaAssetRepository {
  Future<MediaAsset?> findById(String id);
  Future<List<MediaAsset>> listActive();
  Future<void> save(MediaAsset mediaAsset);
}

abstract interface class ProgramRepository {
  Future<Program?> findById(String id);
  Future<List<Program>> listActive();
  Future<void> save(Program program);
  Future<void> saveExercise(ProgramExercise exercise);
  Future<void> replaceExercises(Program program, DateTime deletedAt);
}

abstract interface class WorkoutTemplateRepository {
  Future<WorkoutTemplate?> findById(String id);
  Future<List<WorkoutTemplate>> listActive();
  Future<void> save(WorkoutTemplate template);
  Future<void> saveProgram(WorkoutTemplateProgram program);
  Future<void> saveOverride(WorkoutTemplateExerciseOverride override);
  Future<void> replaceComposition(WorkoutTemplate template, DateTime deletedAt);
}

abstract interface class ActiveSessionRepository {
  Future<ActiveWorkoutSession?> findById(String id);
  Future<ActiveWorkoutSession?> findOpen();
  Future<void> save(ActiveWorkoutSession session);
  Future<void> saveSetResult(ActiveSetResult result);
  Future<void> saveSetTimerState(ActiveSetTimerState state);
  Future<void> saveRestState(ActiveRestState restState);
  Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state);
  Future<void> saveExerciseStepProgressState(
    ActiveExerciseStepProgressState state,
  );
  Future<void> saveExerciseStepResult(ActiveExerciseStepResult result);
  Future<void> deleteScoreStopwatchState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
    required DateTime deletedAt,
  });
  Future<ActiveRestState?> findRestStateById(String id);
  Future<ActiveScoreStopwatchState?> findScoreStopwatchState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
  });
  Future<ActiveSetTimerState?> findSetTimerState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
  });
  Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
  });
  Future<List<ActiveSetResult>> listSetResults(String sessionId);
  Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId);
  Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
    String sessionId,
  );
  Future<List<ActiveExerciseStepResult>> listExerciseStepResults(
    String sessionId,
  );
  Future<List<ActiveRestState>> listRestStates(String sessionId);
  Future<List<ActiveScoreStopwatchState>> listScoreStopwatchStates(
    String sessionId,
  );
}

abstract interface class WorkoutHistoryRepository {
  Future<WorkoutHistory?> findById(String id);
  Future<List<WorkoutHistory>> listActive();
  Future<void> save(WorkoutHistory history);
  Future<void> saveSetResult(WorkoutHistorySetResult result);
  Future<void> saveStepResult(WorkoutHistoryStepResult result);
  Future<void> delete(String id, DateTime deletedAt);
}

final class ActivePerformanceMeasures {
  const ActivePerformanceMeasures({
    required this.timeEnabled,
    required this.repsEnabled,
    required this.scoreEnabled,
    this.scoreInputMode = ScoreInputMode.manual,
  });

  final bool timeEnabled;
  final bool repsEnabled;
  final bool scoreEnabled;
  final ScoreInputMode scoreInputMode;

  bool get hasAny => timeEnabled || repsEnabled || scoreEnabled;
}

enum PerformanceMetric { score, reps, time }

final class WorkoutHistorySetPerformance {
  const WorkoutHistorySetPerformance({
    required this.workoutHistoryId,
    required this.startedAt,
    required this.setIndex,
    required this.exerciseNameSnapshot,
    required this.scoreInputModeSnapshot,
    this.actualTimeMs,
    this.actualReps,
    this.actualScore,
    this.actualScoreTimeMs,
    this.completedAt,
  });

  final String workoutHistoryId;
  final DateTime startedAt;
  final int setIndex;
  final String exerciseNameSnapshot;
  final ScoreInputMode scoreInputModeSnapshot;
  final int? actualTimeMs;
  final int? actualReps;
  final double? actualScore;
  final int? actualScoreTimeMs;
  final DateTime? completedAt;
}

final class WorkoutHistoryMetricPerformance {
  const WorkoutHistoryMetricPerformance({
    required this.workoutHistoryId,
    required this.startedAt,
    required this.setIndex,
    required this.exerciseNameSnapshot,
    required this.metric,
    required this.scoreInputModeSnapshot,
    this.actualTimeMs,
    this.actualReps,
    this.actualScore,
    this.actualScoreTimeMs,
    this.completedAt,
  });

  final String workoutHistoryId;
  final DateTime startedAt;
  final int setIndex;
  final String exerciseNameSnapshot;
  final PerformanceMetric metric;
  final ScoreInputMode scoreInputModeSnapshot;
  final int? actualTimeMs;
  final int? actualReps;
  final double? actualScore;
  final int? actualScoreTimeMs;
  final DateTime? completedAt;
}

final class ExercisePerformanceReference {
  const ExercisePerformanceReference({
    required this.hasAnyHistoryForExercise,
    this.last,
    this.record,
  });

  final bool hasAnyHistoryForExercise;
  final WorkoutHistorySetPerformance? last;
  final WorkoutHistoryMetricPerformance? record;
}

abstract interface class ExercisePerformanceReferenceRepository {
  Future<bool> hasAnyCompletedHistoryForExercise(String exerciseId);

  Future<WorkoutHistorySetPerformance?> findLatestSetPerformance({
    required String exerciseId,
    required ActivePerformanceMeasures activeMeasures,
    required int currentSetIndex,
  });

  Future<WorkoutHistoryMetricPerformance?> findBestMetricPerformance({
    required String exerciseId,
    required PerformanceMetric metric,
    required ScoreInputMode scoreInputMode,
  });
}

final class SyncRunSummary {
  const SyncRunSummary({
    required this.pushedChanges,
    required this.pulledChanges,
    this.skipped = false,
    this.failed = false,
    this.message,
  });

  final int pushedChanges;
  final int pulledChanges;
  final bool skipped;
  final bool failed;
  final String? message;
}

abstract interface class SyncGateway {
  Future<SyncRunSummary> synchronize();
}

final class NoOpSyncGateway implements SyncGateway {
  const NoOpSyncGateway();

  @override
  Future<SyncRunSummary> synchronize() async {
    return const SyncRunSummary(pushedChanges: 0, pulledChanges: 0);
  }
}
