import 'package:drift/drift.dart';

abstract class SyncableTable extends Table {
  TextColumn get id => text()();
  DateTimeColumn get createdAt => dateTime()();
  DateTimeColumn get updatedAt => dateTime()();
  DateTimeColumn get deletedAt => dateTime().nullable()();
  IntColumn get schemaVersion => integer().withDefault(const Constant(1))();
  TextColumn get syncState => text().customConstraint(
    "NOT NULL CHECK (sync_state IN ('localOnly', 'dirty', 'synced', "
    "'deleted'))",
  )();
  IntColumn get localRevision =>
      integer().customConstraint('NOT NULL CHECK (local_revision >= 0)')();
  TextColumn get originDeviceId => text().withLength(min: 1)();
  TextColumn get futureOwnerProfileId => text().nullable()();
  DateTimeColumn get lastSyncedAt => dateTime().nullable()();
  TextColumn get remoteRevision => text().nullable()();

  @override
  Set<Column> get primaryKey => {id};

  @override
  List<String> get customConstraints => const [];
}

class OnlineAccountSessions extends Table {
  @override
  String get tableName => 'online_account_sessions';

  TextColumn get id => text()();
  TextColumn get serverUserId => text().withLength(min: 1)();
  TextColumn get email => text().withLength(min: 1)();
  TextColumn get displayName => text().nullable()();
  BoolColumn get isLoggedIn => boolean()();
  DateTimeColumn get createdAt => dateTime()();
  DateTimeColumn get updatedAt => dateTime()();
  DateTimeColumn get lastAuthenticatedAt => dateTime().nullable()();

  @override
  Set<Column> get primaryKey => {id};

  @override
  List<String> get customConstraints => [
    'UNIQUE (server_user_id)',
    'CHECK (length(trim(email)) > 0)',
    'CHECK (display_name IS NULL OR length(trim(display_name)) > 0)',
  ];
}

class SyncMetadataEntries extends Table {
  @override
  String get tableName => 'sync_metadata';

  TextColumn get id => text().withDefault(const Constant('singleton'))();
  TextColumn get serverCursor => text().nullable()();
  DateTimeColumn get lastSuccessfulSyncAt => dateTime().nullable()();
  DateTimeColumn get lastAttemptAt => dateTime().nullable()();
  DateTimeColumn get lastFailureAt => dateTime().nullable()();
  TextColumn get status => text().withDefault(const Constant('idle'))();
  IntColumn get pendingPushCount => integer().nullable()();

  @override
  Set<Column> get primaryKey => {id};

  @override
  List<String> get customConstraints => [
    "CHECK (id = 'singleton')",
    "CHECK (status IN ('idle', 'syncing', 'success', 'failure'))",
    'CHECK (pending_push_count IS NULL OR pending_push_count >= 0)',
  ];
}

class RemoteResourceMappings extends Table {
  @override
  String get tableName => 'remote_resource_mappings';

  TextColumn get id => text()();
  TextColumn get resourceType => text()();
  TextColumn get clientId => text().withLength(min: 1)();
  TextColumn get serverId => text().withLength(min: 1)();
  DateTimeColumn get serverUpdatedAt => dateTime()();

  @override
  Set<Column> get primaryKey => {id};

  @override
  List<String> get customConstraints => [
    "CHECK (resource_type IN ('exercise', 'program', 'workoutTemplate', "
        "'workoutHistory', 'mediaAsset'))",
    'UNIQUE (resource_type, client_id)',
  ];
}

class ShareInboxItems extends Table {
  @override
  String get tableName => 'share_inbox_items';

  TextColumn get shareId => text()();
  TextColumn get senderUserId => text().withLength(min: 1)();
  TextColumn get resourceType => text()();
  TextColumn get payloadJson => text().withLength(min: 1)();
  TextColumn get status => text()();
  DateTimeColumn get createdAt => dateTime()();
  DateTimeColumn get updatedAt => dateTime()();
  DateTimeColumn get respondedAt => dateTime().nullable()();

  @override
  Set<Column> get primaryKey => {shareId};

  @override
  List<String> get customConstraints => [
    "CHECK (resource_type IN ('program', 'workoutTemplate'))",
    "CHECK (status IN ('pending', 'accepted', 'declined', 'revoked'))",
  ];
}

class PendingShareActions extends Table {
  @override
  String get tableName => 'pending_share_actions';

  TextColumn get id => text()();
  TextColumn get actionType => text()();
  TextColumn get shareId => text().nullable()();
  TextColumn get resourceType => text().nullable()();
  TextColumn get payloadJson => text().nullable()();
  TextColumn get recipientEmailsJson => text().nullable()();
  DateTimeColumn get createdAt => dateTime()();
  DateTimeColumn get lastAttemptAt => dateTime().nullable()();
  IntColumn get attemptCount =>
      integer().customConstraint('NOT NULL CHECK (attempt_count >= 0)')();
  TextColumn get status => text()();

  @override
  Set<Column> get primaryKey => {id};

  @override
  List<String> get customConstraints => [
    "CHECK (action_type IN ('send', 'accept', 'decline', 'revoke'))",
    "CHECK (resource_type IS NULL OR resource_type IN "
        "('program', 'workoutTemplate'))",
    "CHECK (status IN ('pending', 'succeeded', 'failed'))",
  ];
}

class MediaAssets extends SyncableTable {
  @override
  String get tableName => 'media_assets';

  TextColumn get kind => text()();
  TextColumn get localUri => text().withLength(min: 1)();
  TextColumn get mimeType => text().nullable()();
  IntColumn get sizeBytes => integer().nullable()();
  IntColumn get width => integer().nullable()();
  IntColumn get height => integer().nullable()();
  IntColumn get durationMs => integer().nullable()();
  TextColumn get checksum => text().nullable()();
  TextColumn get remoteUri => text().nullable()();
  TextColumn get thumbnailLocalUri => text().nullable()();

  @override
  List<String> get customConstraints => [
    "CHECK (kind IN ('image', 'video'))",
    'CHECK (size_bytes IS NULL OR size_bytes >= 0)',
    'CHECK (width IS NULL OR width > 0)',
    'CHECK (height IS NULL OR height > 0)',
    'CHECK (duration_ms IS NULL OR duration_ms >= 0)',
  ];
}

class Exercises extends SyncableTable {
  @override
  String get tableName => 'exercises';

  TextColumn get name => text().withLength(min: 1)();
  TextColumn get description => text().nullable()();
  @ReferenceName('exerciseIconReferences')
  TextColumn get iconMediaId =>
      text().nullable().references(MediaAssets, #id)();
  @ReferenceName('exerciseVideoReferences')
  TextColumn get videoMediaId =>
      text().nullable().references(MediaAssets, #id)();
  BoolColumn get hasTimeMeasure => boolean()();
  BoolColumn get hasRepsMeasure => boolean()();
  BoolColumn get hasScoreMeasure => boolean()();
  TextColumn get scoreInputMode =>
      text().withDefault(const Constant('manual'))();
  TextColumn get scoreLabel => text().nullable()();
  TextColumn get scoreUnit => text().nullable()();
  IntColumn get defaultTargetTimeSeconds => integer().nullable()();
  IntColumn get defaultTargetReps => integer().nullable()();
  RealColumn get defaultTargetScore => real().nullable()();
  IntColumn get defaultTargetScoreTimeMs => integer().nullable()();
  BoolColumn get autoStartNextTimedStep =>
      boolean().withDefault(const Constant(true))();
  TextColumn get category =>
      text().withDefault(const Constant('uncategorized'))();
  BoolColumn get isExample => boolean().withDefault(const Constant(false))();
  TextColumn get tagsJson => text().withDefault(const Constant('[]'))();
  DateTimeColumn get archivedAt => dateTime().nullable()();

  @override
  List<String> get customConstraints => [
    'CHECK (has_time_measure OR has_reps_measure OR has_score_measure)',
    "CHECK (score_input_mode IN ('manual', 'stopwatch'))",
    "CHECK (has_score_measure OR score_input_mode = 'manual')",
    "CHECK (score_input_mode != 'manual' OR NOT has_score_measure OR "
        '(score_label IS NOT NULL AND length(trim(score_label)) > 0 '
        'AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0))',
    'CHECK (default_target_time_seconds IS NULL OR '
        'default_target_time_seconds > 0)',
    'CHECK (default_target_reps IS NULL OR default_target_reps > 0)',
    'CHECK (default_target_score IS NULL OR default_target_score >= 0)',
    'CHECK (default_target_score_time_ms IS NULL OR '
        'default_target_score_time_ms > 0)',
    "CHECK (category IN ('shoot', 'freeThrows', 'dribble', 'finishing', "
        "'conditioning', 'defense', 'mobility', 'uncategorized'))",
    'CHECK (json_valid(tags_json))',
  ];
}

class ExerciseImages extends SyncableTable {
  @override
  String get tableName => 'exercise_images';

  TextColumn get exerciseId => text().references(Exercises, #id)();
  TextColumn get mediaAssetId => text().references(MediaAssets, #id)();
  IntColumn get position => integer()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (exercise_id, position)',
    'UNIQUE (exercise_id, media_asset_id)',
    'CHECK (position >= 0 AND position < 5)',
  ];
}

class ExerciseSteps extends SyncableTable {
  @override
  String get tableName => 'exercise_steps';

  TextColumn get exerciseId => text().references(Exercises, #id)();
  IntColumn get position => integer()();
  TextColumn get name => text().withLength(min: 1)();
  TextColumn get type => text()();
  IntColumn get defaultTargetValue => integer()();
  BoolColumn get hasScore => boolean()();
  TextColumn get scoreInputMode => text().nullable()();
  TextColumn get scoreLabel => text().nullable()();
  TextColumn get scoreUnit => text().nullable()();
  RealColumn get defaultTargetScore => real().nullable()();
  IntColumn get defaultTargetScoreTimeMs => integer().nullable()();

  @override
  List<String> get customConstraints => [
    "CHECK (type IN ('time', 'reps'))",
    'CHECK (position >= 0 AND position < 8)',
    'CHECK (default_target_value > 0)',
    "CHECK (score_input_mode IS NULL OR score_input_mode IN ('manual', "
        "'stopwatch'))",
    'CHECK (has_score OR (score_input_mode IS NULL '
        'AND score_label IS NULL AND score_unit IS NULL '
        'AND default_target_score IS NULL '
        'AND default_target_score_time_ms IS NULL))',
    'CHECK (NOT has_score OR score_input_mode IS NOT NULL)',
    "CHECK (NOT has_score OR score_input_mode != 'manual' OR "
        '(score_label IS NOT NULL AND length(trim(score_label)) > 0 '
        'AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0))',
    'CHECK (default_target_score IS NULL OR default_target_score >= 0)',
    'CHECK (default_target_score_time_ms IS NULL OR '
        'default_target_score_time_ms > 0)',
    "CHECK (score_input_mode != 'manual' OR "
        'default_target_score_time_ms IS NULL)',
    "CHECK (score_input_mode != 'stopwatch' OR "
        '(score_label IS NULL AND score_unit IS NULL '
        'AND default_target_score IS NULL))',
    'CHECK (default_target_score IS NULL OR '
        'default_target_score_time_ms IS NULL)',
  ];
}

class Programs extends SyncableTable {
  @override
  String get tableName => 'programs';

  TextColumn get name => text().withLength(min: 1)();
  IntColumn get defaultRestSeconds => integer()();
  BoolColumn get isExample => boolean().withDefault(const Constant(false))();
  TextColumn get tagsJson => text().withDefault(const Constant('[]'))();

  @override
  List<String> get customConstraints => [
    'CHECK (default_rest_seconds >= 0)',
    'CHECK (json_valid(tags_json))',
  ];
}

class ProgramExercises extends SyncableTable {
  @override
  String get tableName => 'program_exercises';

  TextColumn get programId => text().references(Programs, #id)();
  TextColumn get sourceExerciseId =>
      text().nullable().references(Exercises, #id)();
  IntColumn get position => integer()();
  TextColumn get exerciseNameSnapshot => text().withLength(min: 1)();
  TextColumn get exerciseDescriptionSnapshot => text().nullable()();
  @ReferenceName('programExerciseImageSnapshotReferences')
  TextColumn get exerciseImageMediaIdSnapshot =>
      text().nullable().references(MediaAssets, #id)();
  TextColumn get exerciseImageMediaIdsSnapshotJson => text().nullable()();
  TextColumn get exerciseStepsSnapshotJson => text().nullable()();
  BoolColumn get autoStartNextTimedStepSnapshot =>
      boolean().withDefault(const Constant(true))();
  BoolColumn get autoStartNextTimedStepOverride => boolean().nullable()();

  @ReferenceName('programExerciseVideoSnapshotReferences')
  TextColumn get exerciseVideoMediaIdSnapshot =>
      text().nullable().references(MediaAssets, #id)();
  BoolColumn get exerciseArchivedSnapshot =>
      boolean().withDefault(const Constant(false))();
  BoolColumn get availableTimeSnapshot => boolean()();
  BoolColumn get availableRepsSnapshot => boolean()();
  BoolColumn get availableScoreSnapshot => boolean()();
  TextColumn get scoreInputModeSnapshot =>
      text().withDefault(const Constant('manual'))();
  TextColumn get scoreLabelSnapshot => text().nullable()();
  TextColumn get scoreUnitSnapshot => text().nullable()();
  IntColumn get setsCount => integer()();
  BoolColumn get timeEnabled => boolean()();
  BoolColumn get repsEnabled => boolean()();
  BoolColumn get scoreEnabled => boolean()();
  IntColumn get targetTimeSeconds => integer().nullable()();
  IntColumn get targetReps => integer().nullable()();
  RealColumn get targetScore => real().nullable()();
  IntColumn get targetScoreTimeMs => integer().nullable()();
  IntColumn get restSecondsOverride => integer().nullable()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (program_id, position)',
    'CHECK (position >= 0)',
    'CHECK (sets_count > 0)',
    'CHECK (time_enabled OR reps_enabled OR score_enabled)',
    'CHECK (NOT time_enabled OR available_time_snapshot)',
    'CHECK (NOT reps_enabled OR available_reps_snapshot)',
    'CHECK (NOT score_enabled OR available_score_snapshot)',
    'CHECK (target_time_seconds IS NULL OR target_time_seconds > 0)',
    'CHECK (target_reps IS NULL OR target_reps > 0)',
    'CHECK (target_score IS NULL OR target_score >= 0)',
    'CHECK (target_score_time_ms IS NULL OR target_score_time_ms > 0)',
    'CHECK (rest_seconds_override IS NULL OR rest_seconds_override >= 0)',
    'CHECK (target_time_seconds IS NULL OR time_enabled)',
    'CHECK (target_reps IS NULL OR reps_enabled)',
    "CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
    'CHECK (target_score IS NULL OR '
        "(score_enabled AND score_input_mode_snapshot = 'manual'))",
    'CHECK (target_score_time_ms IS NULL OR '
        "(score_enabled AND score_input_mode_snapshot = 'stopwatch'))",
    'CHECK (target_score IS NULL OR target_score_time_ms IS NULL)',
    "CHECK (score_input_mode_snapshot != 'manual' OR "
        'NOT available_score_snapshot OR (score_label_snapshot '
        'IS NOT NULL AND length(trim(score_label_snapshot)) > 0 '
        'AND score_unit_snapshot IS NOT NULL '
        'AND length(trim(score_unit_snapshot)) > 0))',
  ];
}

class WorkoutTemplates extends SyncableTable {
  @override
  String get tableName => 'workout_templates';

  TextColumn get name => text().withLength(min: 1)();
  DateTimeColumn get lastStartedAt => dateTime().nullable()();
  BoolColumn get isExample => boolean().withDefault(const Constant(false))();
  TextColumn get tagsJson => text().withDefault(const Constant('[]'))();

  @override
  List<String> get customConstraints => ['CHECK (json_valid(tags_json))'];
}

class LocalSeedMetadata extends Table {
  @override
  String get tableName => 'local_seed_metadata';

  TextColumn get key => text()();
  IntColumn get version =>
      integer().customConstraint('NOT NULL CHECK (version >= 0)')();
  DateTimeColumn get appliedAt => dateTime()();

  @override
  Set<Column> get primaryKey => {key};
}

class WorkoutTemplatePrograms extends SyncableTable {
  @override
  String get tableName => 'workout_template_programs';

  TextColumn get workoutTemplateId =>
      text().references(WorkoutTemplates, #id)();
  TextColumn get sourceProgramId =>
      text().nullable().references(Programs, #id)();
  IntColumn get position => integer()();
  TextColumn get programNameSnapshot => text().withLength(min: 1)();
  IntColumn get defaultRestSecondsSnapshot => integer()();
  TextColumn get programSnapshotJson => text().withLength(min: 1)();

  @override
  List<String> get customConstraints => [
    'UNIQUE (workout_template_id, position)',
    'CHECK (position >= 0)',
    'CHECK (default_rest_seconds_snapshot >= 0)',
  ];
}

class WorkoutTemplateExerciseOverrides extends SyncableTable {
  @override
  String get tableName => 'workout_template_exercise_overrides';

  TextColumn get workoutTemplateProgramId =>
      text().references(WorkoutTemplatePrograms, #id)();
  TextColumn get snapshotProgramExerciseId => text().withLength(min: 1)();
  IntColumn get setsCountOverride => integer().nullable()();
  IntColumn get targetTimeSecondsOverride => integer().nullable()();
  IntColumn get targetRepsOverride => integer().nullable()();
  RealColumn get targetScoreOverride => real().nullable()();
  IntColumn get targetScoreTimeMsOverride => integer().nullable()();
  BoolColumn get autoStartNextTimedStepOverride => boolean().nullable()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (workout_template_program_id, snapshot_program_exercise_id)',
    'CHECK (sets_count_override IS NULL OR sets_count_override > 0)',
    'CHECK (target_time_seconds_override IS NULL OR '
        'target_time_seconds_override > 0)',
    'CHECK (target_reps_override IS NULL OR target_reps_override > 0)',
    'CHECK (target_score_override IS NULL OR target_score_override >= 0)',
    'CHECK (target_score_time_ms_override IS NULL OR '
        'target_score_time_ms_override > 0)',
    'CHECK (target_score_override IS NULL OR '
        'target_score_time_ms_override IS NULL)',
  ];
}

class ActiveWorkoutSessions extends SyncableTable {
  @override
  String get tableName => 'active_workout_sessions';

  TextColumn get sourceWorkoutTemplateId =>
      text().nullable().references(WorkoutTemplates, #id)();
  TextColumn get status => text()();
  DateTimeColumn get startedAt => dateTime()();
  DateTimeColumn get pausedAt => dateTime().nullable()();
  DateTimeColumn get endedAt => dateTime().nullable()();
  DateTimeColumn get lastPersistedAt => dateTime()();
  IntColumn get elapsedActiveMs => integer()();
  IntColumn get currentProgramIndex => integer()();
  IntColumn get currentExerciseIndex => integer()();
  IntColumn get currentSetIndex => integer()();
  TextColumn get resolvedTemplateSnapshotJson => text().withLength(min: 1)();

  @override
  List<String> get customConstraints => [
    "CHECK (status IN ('running', 'paused', 'savedExit', 'completed', "
        "'abandoned'))",
    'CHECK (elapsed_active_ms >= 0)',
    'CHECK (current_program_index >= 0)',
    'CHECK (current_exercise_index >= 0)',
    'CHECK (current_set_index >= 0)',
  ];
}

class ActiveSetResults extends SyncableTable {
  @override
  String get tableName => 'active_set_results';

  TextColumn get activeWorkoutSessionId =>
      text().references(ActiveWorkoutSessions, #id)();
  TextColumn get programSnapshotId => text().withLength(min: 1)();
  TextColumn get exerciseSnapshotId => text().withLength(min: 1)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  DateTimeColumn get startedAt => dateTime().nullable()();
  DateTimeColumn get completedAt => dateTime().nullable()();
  IntColumn get actualTimeMs => integer().nullable()();
  IntColumn get actualReps => integer().nullable()();
  RealColumn get actualScore => real().nullable()();
  IntColumn get actualScoreTimeMs => integer().nullable()();
  TextColumn get scoreInputModeSnapshot =>
      text().withDefault(const Constant('manual'))();
  TextColumn get scoreLabelSnapshot => text().nullable()();
  TextColumn get scoreUnitSnapshot => text().nullable()();
  TextColumn get note => text().nullable()();
  TextColumn get status => text().withDefault(const Constant('completed'))();

  @override
  List<String> get customConstraints => [
    'UNIQUE (active_workout_session_id, program_index, exercise_index, '
        'set_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    'CHECK (actual_time_ms IS NULL OR actual_time_ms >= 0)',
    'CHECK (actual_reps IS NULL OR actual_reps >= 0)',
    'CHECK (actual_score IS NULL OR actual_score >= 0)',
    'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
    "CHECK (status IN ('completed', 'skipped'))",
    'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
        'AND actual_reps IS NULL AND actual_score IS NULL '
        'AND actual_score_time_ms IS NULL))',
    "CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
    'CHECK (actual_score IS NULL OR '
        "score_input_mode_snapshot = 'manual')",
    'CHECK (actual_score_time_ms IS NULL OR '
        "score_input_mode_snapshot = 'stopwatch')",
    'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
    'CHECK (actual_score IS NULL OR (score_label_snapshot IS NOT NULL '
        'AND length(trim(score_label_snapshot)) > 0 '
        'AND score_unit_snapshot IS NOT NULL '
        'AND length(trim(score_unit_snapshot)) > 0))',
  ];
}

class ActiveScoreStopwatchStates extends SyncableTable {
  @override
  String get tableName => 'active_score_stopwatch_states';

  TextColumn get activeWorkoutSessionId =>
      text().references(ActiveWorkoutSessions, #id)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  TextColumn get status => text()();
  DateTimeColumn get startedAt => dateTime()();
  IntColumn get accumulatedMs => integer()();
  DateTimeColumn get stoppedAt => dateTime().nullable()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (active_workout_session_id, program_index, exercise_index, '
        'set_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    "CHECK (status IN ('running', 'paused', 'stopped'))",
    'CHECK (accumulated_ms >= 0)',
  ];
}

class ActiveSetTimerStates extends SyncableTable {
  @override
  String get tableName => 'active_set_timer_states';

  TextColumn get activeWorkoutSessionId =>
      text().references(ActiveWorkoutSessions, #id)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  TextColumn get status => text()();
  DateTimeColumn get startedAt => dateTime().nullable()();
  IntColumn get accumulatedMs => integer()();
  DateTimeColumn get stoppedAt => dateTime().nullable()();
  DateTimeColumn get skippedAt => dateTime().nullable()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (active_workout_session_id, program_index, exercise_index, '
        'set_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    "CHECK (status IN ('running', 'paused', 'stopped', 'skipped'))",
    'CHECK (status != \'running\' OR started_at IS NOT NULL)',
    'CHECK (accumulated_ms >= 0)',
    'CHECK (stopped_at IS NULL OR skipped_at IS NULL)',
  ];
}

class ActiveRestStates extends SyncableTable {
  @override
  String get tableName => 'active_rest_states';

  TextColumn get activeWorkoutSessionId =>
      text().references(ActiveWorkoutSessions, #id)();
  IntColumn get afterProgramIndex => integer()();
  IntColumn get afterExerciseIndex => integer()();
  IntColumn get afterSetIndex => integer()();
  IntColumn get plannedRestSeconds => integer()();
  IntColumn get adjustedRestSeconds => integer()();
  DateTimeColumn get startedAt => dateTime()();
  DateTimeColumn get endedAt => dateTime().nullable()();
  DateTimeColumn get skippedAt => dateTime().nullable()();
  DateTimeColumn get pausedAt => dateTime().nullable()();
  IntColumn get accumulatedPausedMs =>
      integer().withDefault(const Constant(0))();

  @override
  List<String> get customConstraints => [
    'CHECK (after_program_index >= 0)',
    'CHECK (after_exercise_index >= 0)',
    'CHECK (after_set_index >= 0)',
    'CHECK (planned_rest_seconds >= 0)',
    'CHECK (adjusted_rest_seconds >= 0)',
    'CHECK (accumulated_paused_ms >= 0)',
    'CHECK (ended_at IS NULL OR skipped_at IS NULL)',
  ];
}

class ActiveExerciseStepProgressStates extends SyncableTable {
  @override
  String get tableName => 'active_exercise_step_progress_states';

  TextColumn get activeWorkoutSessionId =>
      text().references(ActiveWorkoutSessions, #id)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  IntColumn get currentPassageIndex => integer()();
  IntColumn get currentStepIndex => integer()();
  TextColumn get currentStepSnapshotId => text().withLength(min: 1)();
  TextColumn get status => text()();
  DateTimeColumn get startedAt => dateTime().nullable()();
  IntColumn get accumulatedMs => integer()();
  DateTimeColumn get lastTransitionAt => dateTime()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (active_workout_session_id, program_index, exercise_index, '
        'set_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    'CHECK (current_passage_index >= 0)',
    'CHECK (current_step_index >= 0)',
    "CHECK (status IN ('notStarted', 'waitingManual', 'runningTimer', "
        "'pausedTimer', 'stoppedTimer', 'sequenceComplete'))",
    'CHECK (status != \'runningTimer\' OR started_at IS NOT NULL)',
    'CHECK (accumulated_ms >= 0)',
  ];
}

class ActiveExerciseStepResults extends SyncableTable {
  @override
  String get tableName => 'active_exercise_step_results';

  TextColumn get activeWorkoutSessionId =>
      text().references(ActiveWorkoutSessions, #id)();
  TextColumn get programSnapshotId => text().withLength(min: 1)();
  TextColumn get exerciseSnapshotId => text().withLength(min: 1)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  IntColumn get passageIndex => integer()();
  IntColumn get stepIndex => integer()();
  TextColumn get stepSnapshotId => text().withLength(min: 1)();
  TextColumn get stepNameSnapshot => text().withLength(min: 1)();
  TextColumn get stepTypeSnapshot => text()();
  IntColumn get targetValueSnapshot => integer()();
  BoolColumn get hasScoreSnapshot => boolean()();
  TextColumn get scoreInputModeSnapshot => text().nullable()();
  TextColumn get scoreLabelSnapshot => text().nullable()();
  TextColumn get scoreUnitSnapshot => text().nullable()();
  RealColumn get targetScoreSnapshot => real().nullable()();
  IntColumn get targetScoreTimeMsSnapshot => integer().nullable()();
  TextColumn get status => text()();
  DateTimeColumn get startedAt => dateTime().nullable()();
  DateTimeColumn get completedAt => dateTime().nullable()();
  IntColumn get actualTimeMs => integer().nullable()();
  IntColumn get actualReps => integer().nullable()();
  RealColumn get actualScore => real().nullable()();
  IntColumn get actualScoreTimeMs => integer().nullable()();
  TextColumn get note => text().nullable()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (active_workout_session_id, program_index, exercise_index, '
        'set_index, passage_index, step_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    'CHECK (passage_index >= 0)',
    'CHECK (step_index >= 0)',
    "CHECK (step_type_snapshot IN ('time', 'reps'))",
    'CHECK (target_value_snapshot > 0)',
    'CHECK (score_input_mode_snapshot IS NULL OR '
        "score_input_mode_snapshot IN ('manual', 'stopwatch'))",
    "CHECK (status IN ('completed', 'skipped'))",
    'CHECK (actual_time_ms IS NULL OR actual_time_ms >= 0)',
    'CHECK (actual_reps IS NULL OR actual_reps >= 0)',
    'CHECK (actual_score IS NULL OR actual_score >= 0)',
    'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
    'CHECK (actual_time_ms IS NULL OR step_type_snapshot = \'time\')',
    'CHECK (actual_reps IS NULL OR step_type_snapshot = \'reps\')',
    'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
        'AND actual_reps IS NULL AND actual_score IS NULL '
        'AND actual_score_time_ms IS NULL))',
    'CHECK (target_score_snapshot IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
    'CHECK (target_score_time_ms_snapshot IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
    'CHECK (actual_score IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
    'CHECK (actual_score_time_ms IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
    'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
    'CHECK (target_score_snapshot IS NULL OR '
        'target_score_time_ms_snapshot IS NULL)',
  ];
}

class WorkoutHistories extends SyncableTable {
  @override
  String get tableName => 'workout_history';

  TextColumn get sourceWorkoutTemplateId =>
      text().nullable().references(WorkoutTemplates, #id)();
  TextColumn get sourceActiveWorkoutSessionId =>
      text().nullable().references(ActiveWorkoutSessions, #id)();
  TextColumn get nameSnapshot => text().withLength(min: 1)();
  DateTimeColumn get startedAt => dateTime()();
  DateTimeColumn get endedAt => dateTime()();
  IntColumn get totalActiveMs => integer()();
  BoolColumn get completed => boolean()();
  TextColumn get historySnapshotJson => text().withLength(min: 1)();

  @override
  List<String> get customConstraints => ['CHECK (total_active_ms >= 0)'];
}

class WorkoutHistorySetResults extends SyncableTable {
  @override
  String get tableName => 'workout_history_set_results';

  TextColumn get workoutHistoryId => text().references(WorkoutHistories, #id)();
  TextColumn get programSnapshotId => text().withLength(min: 1)();
  TextColumn get exerciseSnapshotId => text().withLength(min: 1)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  TextColumn get programNameSnapshot => text().withLength(min: 1)();
  TextColumn get exerciseNameSnapshot => text().withLength(min: 1)();
  BoolColumn get timeEnabledSnapshot => boolean()();
  BoolColumn get repsEnabledSnapshot => boolean()();
  BoolColumn get scoreEnabledSnapshot => boolean()();
  TextColumn get scoreInputModeSnapshot =>
      text().withDefault(const Constant('manual'))();
  IntColumn get targetTimeSecondsSnapshot => integer().nullable()();
  IntColumn get targetRepsSnapshot => integer().nullable()();
  RealColumn get targetScoreSnapshot => real().nullable()();
  IntColumn get targetScoreTimeMsSnapshot => integer().nullable()();
  IntColumn get actualTimeMs => integer().nullable()();
  IntColumn get actualReps => integer().nullable()();
  RealColumn get actualScore => real().nullable()();
  IntColumn get actualScoreTimeMs => integer().nullable()();
  TextColumn get scoreLabelSnapshot => text().nullable()();
  TextColumn get scoreUnitSnapshot => text().nullable()();
  TextColumn get sourceExerciseIdSnapshot => text().nullable()();
  DateTimeColumn get startedAt => dateTime().nullable()();
  DateTimeColumn get completedAt => dateTime().nullable()();
  TextColumn get status => text().withDefault(const Constant('completed'))();

  @override
  List<String> get customConstraints => [
    'UNIQUE (workout_history_id, program_index, exercise_index, '
        'set_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    'CHECK (time_enabled_snapshot OR reps_enabled_snapshot OR '
        'score_enabled_snapshot)',
    'CHECK (target_time_seconds_snapshot IS NULL OR '
        'target_time_seconds_snapshot > 0)',
    'CHECK (target_reps_snapshot IS NULL OR target_reps_snapshot > 0)',
    'CHECK (target_score_snapshot IS NULL OR target_score_snapshot >= 0)',
    'CHECK (target_score_time_ms_snapshot IS NULL OR '
        'target_score_time_ms_snapshot > 0)',
    'CHECK (actual_time_ms IS NULL OR actual_time_ms >= 0)',
    'CHECK (actual_reps IS NULL OR actual_reps >= 0)',
    'CHECK (actual_score IS NULL OR actual_score >= 0)',
    'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
    "CHECK (status IN ('completed', 'skipped'))",
    'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
        'AND actual_reps IS NULL AND actual_score IS NULL '
        'AND actual_score_time_ms IS NULL))',
    "CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
    'CHECK (target_score_snapshot IS NULL OR '
        "(score_enabled_snapshot AND score_input_mode_snapshot = 'manual'))",
    'CHECK (target_score_time_ms_snapshot IS NULL OR '
        "(score_enabled_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
    'CHECK (target_score_snapshot IS NULL OR '
        'target_score_time_ms_snapshot IS NULL)',
    'CHECK (actual_score IS NULL OR '
        "score_input_mode_snapshot = 'manual')",
    'CHECK (actual_score_time_ms IS NULL OR '
        "score_input_mode_snapshot = 'stopwatch')",
    'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
    'CHECK (actual_score IS NULL OR (score_label_snapshot IS NOT NULL '
        'AND length(trim(score_label_snapshot)) > 0 '
        'AND score_unit_snapshot IS NOT NULL '
        'AND length(trim(score_unit_snapshot)) > 0))',
  ];
}

class WorkoutHistoryStepResults extends SyncableTable {
  @override
  String get tableName => 'workout_history_step_results';

  TextColumn get workoutHistoryId => text().references(WorkoutHistories, #id)();
  TextColumn get programSnapshotId => text().withLength(min: 1)();
  TextColumn get exerciseSnapshotId => text().withLength(min: 1)();
  IntColumn get programIndex => integer()();
  IntColumn get exerciseIndex => integer()();
  IntColumn get setIndex => integer()();
  IntColumn get passageIndex => integer()();
  IntColumn get stepIndex => integer()();
  TextColumn get stepSnapshotId => text().withLength(min: 1)();
  TextColumn get stepNameSnapshot => text().withLength(min: 1)();
  TextColumn get stepTypeSnapshot => text()();
  IntColumn get targetValueSnapshot => integer()();
  BoolColumn get hasScoreSnapshot => boolean()();
  TextColumn get scoreInputModeSnapshot => text().nullable()();
  TextColumn get scoreLabelSnapshot => text().nullable()();
  TextColumn get scoreUnitSnapshot => text().nullable()();
  RealColumn get targetScoreSnapshot => real().nullable()();
  IntColumn get targetScoreTimeMsSnapshot => integer().nullable()();
  TextColumn get status => text()();
  DateTimeColumn get startedAt => dateTime().nullable()();
  DateTimeColumn get completedAt => dateTime().nullable()();
  IntColumn get actualTimeMs => integer().nullable()();
  IntColumn get actualReps => integer().nullable()();
  RealColumn get actualScore => real().nullable()();
  IntColumn get actualScoreTimeMs => integer().nullable()();
  TextColumn get note => text().nullable()();
  TextColumn get sourceExerciseIdSnapshot => text().nullable()();

  @override
  List<String> get customConstraints => [
    'UNIQUE (workout_history_id, program_index, exercise_index, set_index, '
        'passage_index, step_index)',
    'CHECK (program_index >= 0)',
    'CHECK (exercise_index >= 0)',
    'CHECK (set_index >= 0)',
    'CHECK (passage_index >= 0)',
    'CHECK (step_index >= 0)',
    "CHECK (step_type_snapshot IN ('time', 'reps'))",
    'CHECK (target_value_snapshot > 0)',
    'CHECK (score_input_mode_snapshot IS NULL OR '
        "score_input_mode_snapshot IN ('manual', 'stopwatch'))",
    "CHECK (status IN ('completed', 'skipped'))",
    'CHECK (actual_time_ms IS NULL OR actual_time_ms >= 0)',
    'CHECK (actual_reps IS NULL OR actual_reps >= 0)',
    'CHECK (actual_score IS NULL OR actual_score >= 0)',
    'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
    'CHECK (actual_time_ms IS NULL OR step_type_snapshot = \'time\')',
    'CHECK (actual_reps IS NULL OR step_type_snapshot = \'reps\')',
    'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
        'AND actual_reps IS NULL AND actual_score IS NULL '
        'AND actual_score_time_ms IS NULL))',
    'CHECK (target_score_snapshot IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
    'CHECK (target_score_time_ms_snapshot IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
    'CHECK (actual_score IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
    'CHECK (actual_score_time_ms IS NULL OR '
        "(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
    'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
    'CHECK (target_score_snapshot IS NULL OR '
        'target_score_time_ms_snapshot IS NULL)',
  ];
}

class ChangeLogEntries extends Table {
  @override
  String get tableName => 'change_log';

  TextColumn get id => text()();
  TextColumn get entityType => text().withLength(min: 1)();
  TextColumn get entityId => text().withLength(min: 1)();
  TextColumn get operation => text()();
  TextColumn get payloadHash => text().nullable()();
  IntColumn get localRevision => integer()();
  TextColumn get originDeviceId => text().withLength(min: 1)();
  DateTimeColumn get createdAt => dateTime()();
  DateTimeColumn get syncedAt => dateTime().nullable()();

  @override
  Set<Column> get primaryKey => {id};

  @override
  List<String> get customConstraints => const [
    "CHECK (operation IN ('insert', 'update', 'softDelete', 'restore'))",
    'CHECK (local_revision >= 0)',
  ];
}
