// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

part of '../../repositories.dart';

/// In-memory project repository useful for tests and early application wiring.
class InMemoryProjectRepository implements ProjectRepository {
  /// Creates a `InMemoryProjectRepository` instance with the values required by its invariants.
  /// Callers should pass already-normalized scheduler data because validation and defaulting belong at the model or service boundary, not in arbitrary UI code.
  InMemoryProjectRepository(
      [Iterable<ProjectProfile> initialProjects = const [],
      this.defaultOwnerId = 'owner-1'])
      : _projectsById = {
          for (final project in initialProjects) project.id: project,
        },
        _ownersById = {
          for (final project in initialProjects) project.id: defaultOwnerId,
        },
        _revisionsById = {
          for (final project in initialProjects) project.id: 1,
        };

  /// Private state stored as `_projectsById` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  final Map<String, ProjectProfile> _projectsById;

  /// Private state stored as `_ownersById` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  final Map<String, String> _ownersById;

  /// Private state stored as `_revisionsById` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  final Map<String, int> _revisionsById;

  /// Stores the `defaultOwnerId` value for this object.
  /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
  final String defaultOwnerId;

  /// Runs the `findById` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @override
  Future<ProjectProfile?> findById(String id) async {
    return _projectsById[id];
  }

  /// Runs the `findRecordById` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @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,
    );
  }

  /// Runs the `findAll` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @override
  Future<List<ProjectProfile>> findAll() async {
    return List<ProjectProfile>.unmodifiable(_projectsById.values);
  }

  /// Runs the `findByOwner` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @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);
  }

  /// Runs the `save` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @override
  Future<void> save(ProjectProfile project) async {
    _projectsById[project.id] = project;
    _ownersById.putIfAbsent(project.id, () => defaultOwnerId);
    _revisionsById[project.id] = _revisionFor(project.id) + 1;
  }

  /// Runs the `insert` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @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);
  }

  /// Runs the `saveIfRevision` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @override
  Future<RepositoryMutationResult> saveIfRevision({
    required ProjectProfile project,
    required String ownerId,
    required int expectedRevision,
  }) async {
    if (expectedRevision <= 0) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.invalidRevision,
          entityId: project.id,
          expectedRevision: expectedRevision,
        ),
      );
    }
    final current = _projectsById[project.id];
    if (current == null) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.notFound,
          entityId: project.id,
        ),
      );
    }
    if (_ownerFor(project.id) != ownerId) {
      return RepositoryMutationResult.failure(
        RepositoryFailure(
          code: RepositoryFailureCode.ownerMismatch,
          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);
  }

  /// Runs the `archive` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @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,
    );
  }

  /// Runs the `_ownerFor` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  String _ownerFor(String id) => _ownersById[id] ?? defaultOwnerId;

  /// Runs the `_revisionFor` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  int _revisionFor(String id) => _revisionsById[id] ?? 1;
}
