// Application orchestration contracts for UI-facing use cases.
//
// This layer sits above the pure domain services and repository interfaces. It
// gives future Flutter/use-case code one atomic boundary per user command
// without importing widgets, MongoDB clients, or platform notification APIs.

import 'backlog.dart';
import 'locked_time.dart';
import 'models.dart';
import 'project_statistics.dart';
import 'repositories.dart';
import 'scheduling_engine.dart';
import 'task_lifecycle.dart';
import 'time_contracts.dart';

/// Owner-local time context supplied to application operations.
class OwnerTimeZoneContext {
  OwnerTimeZoneContext({
    required String ownerId,
    required String timeZoneId,
  })  : ownerId = _requiredTrimmed(ownerId, 'ownerId'),
        timeZoneId = _requiredTrimmed(timeZoneId, 'timeZoneId');

  /// Authorization-neutral owner scope. V1 does not implement accounts.
  final String ownerId;

  /// IANA-style time-zone identifier chosen by the app boundary.
  final String timeZoneId;
}

/// Per-operation application context.
class ApplicationOperationContext {
  ApplicationOperationContext({
    required String operationId,
    required this.now,
    required this.ownerTimeZone,
    required this.clock,
    required this.idGenerator,
  }) : operationId = _requiredTrimmed(operationId, 'operationId');

  /// Convenience constructor that captures [Clock.now] once.
  factory ApplicationOperationContext.start({
    required String operationId,
    required OwnerTimeZoneContext ownerTimeZone,
    required Clock clock,
    required IdGenerator idGenerator,
  }) {
    return ApplicationOperationContext(
      operationId: operationId,
      now: clock.now(),
      ownerTimeZone: ownerTimeZone,
      clock: clock,
      idGenerator: idGenerator,
    );
  }

  /// Exactly-once key for this user command.
  final String operationId;

  /// Instant used by the operation. Use this instead of repeatedly reading the
  /// clock during one command.
  final DateTime now;

  /// Owner scope and time-zone data for date-sensitive rules.
  final OwnerTimeZoneContext ownerTimeZone;

  /// Injected clock available to lower-level services that need one.
  final Clock clock;

  /// Injected ID generator for records created by the operation.
  final IdGenerator idGenerator;
}

/// Stable application-layer result categories.
enum ApplicationFailureCode {
  validation,
  notFound,
  conflict,
  staleRevision,
  noSlot,
  duplicateOperation,
  persistenceFailure,
  unexpected,
}

/// Typed failure returned by application commands and queries.
class ApplicationFailure {
  const ApplicationFailure({
    required this.code,
    this.entityId,
    this.detailCode,
  });

  /// Stable machine-readable category.
  final ApplicationFailureCode code;

  /// Optional stable entity id related to the failure.
  final String? entityId;

  /// Optional narrower stable reason, such as a domain validation code name.
  final String? detailCode;
}

/// Result wrapper for expected application success/failure paths.
class ApplicationResult<T> {
  const ApplicationResult._({
    this.value,
    this.failure,
  });

  /// Successful result carrying [value].
  factory ApplicationResult.success(T value) {
    return ApplicationResult._(value: value);
  }

  /// Expected failure result.
  factory ApplicationResult.failure(ApplicationFailure failure) {
    return ApplicationResult._(failure: failure);
  }

  /// Successful value, if any.
  final T? value;

  /// Typed failure, if the operation did not succeed.
  final ApplicationFailure? failure;

  /// Whether the operation succeeded.
  bool get isSuccess => failure == null;

  /// Whether the operation failed with a typed application failure.
  bool get isFailure => failure != null;

  /// Return the value or throw if this result is a failure.
  T get requireValue {
    if (failure != null) {
      throw StateError(
          'Application result is a failure: ${failure!.code.name}');
    }
    return value as T;
  }
}

/// Exception for expected failures raised inside a unit-of-work callback.
class ApplicationFailureException implements Exception {
  const ApplicationFailureException(this.failure);

  /// Failure to return from the application boundary.
  final ApplicationFailure failure;
}

/// Exception representing persistence/driver failures at repository boundaries.
class ApplicationPersistenceException implements Exception {
  const ApplicationPersistenceException();
}

/// Persisted exactly-once operation record.
class ApplicationOperationRecord {
  ApplicationOperationRecord({
    required String operationId,
    required String ownerId,
    required String operationName,
    required this.committedAt,
  })  : operationId = _requiredTrimmed(operationId, 'operationId'),
        ownerId = _requiredTrimmed(ownerId, 'ownerId'),
        operationName = _requiredTrimmed(operationName, 'operationName');

  /// Exactly-once operation id.
  final String operationId;

  /// Authorization-neutral owner scope.
  final String ownerId;

  /// Stable use-case/command label.
  final String operationName;

  /// Commit instant.
  final DateTime committedAt;
}

/// Owner-scoped acknowledgement for a one-time scheduling notice.
class NoticeAcknowledgementRecord {
  NoticeAcknowledgementRecord({
    required String noticeId,
    required String ownerId,
    required this.acknowledgedAt,
  })  : noticeId = _requiredTrimmed(noticeId, 'noticeId'),
        ownerId = _requiredTrimmed(ownerId, 'ownerId');

  /// Stable notice id generated by the read/use-case boundary.
  final String noticeId;

  /// Authorization-neutral owner scope.
  final String ownerId;

  /// Instant the notice was consumed.
  final DateTime acknowledgedAt;
}

/// Owner-level settings needed by application use cases.
class OwnerSettings {
  OwnerSettings({
    required String ownerId,
    required String timeZoneId,
    WallTime? dayStart,
    WallTime? dayEnd,
    this.compactModeEnabled = false,
    this.backlogStalenessSettings = const BacklogStalenessSettings(),
  })  : ownerId = _requiredTrimmed(ownerId, 'ownerId'),
        timeZoneId = _requiredTrimmed(timeZoneId, 'timeZoneId'),
        dayStart = dayStart ?? WallTime(hour: 0, minute: 0),
        dayEnd = dayEnd ?? WallTime(hour: 23, minute: 59) {
    if (this.dayStart.compareTo(this.dayEnd) >= 0) {
      throw ArgumentError.value(
        {'dayStart': this.dayStart, 'dayEnd': this.dayEnd},
        'dayEnd',
        'Day end must be after day start.',
      );
    }
  }

  /// Authorization-neutral owner scope.
  final String ownerId;

  /// Owner time-zone id used by date-sensitive application use cases.
  final String timeZoneId;

  /// Default local day start.
  final WallTime dayStart;

  /// Default local day end.
  final WallTime dayEnd;

  /// Persisted compact-mode preference.
  final bool compactModeEnabled;

  /// Configurable backlog age thresholds.
  final BacklogStalenessSettings backlogStalenessSettings;

  /// Return a copy with selected settings changed.
  OwnerSettings copyWith({
    String? ownerId,
    String? timeZoneId,
    WallTime? dayStart,
    WallTime? dayEnd,
    bool? compactModeEnabled,
    BacklogStalenessSettings? backlogStalenessSettings,
  }) {
    return OwnerSettings(
      ownerId: ownerId ?? this.ownerId,
      timeZoneId: timeZoneId ?? this.timeZoneId,
      dayStart: dayStart ?? this.dayStart,
      dayEnd: dayEnd ?? this.dayEnd,
      compactModeEnabled: compactModeEnabled ?? this.compactModeEnabled,
      backlogStalenessSettings:
          backlogStalenessSettings ?? this.backlogStalenessSettings,
    );
  }
}

/// Repository contract for internal task activities.
abstract interface class TaskActivityRepository {
  Future<TaskActivity?> findById(String id);

  Future<List<TaskActivity>> findByOperationId(String operationId);

  Future<List<TaskActivity>> findByTaskId(String taskId);

  Future<List<TaskActivity>> findByProjectId(String projectId);

  Future<List<TaskActivity>> findAll();

  Future<RepositoryPage<TaskActivity>> findByOwner({
    required String ownerId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  });

  Future<void> save(TaskActivity activity);

  Future<void> saveAll(Iterable<TaskActivity> activities);

  Future<RepositoryMutationResult> append({
    required TaskActivity activity,
    required String ownerId,
  });
}

/// Repository contract for project statistics aggregates.
abstract interface class ProjectStatisticsRepository {
  Future<ProjectStatistics?> findByProjectId(String projectId);

  Future<RepositoryRecord<ProjectStatistics>?> findRecordByProjectId(
    String projectId,
  );

  Future<List<ProjectStatistics>> findAll();

  Future<void> save(ProjectStatistics statistics);

  Future<RepositoryMutationResult> saveIfRevision({
    required ProjectStatistics statistics,
    required String ownerId,
    required int expectedRevision,
  });
}

/// Repository contract for owner settings.
abstract interface class OwnerSettingsRepository {
  Future<OwnerSettings?> findByOwnerId(String ownerId);

  Future<RepositoryRecord<OwnerSettings>?> findRecordByOwnerId(String ownerId);

  Future<void> save(OwnerSettings settings);

  Future<RepositoryMutationResult> saveIfRevision({
    required OwnerSettings settings,
    required int expectedRevision,
  });
}

/// Repository contract for consumed one-time notices.
abstract interface class NoticeAcknowledgementRepository {
  Future<NoticeAcknowledgementRecord?> findById({
    required String ownerId,
    required String noticeId,
  });

  Future<List<NoticeAcknowledgementRecord>> findByOwnerId(String ownerId);

  Future<RepositoryPage<NoticeAcknowledgementRecord>> findByOwner({
    required String ownerId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  });

  Future<void> save(NoticeAcknowledgementRecord record);

  Future<RepositoryMutationResult> insert(
    NoticeAcknowledgementRecord record,
  );
}

/// Repository contract for exactly-once operation records.
abstract interface class ApplicationOperationRepository {
  Future<ApplicationOperationRecord?> findById(String operationId);

  Future<ApplicationOperationRecord?> findByIdempotencyKey({
    required String ownerId,
    required String operationId,
  });

  Future<void> save(ApplicationOperationRecord operation);

  Future<RepositoryMutationResult> insertIfAbsent(
    ApplicationOperationRecord operation,
  );
}

/// Repository set available inside one application unit of work.
abstract interface class ApplicationUnitOfWorkRepositories {
  TaskRepository get tasks;

  ProjectRepository get projects;

  LockedBlockRepository get lockedBlocks;

  SchedulingSnapshotRepository get schedulingSnapshots;

  TaskActivityRepository get taskActivities;

  ProjectStatisticsRepository get projectStatistics;

  OwnerSettingsRepository get ownerSettings;

  NoticeAcknowledgementRepository get noticeAcknowledgements;

  ApplicationOperationRepository get operations;
}

/// Atomic application transaction boundary.
abstract interface class ApplicationUnitOfWork {
  Future<ApplicationResult<T>> run<T>({
    required ApplicationOperationContext context,
    required String operationName,
    required Future<ApplicationResult<T>> Function(
      ApplicationUnitOfWorkRepositories repositories,
    ) action,
  });

  Future<ApplicationResult<T>> read<T>({
    required Future<ApplicationResult<T>> Function(
      ApplicationUnitOfWorkRepositories repositories,
    ) action,
  });
}

/// Repository loader for common scheduler input assembly.
class ApplicationSchedulingLoader {
  const ApplicationSchedulingLoader(this.repositories);

  /// Repositories for the current application operation.
  final ApplicationUnitOfWorkRepositories repositories;

  /// Load scheduler input for [window] from the repository boundary.
  ///
  /// UI code should not persist partial [SchedulingResult] objects. Commands
  /// should load authoritative state here, invoke domain services, then commit
  /// resulting entities through a unit of work.
  Future<SchedulingInput> loadInputForWindow({
    required SchedulingWindow window,
    List<TimeInterval> lockedIntervals = const <TimeInterval>[],
    List<TimeInterval> requiredVisibleIntervals = const <TimeInterval>[],
  }) async {
    final scheduledTasks = await repositories.tasks.findScheduledInWindow(
      window,
    );
    return SchedulingInput(
      tasks: scheduledTasks,
      window: window,
      lockedIntervals: lockedIntervals,
      requiredVisibleIntervals: requiredVisibleIntervals,
    );
  }
}

/// In-memory unit-of-work implementation for deterministic application tests.
class InMemoryApplicationUnitOfWork implements ApplicationUnitOfWork {
  InMemoryApplicationUnitOfWork({
    Iterable<Task> initialTasks = const <Task>[],
    Iterable<ProjectProfile> initialProjects = const <ProjectProfile>[],
    Iterable<LockedBlock> initialLockedBlocks = const <LockedBlock>[],
    Iterable<LockedBlockOverride> initialLockedOverrides =
        const <LockedBlockOverride>[],
    Iterable<SchedulingStateSnapshot> initialSchedulingSnapshots =
        const <SchedulingStateSnapshot>[],
    Iterable<TaskActivity> initialTaskActivities = const <TaskActivity>[],
    Iterable<ProjectStatistics> initialProjectStatistics =
        const <ProjectStatistics>[],
    Iterable<OwnerSettings> initialOwnerSettings = const <OwnerSettings>[],
    Iterable<NoticeAcknowledgementRecord> initialNoticeAcknowledgements =
        const <NoticeAcknowledgementRecord>[],
    Iterable<ApplicationOperationRecord> initialOperations =
        const <ApplicationOperationRecord>[],
  }) : _state = _InMemoryApplicationState(
          tasksById: {
            for (final task in initialTasks) task.id: task,
          },
          taskOwnersById: {
            for (final task in initialTasks) task.id: 'owner-1',
          },
          taskRevisionsById: {
            for (final task in initialTasks) task.id: 1,
          },
          projectsById: {
            for (final project in initialProjects) project.id: project,
          },
          projectOwnersById: {
            for (final project in initialProjects) project.id: 'owner-1',
          },
          projectRevisionsById: {
            for (final project in initialProjects) project.id: 1,
          },
          lockedBlocksById: {
            for (final block in initialLockedBlocks) block.id: block,
          },
          lockedBlockOwnersById: {
            for (final block in initialLockedBlocks) block.id: 'owner-1',
          },
          lockedBlockRevisionsById: {
            for (final block in initialLockedBlocks) block.id: 1,
          },
          lockedOverridesById: {
            for (final override in initialLockedOverrides)
              override.id: override,
          },
          lockedOverrideOwnersById: {
            for (final override in initialLockedOverrides)
              override.id: 'owner-1',
          },
          lockedOverrideRevisionsById: {
            for (final override in initialLockedOverrides) override.id: 1,
          },
          schedulingSnapshotsById: {
            for (final snapshot in initialSchedulingSnapshots)
              snapshot.id: snapshot,
          },
          taskActivitiesById: {
            for (final activity in initialTaskActivities) activity.id: activity,
          },
          taskActivityOwnersById: {
            for (final activity in initialTaskActivities)
              activity.id: 'owner-1',
          },
          projectStatisticsByProjectId: {
            for (final statistics in initialProjectStatistics)
              statistics.projectId: statistics,
          },
          projectStatisticsOwnersByProjectId: {
            for (final statistics in initialProjectStatistics)
              statistics.projectId: 'owner-1',
          },
          projectStatisticsRevisionsByProjectId: {
            for (final statistics in initialProjectStatistics)
              statistics.projectId: 1,
          },
          ownerSettingsByOwnerId: {
            for (final settings in initialOwnerSettings)
              settings.ownerId: settings,
          },
          ownerSettingsRevisionsByOwnerId: {
            for (final settings in initialOwnerSettings) settings.ownerId: 1,
          },
          noticeAcknowledgementsByScopedId: {
            for (final acknowledgement in initialNoticeAcknowledgements)
              _noticeAcknowledgementKey(
                ownerId: acknowledgement.ownerId,
                noticeId: acknowledgement.noticeId,
              ): acknowledgement,
          },
          noticeAcknowledgementRevisionsByScopedId: {
            for (final acknowledgement in initialNoticeAcknowledgements)
              _noticeAcknowledgementKey(
                ownerId: acknowledgement.ownerId,
                noticeId: acknowledgement.noticeId,
              ): 1,
          },
          operationsById: {
            for (final operation in initialOperations)
              operation.operationId: operation,
          },
        );

  _InMemoryApplicationState _state;
  bool _failNextCommit = false;

  /// Current committed tasks, useful for contract tests.
  List<Task> get currentTasks =>
      List<Task>.unmodifiable(_state.tasksById.values);

  /// Current committed projects, useful for contract tests.
  List<ProjectProfile> get currentProjects {
    return List<ProjectProfile>.unmodifiable(_state.projectsById.values);
  }

  /// Current committed locked blocks, useful for contract tests.
  List<LockedBlock> get currentLockedBlocks {
    return List<LockedBlock>.unmodifiable(_state.lockedBlocksById.values);
  }

  /// Current committed locked overrides, useful for contract tests.
  List<LockedBlockOverride> get currentLockedOverrides {
    return List<LockedBlockOverride>.unmodifiable(
      _state.lockedOverridesById.values,
    );
  }

  /// Current committed scheduling snapshots, useful for contract tests.
  List<SchedulingStateSnapshot> get currentSchedulingSnapshots {
    return List<SchedulingStateSnapshot>.unmodifiable(
      _state.schedulingSnapshotsById.values,
    );
  }

  /// Current committed activities, useful for contract tests.
  List<TaskActivity> get currentTaskActivities {
    return List<TaskActivity>.unmodifiable(_state.taskActivitiesById.values);
  }

  /// Current committed project statistics, useful for contract tests.
  List<ProjectStatistics> get currentProjectStatistics {
    return List<ProjectStatistics>.unmodifiable(
      _state.projectStatisticsByProjectId.values,
    );
  }

  /// Current committed owner settings, useful for contract tests.
  List<OwnerSettings> get currentOwnerSettings {
    return List<OwnerSettings>.unmodifiable(
        _state.ownerSettingsByOwnerId.values);
  }

  /// Current committed notice acknowledgements, useful for contract tests.
  List<NoticeAcknowledgementRecord> get currentNoticeAcknowledgements {
    return List<NoticeAcknowledgementRecord>.unmodifiable(
      _state.noticeAcknowledgementsByScopedId.values,
    );
  }

  /// Current committed operation records, useful for contract tests.
  List<ApplicationOperationRecord> get currentOperations {
    return List<ApplicationOperationRecord>.unmodifiable(
      _state.operationsById.values,
    );
  }

  /// Cause the next successful transaction to fail at commit time.
  void failNextCommitForTesting() {
    _failNextCommit = true;
  }

  @override
  Future<ApplicationResult<T>> run<T>({
    required ApplicationOperationContext context,
    required String operationName,
    required Future<ApplicationResult<T>> Function(
      ApplicationUnitOfWorkRepositories repositories,
    ) action,
  }) async {
    if (_state.operationsById.containsKey(context.operationId)) {
      return ApplicationResult.failure(
        ApplicationFailure(
          code: ApplicationFailureCode.duplicateOperation,
          entityId: context.operationId,
        ),
      );
    }

    final staged = _state.copy();
    final repositories = _InMemoryApplicationRepositories(staged);

    try {
      final result = await action(repositories);
      if (result.isFailure) {
        return result;
      }

      staged.operationsById[context.operationId] = ApplicationOperationRecord(
        operationId: context.operationId,
        ownerId: context.ownerTimeZone.ownerId,
        operationName: operationName,
        committedAt: context.now,
      );

      if (_failNextCommit) {
        _failNextCommit = false;
        throw const ApplicationPersistenceException();
      }

      _state = staged;
      return result;
    } on ApplicationFailureException catch (error) {
      return ApplicationResult.failure(error.failure);
    } on DomainValidationException catch (error) {
      return ApplicationResult.failure(
        ApplicationFailure(
          code: ApplicationFailureCode.validation,
          detailCode: error.code.name,
        ),
      );
    } on ArgumentError catch (error) {
      return ApplicationResult.failure(
        ApplicationFailure(
          code: ApplicationFailureCode.validation,
          detailCode: error.name,
        ),
      );
    } on ApplicationPersistenceException {
      return ApplicationResult.failure(
        const ApplicationFailure(
          code: ApplicationFailureCode.persistenceFailure,
        ),
      );
    } catch (_) {
      return ApplicationResult.failure(
        const ApplicationFailure(code: ApplicationFailureCode.unexpected),
      );
    }
  }

  @override
  Future<ApplicationResult<T>> read<T>({
    required Future<ApplicationResult<T>> Function(
      ApplicationUnitOfWorkRepositories repositories,
    ) action,
  }) async {
    final staged = _state.copy();
    final repositories = _InMemoryApplicationRepositories(staged);

    try {
      return await action(repositories);
    } on ApplicationFailureException catch (error) {
      return ApplicationResult.failure(error.failure);
    } on DomainValidationException catch (error) {
      return ApplicationResult.failure(
        ApplicationFailure(
          code: ApplicationFailureCode.validation,
          detailCode: error.code.name,
        ),
      );
    } on ArgumentError catch (error) {
      return ApplicationResult.failure(
        ApplicationFailure(
          code: ApplicationFailureCode.validation,
          detailCode: error.name,
        ),
      );
    } on ApplicationPersistenceException {
      return ApplicationResult.failure(
        const ApplicationFailure(
          code: ApplicationFailureCode.persistenceFailure,
        ),
      );
    } catch (_) {
      return ApplicationResult.failure(
        const ApplicationFailure(code: ApplicationFailureCode.unexpected),
      );
    }
  }
}

class _InMemoryApplicationState {
  _InMemoryApplicationState({
    required this.tasksById,
    required this.taskOwnersById,
    required this.taskRevisionsById,
    required this.projectsById,
    required this.projectOwnersById,
    required this.projectRevisionsById,
    required this.lockedBlocksById,
    required this.lockedBlockOwnersById,
    required this.lockedBlockRevisionsById,
    required this.lockedOverridesById,
    required this.lockedOverrideOwnersById,
    required this.lockedOverrideRevisionsById,
    required this.schedulingSnapshotsById,
    required this.taskActivitiesById,
    required this.taskActivityOwnersById,
    required this.projectStatisticsByProjectId,
    required this.projectStatisticsOwnersByProjectId,
    required this.projectStatisticsRevisionsByProjectId,
    required this.ownerSettingsByOwnerId,
    required this.ownerSettingsRevisionsByOwnerId,
    required this.noticeAcknowledgementsByScopedId,
    required this.noticeAcknowledgementRevisionsByScopedId,
    required this.operationsById,
  });

  final Map<String, Task> tasksById;
  final Map<String, String> taskOwnersById;
  final Map<String, int> taskRevisionsById;
  final Map<String, ProjectProfile> projectsById;
  final Map<String, String> projectOwnersById;
  final Map<String, int> projectRevisionsById;
  final Map<String, LockedBlock> lockedBlocksById;
  final Map<String, String> lockedBlockOwnersById;
  final Map<String, int> lockedBlockRevisionsById;
  final Map<String, LockedBlockOverride> lockedOverridesById;
  final Map<String, String> lockedOverrideOwnersById;
  final Map<String, int> lockedOverrideRevisionsById;
  final Map<String, SchedulingStateSnapshot> schedulingSnapshotsById;
  final Map<String, TaskActivity> taskActivitiesById;
  final Map<String, String> taskActivityOwnersById;
  final Map<String, ProjectStatistics> projectStatisticsByProjectId;
  final Map<String, String> projectStatisticsOwnersByProjectId;
  final Map<String, int> projectStatisticsRevisionsByProjectId;
  final Map<String, OwnerSettings> ownerSettingsByOwnerId;
  final Map<String, int> ownerSettingsRevisionsByOwnerId;
  final Map<String, NoticeAcknowledgementRecord>
      noticeAcknowledgementsByScopedId;
  final Map<String, int> noticeAcknowledgementRevisionsByScopedId;
  final Map<String, ApplicationOperationRecord> operationsById;

  _InMemoryApplicationState copy() {
    return _InMemoryApplicationState(
      tasksById: Map<String, Task>.of(tasksById),
      taskOwnersById: Map<String, String>.of(taskOwnersById),
      taskRevisionsById: Map<String, int>.of(taskRevisionsById),
      projectsById: Map<String, ProjectProfile>.of(projectsById),
      projectOwnersById: Map<String, String>.of(projectOwnersById),
      projectRevisionsById: Map<String, int>.of(projectRevisionsById),
      lockedBlocksById: Map<String, LockedBlock>.of(lockedBlocksById),
      lockedBlockOwnersById: Map<String, String>.of(lockedBlockOwnersById),
      lockedBlockRevisionsById: Map<String, int>.of(
        lockedBlockRevisionsById,
      ),
      lockedOverridesById:
          Map<String, LockedBlockOverride>.of(lockedOverridesById),
      lockedOverrideOwnersById: Map<String, String>.of(
        lockedOverrideOwnersById,
      ),
      lockedOverrideRevisionsById: Map<String, int>.of(
        lockedOverrideRevisionsById,
      ),
      schedulingSnapshotsById:
          Map<String, SchedulingStateSnapshot>.of(schedulingSnapshotsById),
      taskActivitiesById: Map<String, TaskActivity>.of(taskActivitiesById),
      taskActivityOwnersById: Map<String, String>.of(taskActivityOwnersById),
      projectStatisticsByProjectId:
          Map<String, ProjectStatistics>.of(projectStatisticsByProjectId),
      projectStatisticsOwnersByProjectId: Map<String, String>.of(
        projectStatisticsOwnersByProjectId,
      ),
      projectStatisticsRevisionsByProjectId: Map<String, int>.of(
        projectStatisticsRevisionsByProjectId,
      ),
      ownerSettingsByOwnerId: Map<String, OwnerSettings>.of(
        ownerSettingsByOwnerId,
      ),
      ownerSettingsRevisionsByOwnerId: Map<String, int>.of(
        ownerSettingsRevisionsByOwnerId,
      ),
      noticeAcknowledgementsByScopedId:
          Map<String, NoticeAcknowledgementRecord>.of(
        noticeAcknowledgementsByScopedId,
      ),
      noticeAcknowledgementRevisionsByScopedId: Map<String, int>.of(
        noticeAcknowledgementRevisionsByScopedId,
      ),
      operationsById: Map<String, ApplicationOperationRecord>.of(
        operationsById,
      ),
    );
  }
}

class _InMemoryApplicationRepositories
    implements ApplicationUnitOfWorkRepositories {
  _InMemoryApplicationRepositories(_InMemoryApplicationState state)
      : tasks = _UnitOfWorkTaskRepository(
          tasksById: state.tasksById,
          ownersById: state.taskOwnersById,
          revisionsById: state.taskRevisionsById,
        ),
        projects = _UnitOfWorkProjectRepository(
          projectsById: state.projectsById,
          ownersById: state.projectOwnersById,
          revisionsById: state.projectRevisionsById,
        ),
        lockedBlocks = _UnitOfWorkLockedBlockRepository(
          blocksById: state.lockedBlocksById,
          blockOwnersById: state.lockedBlockOwnersById,
          blockRevisionsById: state.lockedBlockRevisionsById,
          overridesById: state.lockedOverridesById,
          overrideOwnersById: state.lockedOverrideOwnersById,
          overrideRevisionsById: state.lockedOverrideRevisionsById,
        ),
        schedulingSnapshots = _UnitOfWorkSchedulingSnapshotRepository(
          state.schedulingSnapshotsById,
        ),
        taskActivities = _UnitOfWorkTaskActivityRepository(
          activitiesById: state.taskActivitiesById,
          ownersById: state.taskActivityOwnersById,
        ),
        projectStatistics = _UnitOfWorkProjectStatisticsRepository(
          statisticsByProjectId: state.projectStatisticsByProjectId,
          ownersByProjectId: state.projectStatisticsOwnersByProjectId,
          revisionsByProjectId: state.projectStatisticsRevisionsByProjectId,
        ),
        ownerSettings = _UnitOfWorkOwnerSettingsRepository(
          settingsByOwnerId: state.ownerSettingsByOwnerId,
          revisionsByOwnerId: state.ownerSettingsRevisionsByOwnerId,
        ),
        noticeAcknowledgements = _UnitOfWorkNoticeAcknowledgementRepository(
          recordsByScopedId: state.noticeAcknowledgementsByScopedId,
          revisionsByScopedId: state.noticeAcknowledgementRevisionsByScopedId,
        ),
        operations = _UnitOfWorkOperationRepository(state.operationsById);

  @override
  final TaskRepository tasks;

  @override
  final ProjectRepository projects;

  @override
  final LockedBlockRepository lockedBlocks;

  @override
  final SchedulingSnapshotRepository schedulingSnapshots;

  @override
  final TaskActivityRepository taskActivities;

  @override
  final ProjectStatisticsRepository projectStatistics;

  @override
  final OwnerSettingsRepository ownerSettings;

  @override
  final NoticeAcknowledgementRepository noticeAcknowledgements;

  @override
  final ApplicationOperationRepository operations;
}

class _UnitOfWorkTaskRepository implements TaskRepository {
  _UnitOfWorkTaskRepository({
    required Map<String, Task> tasksById,
    required Map<String, String> ownersById,
    required Map<String, int> revisionsById,
  })  : _tasksById = tasksById,
        _ownersById = ownersById,
        _revisionsById = revisionsById;

  final Map<String, Task> _tasksById;
  final Map<String, String> _ownersById;
  final Map<String, int> _revisionsById;

  @override
  Future<Task?> findById(String id) async => _tasksById[id];

  @override
  Future<RepositoryRecord<Task>?> findRecordById(String id) async {
    final task = _tasksById[id];
    if (task == null) {
      return null;
    }
    return RepositoryRecord(
      value: task,
      ownerId: _ownerFor(id),
      revision: _revisionFor(id),
    );
  }

  @override
  Future<List<Task>> findAll() async {
    return List<Task>.unmodifiable(_tasksById.values);
  }

  @override
  Future<List<Task>> findByStatus(TaskStatus status) async {
    return List<Task>.unmodifiable(
      _tasksById.values.where((task) => task.status == status),
    );
  }

  @override
  Future<RepositoryPage<Task>> findByOwner({
    required String ownerId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final tasks = _tasksById.values
        .where((task) => _ownerFor(task.id) == ownerId)
        .toList(growable: false)
      ..sort(_compareTaskIds);
    return _page(tasks, page);
  }

  @override
  Future<RepositoryPage<Task>> findByProjectId({
    required String ownerId,
    required String projectId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final tasks = _tasksById.values
        .where(
          (task) =>
              _ownerFor(task.id) == ownerId && task.projectId == projectId,
        )
        .toList(growable: false)
      ..sort(_compareTaskIds);
    return _page(tasks, page);
  }

  @override
  Future<RepositoryPage<Task>> findByParentTaskId({
    required String ownerId,
    required String parentTaskId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final tasks = _tasksById.values
        .where(
          (task) =>
              _ownerFor(task.id) == ownerId &&
              task.parentTaskId == parentTaskId,
        )
        .toList(growable: false)
      ..sort(_compareTaskIds);
    return _page(tasks, page);
  }

  @override
  Future<RepositoryPage<Task>> findBacklogCandidates(
    BacklogCandidateQuery query,
  ) async {
    final tasks = _tasksById.values
        .where(
          (task) =>
              _ownerFor(task.id) == query.ownerId &&
              task.status == TaskStatus.backlog &&
              (query.projectId == null || task.projectId == query.projectId) &&
              query.tags.every(task.backlogTags.contains),
        )
        .toList(growable: false)
      ..sort(_compareBacklogCandidates);
    return _page(tasks, query.page);
  }

  @override
  Future<List<Task>> findScheduledInWindow(SchedulingWindow window) async {
    return List<Task>.unmodifiable(
      _tasksById.values.where((task) {
        return _taskOverlapsWindow(task, window);
      }),
    );
  }

  @override
  Future<RepositoryPage<Task>> findScheduledInWindowForOwner({
    required String ownerId,
    required SchedulingWindow window,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final tasks = _tasksById.values
        .where(
          (task) =>
              _ownerFor(task.id) == ownerId &&
              _taskOverlapsWindow(task, window),
        )
        .toList(growable: false)
      ..sort(_compareScheduledTasks);
    return _page(tasks, page);
  }

  @override
  Future<RepositoryPage<Task>> findForLocalDay({
    required String ownerId,
    required CivilDate localDate,
    required SchedulingWindow window,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) {
    return findScheduledInWindowForOwner(
      ownerId: ownerId,
      window: window,
      page: page,
    );
  }

  @override
  Future<void> save(Task task) async {
    _tasksById[task.id] = task;
    _ownersById.putIfAbsent(task.id, () => 'owner-1');
    _revisionsById[task.id] = _revisionFor(task.id) + 1;
  }

  @override
  Future<void> saveAll(Iterable<Task> tasks) async {
    for (final task in tasks) {
      await save(task);
    }
  }

  @override
  Future<RepositoryMutationResult> insert({
    required Task task,
    required String ownerId,
  }) async {
    if (_tasksById.containsKey(task.id)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateId,
          entityId: task.id,
        ),
      );
    }
    _tasksById[task.id] = task;
    _ownersById[task.id] = ownerId;
    _revisionsById[task.id] = 1;
    return RepositoryMutationResult.success(revision: 1);
  }

  @override
  Future<RepositoryMutationResult> saveIfRevision({
    required Task task,
    required String ownerId,
    required int expectedRevision,
  }) async {
    if (!_tasksById.containsKey(task.id) || _ownerFor(task.id) != ownerId) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
            code: RepositoryFailureCode.notFound, entityId: task.id),
      );
    }
    final actualRevision = _revisionFor(task.id);
    if (actualRevision != expectedRevision) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.staleRevision,
          entityId: task.id,
          expectedRevision: expectedRevision,
          actualRevision: actualRevision,
        ),
      );
    }
    final nextRevision = actualRevision + 1;
    _tasksById[task.id] = task;
    _ownersById[task.id] = ownerId;
    _revisionsById[task.id] = nextRevision;
    return RepositoryMutationResult.success(revision: nextRevision);
  }

  String _ownerFor(String id) => _ownersById[id] ?? 'owner-1';

  int _revisionFor(String id) => _revisionsById[id] ?? 1;
}

class _UnitOfWorkProjectRepository implements ProjectRepository {
  _UnitOfWorkProjectRepository({
    required Map<String, ProjectProfile> projectsById,
    required Map<String, String> ownersById,
    required Map<String, int> revisionsById,
  })  : _projectsById = projectsById,
        _ownersById = ownersById,
        _revisionsById = revisionsById;

  final Map<String, ProjectProfile> _projectsById;
  final Map<String, String> _ownersById;
  final Map<String, int> _revisionsById;

  @override
  Future<ProjectProfile?> findById(String id) async => _projectsById[id];

  @override
  Future<RepositoryRecord<ProjectProfile>?> findRecordById(String id) async {
    final project = _projectsById[id];
    if (project == null) {
      return null;
    }
    return RepositoryRecord(
      value: project,
      ownerId: _ownerFor(id),
      revision: _revisionFor(id),
      archivedAt: project.archivedAt,
    );
  }

  @override
  Future<List<ProjectProfile>> findAll() async {
    return List<ProjectProfile>.unmodifiable(_projectsById.values);
  }

  @override
  Future<RepositoryPage<ProjectProfile>> findByOwner({
    required String ownerId,
    bool includeArchived = false,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final projects = _projectsById.values
        .where(
          (project) =>
              _ownerFor(project.id) == ownerId &&
              (includeArchived || !project.isArchived),
        )
        .toList(growable: false)
      ..sort(_compareProjects);
    return _page(projects, page);
  }

  @override
  Future<void> save(ProjectProfile project) async {
    _projectsById[project.id] = project;
    _ownersById.putIfAbsent(project.id, () => 'owner-1');
    _revisionsById[project.id] = _revisionFor(project.id) + 1;
  }

  @override
  Future<RepositoryMutationResult> insert({
    required ProjectProfile project,
    required String ownerId,
  }) async {
    if (_projectsById.containsKey(project.id)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateId,
          entityId: project.id,
        ),
      );
    }
    _projectsById[project.id] = project;
    _ownersById[project.id] = ownerId;
    _revisionsById[project.id] = 1;
    return RepositoryMutationResult.success(revision: 1);
  }

  @override
  Future<RepositoryMutationResult> saveIfRevision({
    required ProjectProfile project,
    required String ownerId,
    required int expectedRevision,
  }) async {
    if (!_projectsById.containsKey(project.id) ||
        _ownerFor(project.id) != ownerId) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: project.id,
        ),
      );
    }
    final actualRevision = _revisionFor(project.id);
    if (actualRevision != expectedRevision) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.staleRevision,
          entityId: project.id,
          expectedRevision: expectedRevision,
          actualRevision: actualRevision,
        ),
      );
    }
    final nextRevision = actualRevision + 1;
    _projectsById[project.id] = project;
    _ownersById[project.id] = ownerId;
    _revisionsById[project.id] = nextRevision;
    return RepositoryMutationResult.success(revision: nextRevision);
  }

  @override
  Future<RepositoryMutationResult> archive({
    required String projectId,
    required String ownerId,
    required int expectedRevision,
    required DateTime archivedAt,
  }) async {
    final project = _projectsById[projectId];
    if (project == null) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: projectId,
        ),
      );
    }
    return saveIfRevision(
      project: project.copyWith(archivedAt: archivedAt),
      ownerId: ownerId,
      expectedRevision: expectedRevision,
    );
  }

  String _ownerFor(String id) => _ownersById[id] ?? 'owner-1';

  int _revisionFor(String id) => _revisionsById[id] ?? 1;
}

class _UnitOfWorkLockedBlockRepository implements LockedBlockRepository {
  _UnitOfWorkLockedBlockRepository({
    required Map<String, LockedBlock> blocksById,
    required Map<String, String> blockOwnersById,
    required Map<String, int> blockRevisionsById,
    required Map<String, LockedBlockOverride> overridesById,
    required Map<String, String> overrideOwnersById,
    required Map<String, int> overrideRevisionsById,
  })  : _blocksById = blocksById,
        _blockOwnersById = blockOwnersById,
        _blockRevisionsById = blockRevisionsById,
        _overridesById = overridesById,
        _overrideOwnersById = overrideOwnersById,
        _overrideRevisionsById = overrideRevisionsById;

  final Map<String, LockedBlock> _blocksById;
  final Map<String, String> _blockOwnersById;
  final Map<String, int> _blockRevisionsById;
  final Map<String, LockedBlockOverride> _overridesById;
  final Map<String, String> _overrideOwnersById;
  final Map<String, int> _overrideRevisionsById;

  @override
  Future<LockedBlock?> findBlockById(String id) async => _blocksById[id];

  @override
  Future<RepositoryRecord<LockedBlock>?> findBlockRecordById(String id) async {
    final block = _blocksById[id];
    if (block == null) {
      return null;
    }
    return RepositoryRecord(
      value: block,
      ownerId: _blockOwnerFor(id),
      revision: _blockRevisionFor(id),
      archivedAt: block.archivedAt,
    );
  }

  @override
  Future<List<LockedBlock>> findAllBlocks() async {
    return List<LockedBlock>.unmodifiable(_blocksById.values);
  }

  @override
  Future<RepositoryPage<LockedBlock>> findBlocksByOwner({
    required String ownerId,
    bool includeArchived = false,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final blocks = _blocksById.values
        .where(
          (block) =>
              _blockOwnerFor(block.id) == ownerId &&
              (includeArchived || !block.isArchived),
        )
        .toList(growable: false)
      ..sort(_compareLockedBlocks);
    return _page(blocks, page);
  }

  @override
  Future<LockedBlockOverride?> findOverrideById(String id) async {
    return _overridesById[id];
  }

  @override
  Future<RepositoryRecord<LockedBlockOverride>?> findOverrideRecordById(
    String id,
  ) async {
    final override = _overridesById[id];
    if (override == null) {
      return null;
    }
    return RepositoryRecord(
      value: override,
      ownerId: _overrideOwnerFor(id),
      revision: _overrideRevisionFor(id),
    );
  }

  @override
  Future<List<LockedBlockOverride>> findAllOverrides() async {
    return List<LockedBlockOverride>.unmodifiable(_overridesById.values);
  }

  @override
  Future<RepositoryPage<LockedBlockOverride>> findOverridesForDate({
    required String ownerId,
    required CivilDate date,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final overrides = _overridesById.values
        .where(
          (override) =>
              _overrideOwnerFor(override.id) == ownerId &&
              override.date == date,
        )
        .toList(growable: false)
      ..sort(_compareLockedOverrides);
    return _page(overrides, page);
  }

  @override
  Future<void> saveBlock(LockedBlock block) async {
    _blocksById[block.id] = block;
    _blockOwnersById.putIfAbsent(block.id, () => 'owner-1');
    _blockRevisionsById[block.id] = _blockRevisionFor(block.id) + 1;
  }

  @override
  Future<void> saveOverride(LockedBlockOverride override) async {
    _overridesById[override.id] = override;
    _overrideOwnersById.putIfAbsent(override.id, () => 'owner-1');
    _overrideRevisionsById[override.id] = _overrideRevisionFor(override.id) + 1;
  }

  @override
  Future<RepositoryMutationResult> insertBlock({
    required LockedBlock block,
    required String ownerId,
  }) async {
    if (_blocksById.containsKey(block.id)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateId,
          entityId: block.id,
        ),
      );
    }
    _blocksById[block.id] = block;
    _blockOwnersById[block.id] = ownerId;
    _blockRevisionsById[block.id] = 1;
    return RepositoryMutationResult.success(revision: 1);
  }

  @override
  Future<RepositoryMutationResult> saveBlockIfRevision({
    required LockedBlock block,
    required String ownerId,
    required int expectedRevision,
  }) async {
    if (!_blocksById.containsKey(block.id) ||
        _blockOwnerFor(block.id) != ownerId) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: block.id,
        ),
      );
    }
    final actualRevision = _blockRevisionFor(block.id);
    if (actualRevision != expectedRevision) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.staleRevision,
          entityId: block.id,
          expectedRevision: expectedRevision,
          actualRevision: actualRevision,
        ),
      );
    }
    final nextRevision = actualRevision + 1;
    _blocksById[block.id] = block;
    _blockOwnersById[block.id] = ownerId;
    _blockRevisionsById[block.id] = nextRevision;
    return RepositoryMutationResult.success(revision: nextRevision);
  }

  @override
  Future<RepositoryMutationResult> archiveBlock({
    required String blockId,
    required String ownerId,
    required int expectedRevision,
    required DateTime archivedAt,
  }) async {
    final block = _blocksById[blockId];
    if (block == null) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: blockId,
        ),
      );
    }
    return saveBlockIfRevision(
      block: block.copyWith(updatedAt: archivedAt, archivedAt: archivedAt),
      ownerId: ownerId,
      expectedRevision: expectedRevision,
    );
  }

  @override
  Future<RepositoryMutationResult> insertOverride({
    required LockedBlockOverride override,
    required String ownerId,
  }) async {
    if (_overridesById.containsKey(override.id)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateId,
          entityId: override.id,
        ),
      );
    }
    _overridesById[override.id] = override;
    _overrideOwnersById[override.id] = ownerId;
    _overrideRevisionsById[override.id] = 1;
    return RepositoryMutationResult.success(revision: 1);
  }

  @override
  Future<RepositoryMutationResult> saveOverrideIfRevision({
    required LockedBlockOverride override,
    required String ownerId,
    required int expectedRevision,
  }) async {
    if (!_overridesById.containsKey(override.id) ||
        _overrideOwnerFor(override.id) != ownerId) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: override.id,
        ),
      );
    }
    final actualRevision = _overrideRevisionFor(override.id);
    if (actualRevision != expectedRevision) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.staleRevision,
          entityId: override.id,
          expectedRevision: expectedRevision,
          actualRevision: actualRevision,
        ),
      );
    }
    final nextRevision = actualRevision + 1;
    _overridesById[override.id] = override;
    _overrideOwnersById[override.id] = ownerId;
    _overrideRevisionsById[override.id] = nextRevision;
    return RepositoryMutationResult.success(revision: nextRevision);
  }

  String _blockOwnerFor(String id) => _blockOwnersById[id] ?? 'owner-1';

  int _blockRevisionFor(String id) => _blockRevisionsById[id] ?? 1;

  String _overrideOwnerFor(String id) => _overrideOwnersById[id] ?? 'owner-1';

  int _overrideRevisionFor(String id) => _overrideRevisionsById[id] ?? 1;
}

class _UnitOfWorkSchedulingSnapshotRepository
    implements SchedulingSnapshotRepository {
  _UnitOfWorkSchedulingSnapshotRepository(this._snapshotsById);

  final Map<String, SchedulingStateSnapshot> _snapshotsById;

  @override
  Future<SchedulingStateSnapshot?> findById(String id) async {
    return _snapshotsById[id];
  }

  @override
  Future<List<SchedulingStateSnapshot>> findInWindow(
    SchedulingWindow window,
  ) async {
    return List<SchedulingStateSnapshot>.unmodifiable(
      _snapshotsById.values.where(
        (snapshot) => snapshot.window.interval.overlaps(window.interval),
      ),
    );
  }

  @override
  Future<void> save(SchedulingStateSnapshot snapshot) async {
    _snapshotsById[snapshot.id] = snapshot;
  }
}

class _UnitOfWorkTaskActivityRepository implements TaskActivityRepository {
  _UnitOfWorkTaskActivityRepository({
    required Map<String, TaskActivity> activitiesById,
    required Map<String, String> ownersById,
  })  : _activitiesById = activitiesById,
        _ownersById = ownersById;

  final Map<String, TaskActivity> _activitiesById;
  final Map<String, String> _ownersById;

  @override
  Future<TaskActivity?> findById(String id) async => _activitiesById[id];

  @override
  Future<List<TaskActivity>> findByOperationId(String operationId) async {
    return List<TaskActivity>.unmodifiable(
      _activitiesById.values.where(
        (activity) => activity.operationId == operationId,
      ),
    );
  }

  @override
  Future<List<TaskActivity>> findByTaskId(String taskId) async {
    return List<TaskActivity>.unmodifiable(
      _activitiesById.values.where((activity) => activity.taskId == taskId),
    );
  }

  @override
  Future<List<TaskActivity>> findByProjectId(String projectId) async {
    return List<TaskActivity>.unmodifiable(
      _activitiesById.values.where(
        (activity) => activity.projectId == projectId,
      ),
    );
  }

  @override
  Future<List<TaskActivity>> findAll() async {
    return List<TaskActivity>.unmodifiable(_activitiesById.values);
  }

  @override
  Future<RepositoryPage<TaskActivity>> findByOwner({
    required String ownerId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final activities = _activitiesById.values
        .where((activity) => _ownerFor(activity.id) == ownerId)
        .toList(growable: false)
      ..sort(_compareActivities);
    return _page(activities, page);
  }

  @override
  Future<void> save(TaskActivity activity) async {
    _activitiesById[activity.id] = activity;
    _ownersById.putIfAbsent(activity.id, () => 'owner-1');
  }

  @override
  Future<void> saveAll(Iterable<TaskActivity> activities) async {
    for (final activity in activities) {
      _activitiesById[activity.id] = activity;
      _ownersById.putIfAbsent(activity.id, () => 'owner-1');
    }
  }

  @override
  Future<RepositoryMutationResult> append({
    required TaskActivity activity,
    required String ownerId,
  }) async {
    if (_activitiesById.containsKey(activity.id)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateId,
          entityId: activity.id,
        ),
      );
    }
    _activitiesById[activity.id] = activity;
    _ownersById[activity.id] = ownerId;
    return RepositoryMutationResult.success(revision: 1);
  }

  String _ownerFor(String id) => _ownersById[id] ?? 'owner-1';
}

class _UnitOfWorkProjectStatisticsRepository
    implements ProjectStatisticsRepository {
  _UnitOfWorkProjectStatisticsRepository({
    required Map<String, ProjectStatistics> statisticsByProjectId,
    required Map<String, String> ownersByProjectId,
    required Map<String, int> revisionsByProjectId,
  })  : _statisticsByProjectId = statisticsByProjectId,
        _ownersByProjectId = ownersByProjectId,
        _revisionsByProjectId = revisionsByProjectId;

  final Map<String, ProjectStatistics> _statisticsByProjectId;
  final Map<String, String> _ownersByProjectId;
  final Map<String, int> _revisionsByProjectId;

  @override
  Future<ProjectStatistics?> findByProjectId(String projectId) async {
    return _statisticsByProjectId[projectId];
  }

  @override
  Future<RepositoryRecord<ProjectStatistics>?> findRecordByProjectId(
    String projectId,
  ) async {
    final statistics = _statisticsByProjectId[projectId];
    if (statistics == null) {
      return null;
    }
    return RepositoryRecord(
      value: statistics,
      ownerId: _ownersByProjectId[projectId] ?? 'owner-1',
      revision: _revisionsByProjectId[projectId] ?? 1,
    );
  }

  @override
  Future<List<ProjectStatistics>> findAll() async {
    return List<ProjectStatistics>.unmodifiable(_statisticsByProjectId.values);
  }

  @override
  Future<void> save(ProjectStatistics statistics) async {
    _statisticsByProjectId[statistics.projectId] = statistics;
    _ownersByProjectId.putIfAbsent(statistics.projectId, () => 'owner-1');
    _revisionsByProjectId[statistics.projectId] =
        (_revisionsByProjectId[statistics.projectId] ?? 1) + 1;
  }

  @override
  Future<RepositoryMutationResult> saveIfRevision({
    required ProjectStatistics statistics,
    required String ownerId,
    required int expectedRevision,
  }) async {
    final projectId = statistics.projectId;
    if (!_statisticsByProjectId.containsKey(projectId) ||
        (_ownersByProjectId[projectId] ?? 'owner-1') != ownerId) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: projectId,
        ),
      );
    }
    final actualRevision = _revisionsByProjectId[projectId] ?? 1;
    if (actualRevision != expectedRevision) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.staleRevision,
          entityId: projectId,
          expectedRevision: expectedRevision,
          actualRevision: actualRevision,
        ),
      );
    }
    final nextRevision = actualRevision + 1;
    _statisticsByProjectId[projectId] = statistics;
    _ownersByProjectId[projectId] = ownerId;
    _revisionsByProjectId[projectId] = nextRevision;
    return RepositoryMutationResult.success(revision: nextRevision);
  }
}

class _UnitOfWorkOwnerSettingsRepository implements OwnerSettingsRepository {
  _UnitOfWorkOwnerSettingsRepository({
    required Map<String, OwnerSettings> settingsByOwnerId,
    required Map<String, int> revisionsByOwnerId,
  })  : _settingsByOwnerId = settingsByOwnerId,
        _revisionsByOwnerId = revisionsByOwnerId;

  final Map<String, OwnerSettings> _settingsByOwnerId;
  final Map<String, int> _revisionsByOwnerId;

  @override
  Future<OwnerSettings?> findByOwnerId(String ownerId) async {
    return _settingsByOwnerId[ownerId];
  }

  @override
  Future<RepositoryRecord<OwnerSettings>?> findRecordByOwnerId(
    String ownerId,
  ) async {
    final settings = _settingsByOwnerId[ownerId];
    if (settings == null) {
      return null;
    }
    return RepositoryRecord(
      value: settings,
      ownerId: ownerId,
      revision: _revisionsByOwnerId[ownerId] ?? 1,
    );
  }

  @override
  Future<void> save(OwnerSettings settings) async {
    _settingsByOwnerId[settings.ownerId] = settings;
    _revisionsByOwnerId[settings.ownerId] =
        (_revisionsByOwnerId[settings.ownerId] ?? 1) + 1;
  }

  @override
  Future<RepositoryMutationResult> saveIfRevision({
    required OwnerSettings settings,
    required int expectedRevision,
  }) async {
    final ownerId = settings.ownerId;
    if (!_settingsByOwnerId.containsKey(ownerId)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: ownerId,
        ),
      );
    }
    final actualRevision = _revisionsByOwnerId[ownerId] ?? 1;
    if (actualRevision != expectedRevision) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.staleRevision,
          entityId: ownerId,
          expectedRevision: expectedRevision,
          actualRevision: actualRevision,
        ),
      );
    }
    final nextRevision = actualRevision + 1;
    _settingsByOwnerId[ownerId] = settings;
    _revisionsByOwnerId[ownerId] = nextRevision;
    return RepositoryMutationResult.success(revision: nextRevision);
  }
}

class _UnitOfWorkNoticeAcknowledgementRepository
    implements NoticeAcknowledgementRepository {
  _UnitOfWorkNoticeAcknowledgementRepository({
    required Map<String, NoticeAcknowledgementRecord> recordsByScopedId,
    required Map<String, int> revisionsByScopedId,
  })  : _recordsByScopedId = recordsByScopedId,
        _revisionsByScopedId = revisionsByScopedId;

  final Map<String, NoticeAcknowledgementRecord> _recordsByScopedId;
  final Map<String, int> _revisionsByScopedId;

  @override
  Future<NoticeAcknowledgementRecord?> findById({
    required String ownerId,
    required String noticeId,
  }) async {
    return _recordsByScopedId[
        _noticeAcknowledgementKey(ownerId: ownerId, noticeId: noticeId)];
  }

  @override
  Future<List<NoticeAcknowledgementRecord>> findByOwnerId(
    String ownerId,
  ) async {
    return List<NoticeAcknowledgementRecord>.unmodifiable(
      _recordsByScopedId.values.where((record) => record.ownerId == ownerId),
    );
  }

  @override
  Future<RepositoryPage<NoticeAcknowledgementRecord>> findByOwner({
    required String ownerId,
    RepositoryPageRequest page = const RepositoryPageRequest(),
  }) async {
    final records = _recordsByScopedId.values
        .where((record) => record.ownerId == ownerId)
        .toList(growable: false)
      ..sort(_compareNoticeAcknowledgements);
    return _page(records, page);
  }

  @override
  Future<void> save(NoticeAcknowledgementRecord record) async {
    final key = _noticeAcknowledgementKey(
      ownerId: record.ownerId,
      noticeId: record.noticeId,
    );
    _recordsByScopedId[key] = record;
    _revisionsByScopedId[key] = (_revisionsByScopedId[key] ?? 1) + 1;
  }

  @override
  Future<RepositoryMutationResult> insert(
    NoticeAcknowledgementRecord record,
  ) async {
    final key = _noticeAcknowledgementKey(
      ownerId: record.ownerId,
      noticeId: record.noticeId,
    );
    if (_recordsByScopedId.containsKey(key)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateId,
          entityId: record.noticeId,
        ),
      );
    }
    _recordsByScopedId[key] = record;
    _revisionsByScopedId[key] = 1;
    return RepositoryMutationResult.success(revision: 1);
  }
}

class _UnitOfWorkOperationRepository implements ApplicationOperationRepository {
  _UnitOfWorkOperationRepository(this._operationsById);

  final Map<String, ApplicationOperationRecord> _operationsById;

  @override
  Future<ApplicationOperationRecord?> findById(String operationId) async {
    return _operationsById[operationId];
  }

  @override
  Future<ApplicationOperationRecord?> findByIdempotencyKey({
    required String ownerId,
    required String operationId,
  }) async {
    final operation = _operationsById[operationId];
    if (operation == null || operation.ownerId != ownerId) {
      return null;
    }
    return operation;
  }

  @override
  Future<void> save(ApplicationOperationRecord operation) async {
    _operationsById[operation.operationId] = operation;
  }

  @override
  Future<RepositoryMutationResult> insertIfAbsent(
    ApplicationOperationRecord operation,
  ) async {
    if (_operationsById.containsKey(operation.operationId)) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.duplicateOperation,
          entityId: operation.operationId,
        ),
      );
    }
    _operationsById[operation.operationId] = operation;
    return RepositoryMutationResult.success(revision: 1);
  }
}

String _noticeAcknowledgementKey({
  required String ownerId,
  required String noticeId,
}) {
  return '$ownerId::$noticeId';
}

RepositoryPage<T> _page<T>(List<T> sortedItems, RepositoryPageRequest request) {
  final offset = int.tryParse(request.cursor ?? '') ?? 0;
  final safeOffset = offset.clamp(0, sortedItems.length);
  final end = (safeOffset + request.limit).clamp(0, sortedItems.length);
  final items = sortedItems.sublist(safeOffset, end);
  final nextCursor = end < sortedItems.length ? end.toString() : null;
  return RepositoryPage<T>(
    items: List<T>.unmodifiable(items),
    nextCursor: nextCursor,
  );
}

bool _taskOverlapsWindow(Task task, SchedulingWindow window) {
  final start = task.scheduledStart;
  final end = task.scheduledEnd;
  if (start == null || end == null) {
    return false;
  }
  return TimeInterval(start: start, end: end).overlaps(window.interval);
}

int _compareTaskIds(Task left, Task right) => left.id.compareTo(right.id);

int _compareScheduledTasks(Task left, Task right) {
  final leftStart = left.scheduledStart;
  final rightStart = right.scheduledStart;
  if (leftStart != null && rightStart != null) {
    final startComparison = leftStart.compareTo(rightStart);
    if (startComparison != 0) {
      return startComparison;
    }
  }
  return left.id.compareTo(right.id);
}

int _compareBacklogCandidates(Task left, Task right) {
  final createdComparison = left.createdAt.compareTo(right.createdAt);
  if (createdComparison != 0) {
    return createdComparison;
  }
  return left.id.compareTo(right.id);
}

int _compareProjects(ProjectProfile left, ProjectProfile right) {
  final nameComparison = left.name.compareTo(right.name);
  if (nameComparison != 0) {
    return nameComparison;
  }
  return left.id.compareTo(right.id);
}

int _compareLockedBlocks(LockedBlock left, LockedBlock right) {
  final nameComparison = left.name.compareTo(right.name);
  if (nameComparison != 0) {
    return nameComparison;
  }
  return left.id.compareTo(right.id);
}

int _compareLockedOverrides(
  LockedBlockOverride left,
  LockedBlockOverride right,
) {
  final dateComparison = left.date.compareTo(right.date);
  if (dateComparison != 0) {
    return dateComparison;
  }
  final createdComparison = left.createdAt.compareTo(right.createdAt);
  if (createdComparison != 0) {
    return createdComparison;
  }
  return left.id.compareTo(right.id);
}

int _compareActivities(TaskActivity left, TaskActivity right) {
  final occurredComparison = left.occurredAt.compareTo(right.occurredAt);
  if (occurredComparison != 0) {
    return occurredComparison;
  }
  return left.id.compareTo(right.id);
}

int _compareNoticeAcknowledgements(
  NoticeAcknowledgementRecord left,
  NoticeAcknowledgementRecord right,
) {
  final acknowledgedComparison =
      left.acknowledgedAt.compareTo(right.acknowledgedAt);
  if (acknowledgedComparison != 0) {
    return acknowledgedComparison;
  }
  return left.noticeId.compareTo(right.noticeId);
}

String _requiredTrimmed(String value, String name) {
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    throw ArgumentError.value(value, name, 'Value is required.');
  }
  return trimmed;
}
