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

part of '../sqlite_repositories.dart';

/// Drift-backed task repository.
final class SqliteTaskRepository implements TaskRepository {
  /// Creates a task repository using [db].
  SqliteTaskRepository(this.db);

  /// Shared SQLite database handle.
  final SchedulerDb db;

  /// 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<RepoResult<core.Task?>> findById({
    required core.OwnerId ownerId,
    required String taskId,
  }) async {
    final row = await _taskById(taskId);
    if (row == null || row.ownerId != ownerId.value) {
      return const Right(null);
    }
    return Right(_taskFromRow(row));
  }

  /// 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<RepoResult<core.Page<core.Task>>> findByOwner({
    required core.OwnerId ownerId,
    core.PageRequest page = const core.PageRequest(),
  }) async {
    return Right(await _pageTaskRows(
      page: page,
      where: (table) => table.ownerId.equals(ownerId.value),
    ));
  }

  /// Runs the `findByParentTaskId` 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<RepoResult<core.Page<core.Task>>> findByParentTaskId({
    required core.OwnerId ownerId,
    required String parentTaskId,
    core.PageRequest page = const core.PageRequest(),
  }) async {
    return Right(await _pageTaskRows(
      page: page,
      where: (table) =>
          table.ownerId.equals(ownerId.value) &
          table.parentId.equals(parentTaskId),
    ));
  }

  /// Runs the `findByProjectId` 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<RepoResult<core.Page<core.Task>>> findByProjectId({
    required core.OwnerId ownerId,
    required String projectId,
    core.PageRequest page = const core.PageRequest(),
  }) async {
    return Right(await _pageTaskRows(
      page: page,
      where: (table) =>
          table.ownerId.equals(ownerId.value) &
          table.projectId.equals(projectId),
    ));
  }

  /// Runs the `findByStatus` 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<RepoResult<core.Page<core.Task>>> findByStatus({
    required core.OwnerId ownerId,
    required core.TaskStatus status,
    core.PageRequest page = const core.PageRequest(),
  }) async {
    return Right(await _pageTaskRows(
      page: page,
      where: (table) =>
          table.ownerId.equals(ownerId.value) &
          table.status.equals(core.PersistenceEnumMapping.encodeTaskStatus(
            status,
          )),
    ));
  }

  /// 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<RepoResult<core.Revision>> insert({
    required core.OwnerId ownerId,
    required core.Task task,
  }) async {
    if (await _taskById(task.id) != null) {
      return Left(RepositoryDuplicateId(entityId: task.id));
    }
    await db.into(db.tasks).insert(_taskCompanion(
          task,
          ownerId: ownerId,
          revision: core.Revision.initial,
        ));
    return const Right(core.Revision.initial);
  }

  /// 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<RepoResult<core.Revision>> save({
    required core.OwnerId ownerId,
    required core.Task task,
    required core.Revision expectedRevision,
  }) async {
    final checked = await _checkTaskWrite(
      task.id,
      ownerId,
      expectedRevision,
    );
    if (checked is Left<RepositoryFailure, TaskRow>) {
      return Left(checked.value);
    }
    final nextRevision = checked.right.revision.next();
    final updates = _taskCompanion(
      task,
      ownerId: ownerId,
      revision: nextRevision,
    );
    final updated = await (db.update(db.tasks)
          ..where(
            (table) =>
                table.id.equals(task.id) &
                table.ownerId.equals(ownerId.value) &
                table.revision.equals(expectedRevision.value),
          ))
        .write(updates);
    if (updated != 1) {
      return Left(await _staleTaskFailure(task.id, expectedRevision));
    }
    return Right(nextRevision);
  }

  /// Runs the `delete` 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<RepoResult<core.Revision>> delete({
    required core.OwnerId ownerId,
    required String taskId,
    required core.Revision expectedRevision,
  }) async {
    final checked = await _checkTaskWrite(taskId, ownerId, expectedRevision);
    if (checked is Left<RepositoryFailure, TaskRow>) {
      return Left(checked.value);
    }
    final nextRevision = checked.right.revision.next();
    final deleted = await (db.delete(db.tasks)
          ..where(
            (table) =>
                table.id.equals(taskId) &
                table.ownerId.equals(ownerId.value) &
                table.revision.equals(expectedRevision.value),
          ))
        .go();
    if (deleted != 1) {
      return Left(await _staleTaskFailure(taskId, expectedRevision));
    }
    return Right(nextRevision);
  }

  /// Runs the `_pageTaskRows` 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.
  Future<core.Page<core.Task>> _pageTaskRows({
    required core.PageRequest page,
    required drift.Expression<bool> Function($TasksTable table) where,
  }) async {
    final offset = _cursorOffset(page.cursor);
    final query = db.select(db.tasks)
      ..where(where)
      ..orderBy([(table) => drift.OrderingTerm.asc(table.id)])
      ..limit(page.limit + 1, offset: offset);
    final rows = await query.get();
    final pageRows = rows.take(page.limit).toList(growable: false);
    return core.Page(
      items: pageRows.map(_taskFromRow),
      nextCursor: rows.length > page.limit ? '${offset + page.limit}' : null,
    );
  }

  /// Runs the `_taskById` 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.
  Future<TaskRow?> _taskById(String id) {
    return (db.select(db.tasks)..where((table) => table.id.equals(id)))
        .getSingleOrNull();
  }

  /// Runs the `_checkTaskWrite` 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.
  Future<Either<RepositoryFailure, TaskRow>> _checkTaskWrite(
    String id,
    core.OwnerId ownerId,
    core.Revision expectedRevision,
  ) async {
    if (expectedRevision.value <= 0) {
      return Left(RepositoryInvalidRevision(
        entityId: id,
        expectedRevision: expectedRevision,
      ));
    }
    final row = await _taskById(id);
    if (row == null) {
      return Left(RepositoryNotFound(entityId: id));
    }
    if (row.ownerId != ownerId.value) {
      return Left(RepositoryOwnerMismatch(entityId: id));
    }
    if (row.revision != expectedRevision.value) {
      return Left(RepositoryStaleRevision(
        entityId: id,
        expectedRevision: expectedRevision,
        actualRevision: core.Revision(row.revision),
      ));
    }
    return Right(row);
  }

  /// Runs the `_staleTaskFailure` 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.
  Future<RepositoryStaleRevision> _staleTaskFailure(
    String id,
    core.Revision expectedRevision,
  ) async {
    final row = await _taskById(id);
    return RepositoryStaleRevision(
      entityId: id,
      expectedRevision: expectedRevision,
      actualRevision:
          row == null ? expectedRevision.next() : core.Revision(row.revision),
    );
  }
}
