/// Domain-only repository contracts for scheduler persistence adapters.
///
/// This package defines the stable boundary that SQLite, memory, and future
/// adapters must implement. Interfaces accept and return domain objects from
/// `scheduler_core`; storage rows, Drift classes, SQL clients, and platform
/// details stay behind adapter packages.
library;

import 'package:scheduler_core/scheduler_core.dart' as core;

/// Adapter-neutral repository failure.
sealed class RepositoryFailure {
  const RepositoryFailure({
    this.entityId,
    this.expectedRevision,
    this.actualRevision,
    this.message,
  });

  /// Stable id of the entity related to the failure, when available.
  final String? entityId;

  /// Revision the caller expected for a compare-and-set operation.
  final core.Revision? expectedRevision;

  /// Revision currently stored by the adapter.
  final core.Revision? actualRevision;

  /// Optional non-localized diagnostic text for logs and tests.
  final String? message;
}

/// The requested entity was not found.
final class RepositoryNotFound extends RepositoryFailure {
  /// Creates a not-found failure for [entityId].
  const RepositoryNotFound({super.entityId, super.message});
}

/// A record with the requested id already exists.
final class RepositoryDuplicateId extends RepositoryFailure {
  /// Creates a duplicate-id failure for [entityId].
  const RepositoryDuplicateId({super.entityId, super.message});
}

/// The caller supplied an invalid revision value.
final class RepositoryInvalidRevision extends RepositoryFailure {
  /// Creates an invalid-revision failure.
  const RepositoryInvalidRevision({
    super.entityId,
    super.expectedRevision,
    super.message,
  });
}

/// The caller attempted to write using a stale revision.
final class RepositoryStaleRevision extends RepositoryFailure {
  /// Creates a stale-revision failure.
  const RepositoryStaleRevision({
    super.entityId,
    required super.expectedRevision,
    required super.actualRevision,
    super.message,
  });
}

/// The requested record exists under a different owner scope.
final class RepositoryOwnerMismatch extends RepositoryFailure {
  /// Creates an owner-mismatch failure for [entityId].
  const RepositoryOwnerMismatch({super.entityId, super.message});
}

/// The adapter could not complete the operation due to storage failure.
final class RepositoryStorageFailure extends RepositoryFailure {
  /// Creates a storage failure with optional [message].
  const RepositoryStorageFailure({super.entityId, super.message});
}

/// Minimal Either type for expected repository success/failure paths.
sealed class Either<L, R> {
  const Either();

  /// Whether this value is a left/failure value.
  bool get isLeft => this is Left<L, R>;

  /// Whether this value is a right/success value.
  bool get isRight => this is Right<L, R>;

  /// Return the left value or throw when this is right.
  L get left => switch (this) {
        Left<L, R>(:final value) => value,
        Right<L, R>() => throw StateError('Either does not contain left.'),
      };

  /// Return the right value or throw when this is left.
  R get right => switch (this) {
        Left<L, R>() => throw StateError('Either does not contain right.'),
        Right<L, R>(:final value) => value,
      };
}

/// Failure side of [Either].
final class Left<L, R> extends Either<L, R> {
  /// Creates a left/failure value.
  const Left(this.value);

  /// Wrapped failure value.
  final L value;
}

/// Success side of [Either].
final class Right<L, R> extends Either<L, R> {
  /// Creates a right/success value.
  const Right(this.value);

  /// Wrapped success value.
  final R value;
}

/// Repository result wrapper for expected adapter outcomes.
typedef RepoResult<T> = Either<RepositoryFailure, T>;

/// Repository contract for task records.
abstract interface class TaskRepository {
  /// Return the task with [taskId] in [ownerId], or null when it is absent.
  Future<RepoResult<core.Task?>> findById({
    required core.OwnerId ownerId,
    required String taskId,
  });

  /// Return owner-scoped tasks in deterministic adapter order.
  Future<RepoResult<core.Page<core.Task>>> findByOwner({
    required core.OwnerId ownerId,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Return owner-scoped tasks assigned to [projectId].
  Future<RepoResult<core.Page<core.Task>>> findByProjectId({
    required core.OwnerId ownerId,
    required String projectId,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Return owner-scoped direct child tasks for [parentTaskId].
  Future<RepoResult<core.Page<core.Task>>> findByParentTaskId({
    required core.OwnerId ownerId,
    required String parentTaskId,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Return owner-scoped tasks with [status].
  Future<RepoResult<core.Page<core.Task>>> findByStatus({
    required core.OwnerId ownerId,
    required core.TaskStatus status,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Insert [task] for [ownerId] and return the assigned revision.
  Future<RepoResult<core.Revision>> insert({
    required core.OwnerId ownerId,
    required core.Task task,
  });

  /// Save [task] if [expectedRevision] still matches the stored record.
  Future<RepoResult<core.Revision>> save({
    required core.OwnerId ownerId,
    required core.Task task,
    required core.Revision expectedRevision,
  });

  /// Remove [taskId] if [expectedRevision] still matches the stored record.
  Future<RepoResult<core.Revision>> delete({
    required core.OwnerId ownerId,
    required String taskId,
    required core.Revision expectedRevision,
  });
}

/// Repository contract for project profile records.
abstract interface class ProjectRepository {
  /// Return the project with [projectId] in [ownerId], or null when absent.
  Future<RepoResult<core.ProjectProfile?>> findById({
    required core.OwnerId ownerId,
    required String projectId,
  });

  /// Return owner-scoped projects, optionally including archived profiles.
  Future<RepoResult<core.Page<core.ProjectProfile>>> findByOwner({
    required core.OwnerId ownerId,
    bool includeArchived = false,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Insert [project] for [ownerId] and return the assigned revision.
  Future<RepoResult<core.Revision>> insert({
    required core.OwnerId ownerId,
    required core.ProjectProfile project,
  });

  /// Save [project] if [expectedRevision] still matches the stored record.
  Future<RepoResult<core.Revision>> save({
    required core.OwnerId ownerId,
    required core.ProjectProfile project,
    required core.Revision expectedRevision,
  });

  /// Archive [projectId] without deleting task history.
  Future<RepoResult<core.Revision>> archive({
    required core.OwnerId ownerId,
    required String projectId,
    required core.Revision expectedRevision,
    required DateTime archivedAtUtc,
  });
}

/// Repository contract for locked blocks and date-scoped overrides.
abstract interface class LockedBlockRepository {
  /// Return the locked block with [blockId] in [ownerId], or null when absent.
  Future<RepoResult<core.LockedBlock?>> findBlockById({
    required core.OwnerId ownerId,
    required String blockId,
  });

  /// Return owner-scoped locked blocks.
  Future<RepoResult<core.Page<core.LockedBlock>>> findBlocksByOwner({
    required core.OwnerId ownerId,
    bool includeArchived = false,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Return the override with [overrideId] in [ownerId], or null when absent.
  Future<RepoResult<core.LockedBlockOverride?>> findOverrideById({
    required core.OwnerId ownerId,
    required String overrideId,
  });

  /// Return owner-scoped overrides for [date].
  Future<RepoResult<core.Page<core.LockedBlockOverride>>> findOverridesForDate({
    required core.OwnerId ownerId,
    required core.CivilDate date,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Insert [block] for [ownerId] and return the assigned revision.
  Future<RepoResult<core.Revision>> insertBlock({
    required core.OwnerId ownerId,
    required core.LockedBlock block,
  });

  /// Save [block] if [expectedRevision] still matches the stored record.
  Future<RepoResult<core.Revision>> saveBlock({
    required core.OwnerId ownerId,
    required core.LockedBlock block,
    required core.Revision expectedRevision,
  });

  /// Archive [blockId] without deleting historical schedule context.
  Future<RepoResult<core.Revision>> archiveBlock({
    required core.OwnerId ownerId,
    required String blockId,
    required core.Revision expectedRevision,
    required DateTime archivedAtUtc,
  });

  /// Insert [override] for [ownerId] and return the assigned revision.
  Future<RepoResult<core.Revision>> insertOverride({
    required core.OwnerId ownerId,
    required core.LockedBlockOverride override,
  });

  /// Save [override] if [expectedRevision] still matches the stored record.
  Future<RepoResult<core.Revision>> saveOverride({
    required core.OwnerId ownerId,
    required core.LockedBlockOverride override,
    required core.Revision expectedRevision,
  });
}

/// Repository contract for owner settings.
abstract interface class SettingsRepository {
  /// Return settings for [ownerId], or null when no settings have been saved.
  Future<RepoResult<core.OwnerSettings?>> findByOwner({
    required core.OwnerId ownerId,
  });

  /// Insert [settings] for [ownerId] and return the assigned revision.
  Future<RepoResult<core.Revision>> insert({
    required core.OwnerId ownerId,
    required core.OwnerSettings settings,
  });

  /// Save [settings] if [expectedRevision] still matches the stored record.
  Future<RepoResult<core.Revision>> save({
    required core.OwnerId ownerId,
    required core.OwnerSettings settings,
    required core.Revision expectedRevision,
  });
}

/// Repository contract for diagnostic schedule snapshots.
abstract interface class ScheduleSnapshotRepository {
  /// Return the snapshot with [snapshotId] in [ownerId], or null when absent.
  Future<RepoResult<core.SchedulingStateSnapshot?>> findById({
    required core.OwnerId ownerId,
    required String snapshotId,
  });

  /// Return snapshots captured for [ownerId] that overlap [window].
  Future<RepoResult<core.Page<core.SchedulingStateSnapshot>>> findInWindow({
    required core.OwnerId ownerId,
    required core.SchedulingWindow window,
    core.PageRequest page = const core.PageRequest(),
  });

  /// Insert [snapshot] for [ownerId] and return the assigned revision.
  Future<RepoResult<core.Revision>> insert({
    required core.OwnerId ownerId,
    required core.SchedulingStateSnapshot snapshot,
  });

  /// Delete snapshots whose retention expiry is before [nowUtc].
  Future<RepoResult<int>> deleteExpired({
    required core.OwnerId ownerId,
    required DateTime nowUtc,
  });
}
