const int watchBridgeSchemaVersion = 7;

enum WatchCommandType {
  startCurrentExercise,
  pauseSession,
  resumeSession,
  startPreparedTimedStep,
  completeCurrentStep,
  skipCurrentStep,
  skipCurrentPassage,
  finishCurrentSet,
  skipCurrentSet,
  skipCurrentRest,
  incrementScore,
  decrementScore,
}

enum WatchCommandAck {
  accepted,
  acceptedNoOp,
  rejectedStaleRevision,
  rejectedNotApplicable,
  rejectedNoActiveSession,
  rejectedSessionMismatch,
  rejectedPhoneBusy,
}

enum WatchSessionPhase {
  noActiveSession,
  ready,
  running,
  paused,
  nextTimerReady,
  restRunning,
  restPaused,
  betweenSetsReady,
}

enum WatchPrimaryAction {
  none,
  startCurrentExercise,
  pauseSession,
  resumeSession,
  startPreparedTimedStep,
  skipCurrentRest,
}

enum WatchSecondaryAction {
  skipCurrentStep,
  skipCurrentPassage,
  finishCurrentSet,
  skipCurrentSet,
  skipCurrentRest,
}

enum WatchTimerKind { rest, step, scoreStopwatch, setTimer }

enum WatchTimerDisplayMode { countdown, elapsed }

enum WatchTimerRunState { stopped, running, paused }

enum WatchManualScoreScope { series, step }

enum WatchStepType { time, reps }

enum WatchAlertKind { countdownTick, countdownFinished }

enum WatchAlertPattern { countdownTick, timerFinished }

final class WatchCommandEnvelope {
  const WatchCommandEnvelope({
    this.schemaVersion = watchBridgeSchemaVersion,
    required this.commandId,
    required this.type,
    required this.sessionId,
    required this.expectedRevision,
    required this.sentAtEpochMs,
  });

  factory WatchCommandEnvelope.fromJson(Map<String, Object?> json) {
    return WatchCommandEnvelope(
      schemaVersion: _intFromJson(
        json['schemaVersion'],
        watchBridgeSchemaVersion,
      ),
      commandId: _stringFromJson(json['commandId']),
      type: _enumFromJson(
        json['type'],
        WatchCommandType.values,
        WatchCommandType.startCurrentExercise,
      ),
      sessionId: _stringFromJson(json['sessionId']),
      expectedRevision: _intFromJson(json['expectedRevision'], 0),
      sentAtEpochMs: _intFromJson(json['sentAtEpochMs'], 0),
    );
  }

  final int schemaVersion;
  final String commandId;
  final WatchCommandType type;
  final String sessionId;
  final int expectedRevision;
  final int sentAtEpochMs;

  Map<String, Object?> toJson() {
    return {
      'schemaVersion': schemaVersion,
      'commandId': commandId,
      'type': type.name,
      'sessionId': sessionId,
      'expectedRevision': expectedRevision,
      'sentAtEpochMs': sentAtEpochMs,
    };
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is WatchCommandEnvelope &&
            schemaVersion == other.schemaVersion &&
            commandId == other.commandId &&
            type == other.type &&
            sessionId == other.sessionId &&
            expectedRevision == other.expectedRevision &&
            sentAtEpochMs == other.sentAtEpochMs;
  }

  @override
  int get hashCode {
    return Object.hash(
      schemaVersion,
      commandId,
      type,
      sessionId,
      expectedRevision,
      sentAtEpochMs,
    );
  }
}

final class WatchAlertEnvelope {
  const WatchAlertEnvelope({
    this.schemaVersion = watchBridgeSchemaVersion,
    required this.alertId,
    required this.sessionId,
    required this.revision,
    required this.kind,
    required this.timerKind,
    this.programIndex,
    this.exerciseIndex,
    this.setIndex,
    this.passageIndex,
    this.stepIndex,
    required this.scheduledForEpochMs,
    required this.expiresAtEpochMs,
    required this.pattern,
  });

  factory WatchAlertEnvelope.fromJson(Map<String, Object?> json) {
    return WatchAlertEnvelope(
      schemaVersion: _intFromJson(
        json['schemaVersion'],
        watchBridgeSchemaVersion,
      ),
      alertId: _stringFromJson(json['alertId']),
      sessionId: _stringFromJson(json['sessionId']),
      revision: _intFromJson(json['revision'], 0),
      kind: _enumFromJson(
        json['kind'],
        WatchAlertKind.values,
        WatchAlertKind.countdownTick,
      ),
      timerKind: _enumFromJson(
        json['timerKind'],
        WatchTimerKind.values,
        WatchTimerKind.step,
      ),
      programIndex: _nullableIntFromJson(json['programIndex']),
      exerciseIndex: _nullableIntFromJson(json['exerciseIndex']),
      setIndex: _nullableIntFromJson(json['setIndex']),
      passageIndex: _nullableIntFromJson(json['passageIndex']),
      stepIndex: _nullableIntFromJson(json['stepIndex']),
      scheduledForEpochMs: _intFromJson(json['scheduledForEpochMs'], 0),
      expiresAtEpochMs: _intFromJson(json['expiresAtEpochMs'], 0),
      pattern: _enumFromJson(
        json['pattern'],
        WatchAlertPattern.values,
        WatchAlertPattern.countdownTick,
      ),
    );
  }

  final int schemaVersion;
  final String alertId;
  final String sessionId;
  final int revision;
  final WatchAlertKind kind;
  final WatchTimerKind timerKind;
  final int? programIndex;
  final int? exerciseIndex;
  final int? setIndex;
  final int? passageIndex;
  final int? stepIndex;
  final int scheduledForEpochMs;
  final int expiresAtEpochMs;
  final WatchAlertPattern pattern;

  Map<String, Object?> toJson() {
    return {
      'schemaVersion': schemaVersion,
      'alertId': alertId,
      'sessionId': sessionId,
      'revision': revision,
      'kind': kind.name,
      'timerKind': timerKind.name,
      'programIndex': programIndex,
      'exerciseIndex': exerciseIndex,
      'setIndex': setIndex,
      'passageIndex': passageIndex,
      'stepIndex': stepIndex,
      'scheduledForEpochMs': scheduledForEpochMs,
      'expiresAtEpochMs': expiresAtEpochMs,
      'pattern': pattern.name,
    };
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is WatchAlertEnvelope &&
            schemaVersion == other.schemaVersion &&
            alertId == other.alertId &&
            sessionId == other.sessionId &&
            revision == other.revision &&
            kind == other.kind &&
            timerKind == other.timerKind &&
            programIndex == other.programIndex &&
            exerciseIndex == other.exerciseIndex &&
            setIndex == other.setIndex &&
            passageIndex == other.passageIndex &&
            stepIndex == other.stepIndex &&
            scheduledForEpochMs == other.scheduledForEpochMs &&
            expiresAtEpochMs == other.expiresAtEpochMs &&
            pattern == other.pattern;
  }

  @override
  int get hashCode {
    return Object.hash(
      schemaVersion,
      alertId,
      sessionId,
      revision,
      kind,
      timerKind,
      programIndex,
      exerciseIndex,
      setIndex,
      passageIndex,
      stepIndex,
      scheduledForEpochMs,
      expiresAtEpochMs,
      pattern,
    );
  }
}

final class WatchSessionProjection {
  const WatchSessionProjection({
    this.schemaVersion = watchBridgeSchemaVersion,
    required this.deviceSessionId,
    required this.revision,
    required this.projectedAtEpochMs,
    this.expiresAtEpochMs = 0,
    required this.phase,
    required this.phoneReachable,
    required this.seriesIndex,
    required this.seriesTotal,
    required this.exerciseName,
    this.programIndex,
    this.exerciseIndex,
    this.setIndex,
    this.passageIndex,
    this.passageTotal,
    this.stepIndex,
    this.stepTotal,
    this.stepName,
    this.stepType,
    this.stepTargetValue,
    this.dominantTimer,
    this.secondaryTimers = const [],
    required this.primaryAction,
    this.secondaryActions = const [],
    this.nextExerciseName,
    this.statusLabel,
    this.hasManualScore = false,
    this.currentManualScoreValue,
    this.canDecrementScore = false,
    this.manualScoreTargetValue,
    this.manualScoreTargetLabel,
    this.manualScoreRepsTargetValue,
    this.manualScoreScope,
    this.healthServicesExerciseTypeStrategy = const [],
  });

  factory WatchSessionProjection.fromJson(Map<String, Object?> json) {
    return WatchSessionProjection(
      schemaVersion: _intFromJson(
        json['schemaVersion'],
        watchBridgeSchemaVersion,
      ),
      deviceSessionId: _stringFromJson(json['deviceSessionId']),
      revision: _intFromJson(json['revision'], 0),
      projectedAtEpochMs: _intFromJson(json['projectedAtEpochMs'], 0),
      expiresAtEpochMs: _intFromJson(json['expiresAtEpochMs'], 0),
      phase: _enumFromJson(
        json['phase'],
        WatchSessionPhase.values,
        WatchSessionPhase.noActiveSession,
      ),
      phoneReachable: _boolFromJson(json['phoneReachable'], false),
      seriesIndex: _intFromJson(json['seriesIndex'], 0),
      seriesTotal: _intFromJson(json['seriesTotal'], 0),
      exerciseName: _stringFromJson(json['exerciseName']),
      programIndex: _nullableIntFromJson(json['programIndex']),
      exerciseIndex: _nullableIntFromJson(json['exerciseIndex']),
      setIndex: _nullableIntFromJson(json['setIndex']),
      passageIndex: _nullableIntFromJson(json['passageIndex']),
      passageTotal: _nullableIntFromJson(json['passageTotal']),
      stepIndex: _nullableIntFromJson(json['stepIndex']),
      stepTotal: _nullableIntFromJson(json['stepTotal']),
      stepName: _nullableStringFromJson(json['stepName']),
      stepType: _nullableEnumFromJson(json['stepType'], WatchStepType.values),
      stepTargetValue: _nullableIntFromJson(json['stepTargetValue']),
      dominantTimer: _timerFromJson(json['dominantTimer']),
      secondaryTimers: _timerListFromJson(json['secondaryTimers']),
      primaryAction: _enumFromJson(
        json['primaryAction'],
        WatchPrimaryAction.values,
        WatchPrimaryAction.none,
      ),
      secondaryActions: _enumListFromJson(
        json['secondaryActions'],
        WatchSecondaryAction.values,
      ),
      nextExerciseName: _nullableStringFromJson(json['nextExerciseName']),
      statusLabel: _nullableStringFromJson(json['statusLabel']),
      hasManualScore: _boolFromJson(json['hasManualScore'], false),
      currentManualScoreValue: _nullableDoubleFromJson(
        json['currentManualScoreValue'],
      ),
      canDecrementScore: _boolFromJson(json['canDecrementScore'], false),
      manualScoreTargetValue: _nullableDoubleFromJson(
        json['manualScoreTargetValue'],
      ),
      manualScoreTargetLabel: _nullableStringFromJson(
        json['manualScoreTargetLabel'],
      ),
      manualScoreRepsTargetValue: _nullableIntFromJson(
        json['manualScoreRepsTargetValue'],
      ),
      manualScoreScope: _nullableEnumFromJson(
        json['manualScoreScope'],
        WatchManualScoreScope.values,
      ),
      healthServicesExerciseTypeStrategy: _stringListFromJson(
        json['healthServicesExerciseTypeStrategy'],
      ),
    );
  }

  final int schemaVersion;
  final String deviceSessionId;
  final int revision;
  final int projectedAtEpochMs;
  final int expiresAtEpochMs;
  final WatchSessionPhase phase;
  final bool phoneReachable;
  final int seriesIndex;
  final int seriesTotal;
  final String exerciseName;
  final int? programIndex;
  final int? exerciseIndex;
  final int? setIndex;
  final int? passageIndex;
  final int? passageTotal;
  final int? stepIndex;
  final int? stepTotal;
  final String? stepName;
  final WatchStepType? stepType;
  final int? stepTargetValue;
  final WatchTimerProjection? dominantTimer;
  final List<WatchTimerProjection> secondaryTimers;
  final WatchPrimaryAction primaryAction;
  final List<WatchSecondaryAction> secondaryActions;
  final String? nextExerciseName;
  final String? statusLabel;
  final bool hasManualScore;
  final double? currentManualScoreValue;
  final bool canDecrementScore;
  final double? manualScoreTargetValue;
  final String? manualScoreTargetLabel;
  final int? manualScoreRepsTargetValue;
  final WatchManualScoreScope? manualScoreScope;
  final List<String> healthServicesExerciseTypeStrategy;

  Map<String, Object?> toJson() {
    return {
      'schemaVersion': schemaVersion,
      'deviceSessionId': deviceSessionId,
      'revision': revision,
      'projectedAtEpochMs': projectedAtEpochMs,
      'expiresAtEpochMs': expiresAtEpochMs,
      'phase': phase.name,
      'phoneReachable': phoneReachable,
      'seriesIndex': seriesIndex,
      'seriesTotal': seriesTotal,
      'exerciseName': exerciseName,
      'programIndex': programIndex,
      'exerciseIndex': exerciseIndex,
      'setIndex': setIndex,
      'passageIndex': passageIndex,
      'passageTotal': passageTotal,
      'stepIndex': stepIndex,
      'stepTotal': stepTotal,
      'stepName': stepName,
      'stepType': stepType?.name,
      'stepTargetValue': stepTargetValue,
      'dominantTimer': dominantTimer?.toJson(),
      'secondaryTimers': secondaryTimers
          .map((timer) => timer.toJson())
          .toList(),
      'primaryAction': primaryAction.name,
      'secondaryActions': secondaryActions
          .map((action) => action.name)
          .toList(),
      'nextExerciseName': nextExerciseName,
      'statusLabel': statusLabel,
      'hasManualScore': hasManualScore,
      'currentManualScoreValue': currentManualScoreValue,
      'canDecrementScore': canDecrementScore,
      'manualScoreTargetValue': manualScoreTargetValue,
      'manualScoreTargetLabel': manualScoreTargetLabel,
      'manualScoreRepsTargetValue': manualScoreRepsTargetValue,
      'manualScoreScope': manualScoreScope?.name,
      'healthServicesExerciseTypeStrategy': healthServicesExerciseTypeStrategy,
    };
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is WatchSessionProjection &&
            schemaVersion == other.schemaVersion &&
            deviceSessionId == other.deviceSessionId &&
            revision == other.revision &&
            projectedAtEpochMs == other.projectedAtEpochMs &&
            expiresAtEpochMs == other.expiresAtEpochMs &&
            phase == other.phase &&
            phoneReachable == other.phoneReachable &&
            seriesIndex == other.seriesIndex &&
            seriesTotal == other.seriesTotal &&
            exerciseName == other.exerciseName &&
            programIndex == other.programIndex &&
            exerciseIndex == other.exerciseIndex &&
            setIndex == other.setIndex &&
            passageIndex == other.passageIndex &&
            passageTotal == other.passageTotal &&
            stepIndex == other.stepIndex &&
            stepTotal == other.stepTotal &&
            stepName == other.stepName &&
            stepType == other.stepType &&
            stepTargetValue == other.stepTargetValue &&
            dominantTimer == other.dominantTimer &&
            _listEquals(secondaryTimers, other.secondaryTimers) &&
            primaryAction == other.primaryAction &&
            _listEquals(secondaryActions, other.secondaryActions) &&
            nextExerciseName == other.nextExerciseName &&
            statusLabel == other.statusLabel &&
            hasManualScore == other.hasManualScore &&
            currentManualScoreValue == other.currentManualScoreValue &&
            canDecrementScore == other.canDecrementScore &&
            manualScoreTargetValue == other.manualScoreTargetValue &&
            manualScoreTargetLabel == other.manualScoreTargetLabel &&
            manualScoreRepsTargetValue == other.manualScoreRepsTargetValue &&
            manualScoreScope == other.manualScoreScope &&
            _listEquals(
              healthServicesExerciseTypeStrategy,
              other.healthServicesExerciseTypeStrategy,
            );
  }

  @override
  int get hashCode {
    return Object.hashAll([
      schemaVersion,
      deviceSessionId,
      revision,
      projectedAtEpochMs,
      expiresAtEpochMs,
      phase,
      phoneReachable,
      seriesIndex,
      seriesTotal,
      exerciseName,
      programIndex,
      exerciseIndex,
      setIndex,
      passageIndex,
      passageTotal,
      stepIndex,
      stepTotal,
      stepName,
      stepType,
      stepTargetValue,
      dominantTimer,
      Object.hashAll(secondaryTimers),
      primaryAction,
      Object.hashAll(secondaryActions),
      nextExerciseName,
      statusLabel,
      hasManualScore,
      currentManualScoreValue,
      canDecrementScore,
      manualScoreTargetValue,
      manualScoreTargetLabel,
      manualScoreRepsTargetValue,
      manualScoreScope,
      Object.hashAll(healthServicesExerciseTypeStrategy),
    ]);
  }
}

final class WatchSensorSummary {
  const WatchSensorSummary({
    this.schemaVersion = watchBridgeSchemaVersion,
    required this.sessionId,
    required this.sampleCount,
    this.minHeartRateBpm,
    this.averageHeartRateBpm,
    this.maxHeartRateBpm,
    this.totalDistanceMeters,
    this.totalCaloriesKcal,
  });

  factory WatchSensorSummary.fromJson(Map<String, Object?> json) {
    return WatchSensorSummary(
      schemaVersion: _intFromJson(
        json['schemaVersion'],
        watchBridgeSchemaVersion,
      ),
      sessionId: _stringFromJson(json['sessionId']),
      sampleCount: _intFromJson(json['sampleCount'], 0),
      minHeartRateBpm: _nullableIntFromJson(json['minHeartRateBpm']),
      averageHeartRateBpm: _nullableDoubleFromJson(json['averageHeartRateBpm']),
      maxHeartRateBpm: _nullableIntFromJson(json['maxHeartRateBpm']),
      totalDistanceMeters:
          _nullableDoubleFromJson(json['totalDistanceMeters']) ??
          _nullableDoubleFromJson(json['distanceMeters']),
      totalCaloriesKcal:
          _nullableDoubleFromJson(json['totalCaloriesKcal']) ??
          _nullableDoubleFromJson(json['caloriesKcal']),
    );
  }

  final int schemaVersion;
  final String sessionId;
  final int sampleCount;
  final int? minHeartRateBpm;
  final double? averageHeartRateBpm;
  final int? maxHeartRateBpm;
  final double? totalDistanceMeters;
  final double? totalCaloriesKcal;

  Map<String, Object?> toJson() {
    return {
      'schemaVersion': schemaVersion,
      'sessionId': sessionId,
      'sampleCount': sampleCount,
      'minHeartRateBpm': minHeartRateBpm,
      'averageHeartRateBpm': averageHeartRateBpm,
      'maxHeartRateBpm': maxHeartRateBpm,
      'totalDistanceMeters': totalDistanceMeters,
      'totalCaloriesKcal': totalCaloriesKcal,
    };
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is WatchSensorSummary &&
            schemaVersion == other.schemaVersion &&
            sessionId == other.sessionId &&
            sampleCount == other.sampleCount &&
            minHeartRateBpm == other.minHeartRateBpm &&
            averageHeartRateBpm == other.averageHeartRateBpm &&
            maxHeartRateBpm == other.maxHeartRateBpm &&
            totalDistanceMeters == other.totalDistanceMeters &&
            totalCaloriesKcal == other.totalCaloriesKcal;
  }

  @override
  int get hashCode {
    return Object.hash(
      schemaVersion,
      sessionId,
      sampleCount,
      minHeartRateBpm,
      averageHeartRateBpm,
      maxHeartRateBpm,
      totalDistanceMeters,
      totalCaloriesKcal,
    );
  }
}

final class WatchSensorSample {
  const WatchSensorSample({
    this.schemaVersion = watchBridgeSchemaVersion,
    this.sampleId,
    required this.sessionId,
    int? capturedAtEpochMs,
    int? recordedAtEpochMs,
    this.programIndex,
    this.exerciseIndex,
    this.setIndex,
    this.passageIndex,
    this.stepIndex,
    this.programSnapshotId,
    this.exerciseSnapshotId,
    this.stepSnapshotId,
    this.heartRateBpm,
    this.distanceMeters,
    this.caloriesKcal,
    this.sensorCollectionStatus,
  }) : capturedAtEpochMs = capturedAtEpochMs ?? recordedAtEpochMs ?? 0;

  factory WatchSensorSample.fromJson(Map<String, Object?> json) {
    return WatchSensorSample(
      schemaVersion: _intFromJson(
        json['schemaVersion'],
        watchBridgeSchemaVersion,
      ),
      sampleId: _nullableStringFromJson(json['sampleId']),
      sessionId: _stringFromJson(json['sessionId']),
      capturedAtEpochMs: _intFromJson(
        json['capturedAtEpochMs'],
        _intFromJson(json['recordedAtEpochMs'], 0),
      ),
      programIndex: _nullableIntFromJson(json['programIndex']),
      exerciseIndex: _nullableIntFromJson(json['exerciseIndex']),
      setIndex: _nullableIntFromJson(json['setIndex']),
      passageIndex: _nullableIntFromJson(json['passageIndex']),
      stepIndex: _nullableIntFromJson(json['stepIndex']),
      programSnapshotId: _nullableStringFromJson(json['programSnapshotId']),
      exerciseSnapshotId: _nullableStringFromJson(json['exerciseSnapshotId']),
      stepSnapshotId: _nullableStringFromJson(json['stepSnapshotId']),
      heartRateBpm: _nullableIntFromJson(json['heartRateBpm']),
      distanceMeters: _nullableDoubleFromJson(json['distanceMeters']),
      caloriesKcal: _nullableDoubleFromJson(json['caloriesKcal']),
      sensorCollectionStatus: _nullableStringFromJson(
        json['sensorCollectionStatus'],
      ),
    );
  }

  final int schemaVersion;
  final String? sampleId;
  final String sessionId;
  final int capturedAtEpochMs;
  final int? programIndex;
  final int? exerciseIndex;
  final int? setIndex;
  final int? passageIndex;
  final int? stepIndex;
  final String? programSnapshotId;
  final String? exerciseSnapshotId;
  final String? stepSnapshotId;
  final int? heartRateBpm;
  final double? distanceMeters;
  final double? caloriesKcal;
  final String? sensorCollectionStatus;

  int get recordedAtEpochMs => capturedAtEpochMs;

  Map<String, Object?> toJson() {
    return {
      'schemaVersion': schemaVersion,
      'sampleId': sampleId,
      'sessionId': sessionId,
      'capturedAtEpochMs': capturedAtEpochMs,
      'recordedAtEpochMs': capturedAtEpochMs,
      'programIndex': programIndex,
      'exerciseIndex': exerciseIndex,
      'setIndex': setIndex,
      'passageIndex': passageIndex,
      'stepIndex': stepIndex,
      'programSnapshotId': programSnapshotId,
      'exerciseSnapshotId': exerciseSnapshotId,
      'stepSnapshotId': stepSnapshotId,
      'heartRateBpm': heartRateBpm,
      'distanceMeters': distanceMeters,
      'caloriesKcal': caloriesKcal,
      'sensorCollectionStatus': sensorCollectionStatus,
    };
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is WatchSensorSample &&
            schemaVersion == other.schemaVersion &&
            sampleId == other.sampleId &&
            sessionId == other.sessionId &&
            capturedAtEpochMs == other.capturedAtEpochMs &&
            programIndex == other.programIndex &&
            exerciseIndex == other.exerciseIndex &&
            setIndex == other.setIndex &&
            passageIndex == other.passageIndex &&
            stepIndex == other.stepIndex &&
            programSnapshotId == other.programSnapshotId &&
            exerciseSnapshotId == other.exerciseSnapshotId &&
            stepSnapshotId == other.stepSnapshotId &&
            heartRateBpm == other.heartRateBpm &&
            distanceMeters == other.distanceMeters &&
            caloriesKcal == other.caloriesKcal &&
            sensorCollectionStatus == other.sensorCollectionStatus;
  }

  @override
  int get hashCode {
    return Object.hash(
      schemaVersion,
      sampleId,
      sessionId,
      capturedAtEpochMs,
      programIndex,
      exerciseIndex,
      setIndex,
      passageIndex,
      stepIndex,
      programSnapshotId,
      exerciseSnapshotId,
      stepSnapshotId,
      heartRateBpm,
      distanceMeters,
      caloriesKcal,
      sensorCollectionStatus,
    );
  }
}

typedef WatchTelemetrySummary = WatchSensorSummary;
typedef WatchTelemetrySample = WatchSensorSample;

final class WatchTimerProjection {
  const WatchTimerProjection({
    required this.kind,
    required this.label,
    required this.displayMode,
    required this.runState,
    required this.referenceEpochMs,
    required this.accumulatedMs,
    this.startedAtEpochMs,
    this.targetMs,
  });

  factory WatchTimerProjection.fromJson(Map<String, Object?> json) {
    return WatchTimerProjection(
      kind: _enumFromJson(
        json['kind'],
        WatchTimerKind.values,
        WatchTimerKind.setTimer,
      ),
      label: _stringFromJson(json['label']),
      displayMode: _enumFromJson(
        json['displayMode'],
        WatchTimerDisplayMode.values,
        WatchTimerDisplayMode.elapsed,
      ),
      runState: _enumFromJson(
        json['runState'],
        WatchTimerRunState.values,
        WatchTimerRunState.stopped,
      ),
      referenceEpochMs: _intFromJson(json['referenceEpochMs'], 0),
      accumulatedMs: _intFromJson(json['accumulatedMs'], 0),
      startedAtEpochMs: _nullableIntFromJson(json['startedAtEpochMs']),
      targetMs: _nullableIntFromJson(json['targetMs']),
    );
  }

  final WatchTimerKind kind;
  final String label;
  final WatchTimerDisplayMode displayMode;
  final WatchTimerRunState runState;
  final int referenceEpochMs;
  final int accumulatedMs;
  final int? startedAtEpochMs;
  final int? targetMs;

  Map<String, Object?> toJson() {
    return {
      'kind': kind.name,
      'label': label,
      'displayMode': displayMode.name,
      'runState': runState.name,
      'referenceEpochMs': referenceEpochMs,
      'accumulatedMs': accumulatedMs,
      'startedAtEpochMs': startedAtEpochMs,
      'targetMs': targetMs,
    };
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is WatchTimerProjection &&
            kind == other.kind &&
            label == other.label &&
            displayMode == other.displayMode &&
            runState == other.runState &&
            referenceEpochMs == other.referenceEpochMs &&
            accumulatedMs == other.accumulatedMs &&
            startedAtEpochMs == other.startedAtEpochMs &&
            targetMs == other.targetMs;
  }

  @override
  int get hashCode {
    return Object.hash(
      kind,
      label,
      displayMode,
      runState,
      referenceEpochMs,
      accumulatedMs,
      startedAtEpochMs,
      targetMs,
    );
  }
}

T _enumFromJson<T extends Enum>(Object? value, List<T> values, T fallback) {
  if (value is String) {
    for (final enumValue in values) {
      if (enumValue.name == value) {
        return enumValue;
      }
    }
  }
  return fallback;
}

T? _nullableEnumFromJson<T extends Enum>(Object? value, List<T> values) {
  if (value is String) {
    for (final enumValue in values) {
      if (enumValue.name == value) {
        return enumValue;
      }
    }
  }
  return null;
}

List<T> _enumListFromJson<T extends Enum>(Object? value, List<T> values) {
  if (value is! List) {
    return const [];
  }
  return [
    for (final item in value)
      if (item is String)
        for (final enumValue in values)
          if (enumValue.name == item) enumValue,
  ];
}

WatchTimerProjection? _timerFromJson(Object? value) {
  if (value is! Map) {
    return null;
  }
  return WatchTimerProjection.fromJson(Map<String, Object?>.from(value));
}

List<WatchTimerProjection> _timerListFromJson(Object? value) {
  if (value is! List) {
    return const [];
  }
  return [
    for (final item in value)
      if (item is Map)
        WatchTimerProjection.fromJson(Map<String, Object?>.from(item)),
  ];
}

String _stringFromJson(Object? value) {
  return value is String ? value : '';
}

String? _nullableStringFromJson(Object? value) {
  return value is String ? value : null;
}

List<String> _stringListFromJson(Object? value) {
  if (value is! List) {
    return const [];
  }
  return value.whereType<String>().toList(growable: false);
}

int _intFromJson(Object? value, int fallback) {
  return value is int ? value : fallback;
}

int? _nullableIntFromJson(Object? value) {
  return value is int ? value : null;
}

double? _nullableDoubleFromJson(Object? value) {
  return switch (value) {
    double() => value,
    int() => value.toDouble(),
    _ => null,
  };
}

bool _boolFromJson(Object? value, bool fallback) {
  return value is bool ? value : fallback;
}

bool _listEquals<T>(List<T> left, List<T> right) {
  if (identical(left, right)) {
    return true;
  }
  if (left.length != right.length) {
    return false;
  }
  for (var index = 0; index < left.length; index += 1) {
    if (left[index] != right[index]) {
      return false;
    }
  }
  return true;
}
