/// Contract for bidirectional mapping between a local domain model and a
/// remote data transfer object (DTO).
///
/// [TLocal] is the domain type used inside the app (e.g. `InventoryItem`).
/// [TRemote] is the wire/DTO type returned by the remote API
/// (e.g. a `Map<String, dynamic>` decoded from JSON, or a typed DTO class).
///
/// Implementations live in the data layer of each feature package and keep
/// all serialisation/deserialisation logic out of the domain layer.
abstract class DataMapper<TLocal, TRemote> {
  /// Converts a [remote] DTO into a local domain object.
  ///
  /// Throws a [DataMappingException] if [remote] is missing required fields
  /// or contains values that cannot be mapped to the domain model.
  TLocal fromRemote(TRemote remote);

  /// Converts a [local] domain object into a remote DTO suitable for sending
  /// to the API (e.g. as a JSON request body).
  TRemote toRemote(TLocal local);

  /// Converts a list of [remote] DTOs into a list of local domain objects.
  ///
  /// Delegates to [fromRemote] for each element. Implementations may override
  /// this for batch-optimised mapping.
  List<TLocal> fromRemoteList(List<TRemote> remotes) => remotes.map(fromRemote).toList();

  /// Converts a list of [local] domain objects into a list of remote DTOs.
  ///
  /// Delegates to [toRemote] for each element. Implementations may override
  /// this for batch-optimised mapping.
  List<TRemote> toRemoteList(List<TLocal> locals) => locals.map(toRemote).toList();
}

/// Thrown when a [DataMapper] cannot convert a remote DTO to a local model.
///
/// Carries the [field] that caused the problem and a human-readable [message].
class DataMappingException implements Exception {
  /// The field or path within the remote DTO that caused the mapping failure.
  final String field;

  /// Human-readable description of the mapping failure.
  final String message;

  /// The raw value that could not be mapped, if available.
  final Object? rawValue;

  const DataMappingException({required this.field, required this.message, this.rawValue});

  @override
  String toString() =>
      'DataMappingException(field: $field): $message'
      '${rawValue != null ? ' (raw: $rawValue)' : ''}';
}
