import 'package:flutter/foundation.dart'; import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; /// Persisted state for sync queue and cursor progression. @immutable class SyncState { const SyncState({ this.cursor, required this.pendingChanges, this.lastSyncAt, this.lastAttemptAt, this.lastError, this.lastRejected = const [], }); factory SyncState.fromJson(Map json) { return SyncState( cursor: json['cursor'] as String?, pendingChanges: (json['pendingChanges'] as List? ?? []) .map( (dynamic item) => ChangeEnvelope.fromJson(item as Map), ) .toList(growable: false), lastSyncAt: _tryParseDate(json['lastSyncAt'] as String?), lastAttemptAt: _tryParseDate(json['lastAttemptAt'] as String?), lastError: json['lastError'] as String?, lastRejected: (json['lastRejected'] as List? ?? []) .map( (dynamic item) => MutationRejection.fromJson(item as Map), ) .toList(growable: false), ); } final String? cursor; final List pendingChanges; final DateTime? lastSyncAt; final DateTime? lastAttemptAt; final String? lastError; final List lastRejected; static const Object _unset = Object(); static const SyncState empty = SyncState( pendingChanges: [], lastRejected: [], ); SyncState copyWith({ Object? cursor = _unset, List? pendingChanges, Object? lastSyncAt = _unset, Object? lastAttemptAt = _unset, Object? lastError = _unset, List? lastRejected, }) { return SyncState( cursor: identical(cursor, _unset) ? this.cursor : cursor as String?, pendingChanges: pendingChanges ?? this.pendingChanges, lastSyncAt: identical(lastSyncAt, _unset) ? this.lastSyncAt : lastSyncAt as DateTime?, lastAttemptAt: identical(lastAttemptAt, _unset) ? this.lastAttemptAt : lastAttemptAt as DateTime?, lastError: identical(lastError, _unset) ? this.lastError : lastError as String?, lastRejected: lastRejected ?? this.lastRejected, ); } Map toJson() { return { 'cursor': cursor, 'pendingChanges': pendingChanges .map((ChangeEnvelope change) => change.toJson()) .toList(growable: false), 'lastSyncAt': lastSyncAt?.toUtc().toIso8601String(), 'lastAttemptAt': lastAttemptAt?.toUtc().toIso8601String(), 'lastError': lastError, 'lastRejected': lastRejected .map((MutationRejection rejection) => rejection.toJson()) .toList(growable: false), }; } static DateTime? _tryParseDate(String? raw) { if (raw == null || raw.isEmpty) { return null; } final DateTime? parsed = DateTime.tryParse(raw); return parsed?.toLocal(); } }