/// Shared repository value objects used by persistence adapters.
///
/// These types are deliberately small and database-agnostic. They live in the
/// core package so every persistence adapter can use the same owner, revision,
/// and paging contracts without importing a storage implementation.
library;

/// Stable owner scope for repository operations.
class OwnerId {
  OwnerId(String value) : value = _requiredTrimmed(value, 'ownerId');

  /// Raw owner identifier.
  final String value;

  @override
  String toString() => value;

  @override
  bool operator ==(Object other) {
    return other is OwnerId && other.value == value;
  }

  @override
  int get hashCode => value.hashCode;
}

/// Optimistic concurrency revision for mutable repository records.
class Revision implements Comparable<Revision> {
  const Revision(this.value) : assert(value >= 0);

  /// First revision assigned to a newly inserted record.
  static const initial = Revision(1);

  /// Numeric revision value.
  final int value;

  /// Return the next monotonically increasing revision.
  Revision next() => Revision(value + 1);

  @override
  int compareTo(Revision other) => value.compareTo(other.value);

  @override
  String toString() => value.toString();

  @override
  bool operator ==(Object other) {
    return other is Revision && other.value == value;
  }

  @override
  int get hashCode => value.hashCode;
}

/// Cursor contract for bounded repository queries.
class PageRequest {
  const PageRequest({
    this.cursor,
    this.limit = 100,
  }) : assert(limit > 0);

  /// Adapter-neutral cursor. Implementations choose the cursor format.
  final String? cursor;

  /// Maximum number of records to return.
  final int limit;
}

/// One bounded page of repository query results.
class Page<T> {
  Page({
    required Iterable<T> items,
    this.nextCursor,
  }) : items = List<T>.unmodifiable(items);

  /// Items returned for this page.
  final List<T> items;

  /// Cursor for the next page, or null when this is the last page.
  final String? nextCursor;

  /// Whether another page is available.
  bool get hasMore => nextCursor != null;
}

String _requiredTrimmed(String value, String name) {
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    throw ArgumentError.value(value, name, 'Value must not be blank.');
  }
  return trimmed;
}
