/// The sync state of a locally-tracked record.
enum ChangeState {
  /// The record matches the last known remote state — no action needed.
  synced,

  /// The record was created locally and has not yet been pushed to the remote.
  created,

  /// The record was modified locally since the last successful sync.
  modified,

  /// The record was deleted locally and the deletion has not yet been applied
  /// on the remote.
  deleted,

  /// A sync attempt failed; the record needs to be retried.
  syncError,
}

/// Tracks the local modification state of a single record pending sync.
class TrackedChange<T> {
  /// Identifier of the record being tracked (matches the domain entity's id).
  final String id;

  /// The current local state of the record, or `null` if [state] is
  /// [ChangeState.deleted].
  final T? value;

  /// The sync state of this record.
  final ChangeState state;

  /// When this change was first recorded locally.
  final DateTime changedAt;

  /// Error message from the last failed sync attempt, if any.
  final String? syncError;

  const TrackedChange({
    required this.id,
    required this.state,
    required this.changedAt,
    this.value,
    this.syncError,
  });

  /// Returns a copy with the given fields replaced.
  TrackedChange<T> copyWith({T? value, ChangeState? state, String? syncError}) {
    return TrackedChange<T>(
      id: id,
      value: value ?? this.value,
      state: state ?? this.state,
      changedAt: changedAt,
      syncError: syncError ?? this.syncError,
    );
  }

  @override
  String toString() => 'TrackedChange(id: $id, state: $state, changedAt: $changedAt)';
}

/// Tracks local modifications to domain records that are pending synchronisation
/// with a remote channel.
///
/// [T] is the domain type being tracked (e.g. `InventoryItem`, `Order`).
///
/// Implementations record which records have been created, modified, or deleted
/// locally so that a sync engine can determine exactly what needs to be pushed
/// to the remote on the next sync pass.
abstract class ChangeTracker<T> {
  /// Records that [record] with [id] was created locally.
  Future<void> trackCreated(String id, T record);

  /// Records that [record] with [id] was modified locally.
  Future<void> trackModified(String id, T record);

  /// Records that the record with [id] was deleted locally.
  Future<void> trackDeleted(String id);

  /// Marks the record with [id] as successfully synced (removes it from the
  /// pending-changes set).
  Future<void> markSynced(String id);

  /// Records a sync error for [id] with the given [errorMessage].
  Future<void> markSyncError(String id, String errorMessage);

  /// Returns all records that have pending local changes (created, modified,
  /// deleted, or errored).
  Future<List<TrackedChange<T>>> pendingChanges();

  /// Returns the [TrackedChange] for [id], or `null` if the record is synced
  /// or not tracked.
  Future<TrackedChange<T>?> getChange(String id);

  /// Returns `true` if there are any pending local changes.
  Future<bool> get hasPendingChanges;

  /// Clears all tracked changes (e.g. after a full re-sync from the remote).
  Future<void> clear();
}

/// An in-memory [ChangeTracker] suitable for testing and development.
class InMemoryChangeTracker<T> implements ChangeTracker<T> {
  final Map<String, TrackedChange<T>> _changes = {};

  @override
  Future<void> trackCreated(String id, T record) async {
    _changes[id] = TrackedChange<T>(
      id: id,
      value: record,
      state: ChangeState.created,
      changedAt: DateTime.now(),
    );
  }

  @override
  Future<void> trackModified(String id, T record) async {
    // Preserve the original changedAt if already tracked as created.
    final existing = _changes[id];
    _changes[id] = TrackedChange<T>(
      id: id,
      value: record,
      state: existing?.state == ChangeState.created ? ChangeState.created : ChangeState.modified,
      changedAt: existing?.changedAt ?? DateTime.now(),
    );
  }

  @override
  Future<void> trackDeleted(String id) async {
    final existing = _changes[id];
    _changes[id] = TrackedChange<T>(
      id: id,
      value: null,
      state: ChangeState.deleted,
      changedAt: existing?.changedAt ?? DateTime.now(),
    );
  }

  @override
  Future<void> markSynced(String id) async {
    _changes.remove(id);
  }

  @override
  Future<void> markSyncError(String id, String errorMessage) async {
    final existing = _changes[id];
    if (existing != null) {
      _changes[id] = existing.copyWith(state: ChangeState.syncError, syncError: errorMessage);
    }
  }

  @override
  Future<List<TrackedChange<T>>> pendingChanges() async {
    return _changes.values.where((c) => c.state != ChangeState.synced).toList()
      ..sort((a, b) => a.changedAt.compareTo(b.changedAt));
  }

  @override
  Future<TrackedChange<T>?> getChange(String id) async => _changes[id];

  @override
  Future<bool> get hasPendingChanges async => _changes.isNotEmpty;

  @override
  Future<void> clear() async => _changes.clear();
}
