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

part of '../sqlite_repositories.dart';

/// SQLite implementation of the app-layer task activity repository.
final class _SqliteApplicationTaskActivityRepository
    implements core.TaskActivityRepository {
  /// Creates a task activity repository for [db].
  _SqliteApplicationTaskActivityRepository(
    this.db, {
    required this.defaultOwnerId,
  });

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

  /// Owner used for save methods without an owner parameter.
  final String defaultOwnerId;

  /// Returns an activity by stable id.
  @override
  Future<core.TaskActivity?> findById(String id) async {
    final row = await _activityById(id);
    return row == null ? null : _taskActivityFromRow(row);
  }

  /// Returns activities emitted by [operationId].
  @override
  Future<List<core.TaskActivity>> findByOperationId(String operationId) {
    return _activitiesWhere(
      (table) => table.operationId.equals(operationId),
    );
  }

  /// Returns activities for [taskId].
  @override
  Future<List<core.TaskActivity>> findByTaskId(String taskId) {
    return _activitiesWhere((table) => table.taskId.equals(taskId));
  }

  /// Returns activities for [projectId].
  @override
  Future<List<core.TaskActivity>> findByProjectId(String projectId) {
    return _activitiesWhere((table) => table.projectId.equals(projectId));
  }

  /// Returns every activity.
  @override
  Future<List<core.TaskActivity>> findAll() {
    return _activitiesWhere((table) => const drift.Constant(true));
  }

  /// Returns owner-scoped activities.
  @override
  Future<core.RepositoryPage<core.TaskActivity>> findByOwner({
    required String ownerId,
    core.RepositoryPageRequest page = const core.RepositoryPageRequest(),
  }) async {
    final rows = await (db.select(db.taskActivities)
          ..where((table) => table.ownerId.equals(ownerId))
          ..orderBy([
            (table) => drift.OrderingTerm.asc(table.occurredAtUtc),
            (table) => drift.OrderingTerm.asc(table.id),
          ])
          ..limit(
            page.limit + 1,
            offset: _applicationCursorOffset(page.cursor),
          ))
        .get();
    return _applicationPageFromRows(rows, page, _taskActivityFromRow);
  }

  /// Saves [activity], inserting it when absent.
  @override
  Future<void> save(core.TaskActivity activity) async {
    final row = await _activityById(activity.id);
    final ownerId = row?.ownerId ?? defaultOwnerId;
    await db.into(db.taskActivities).insertOnConflictUpdate(
          _taskActivityCompanion(activity, ownerId: ownerId),
        );
  }

  /// Saves all [activities].
  @override
  Future<void> saveAll(Iterable<core.TaskActivity> activities) async {
    for (final activity in activities) {
      await save(activity);
    }
  }

  /// Appends [activity] for [ownerId] when its id is unused.
  @override
  Future<core.RepositoryMutationResult> append({
    required core.TaskActivity activity,
    required String ownerId,
  }) async {
    if (await _activityById(activity.id) != null) {
      return _mutationFailure(
        core.RepositoryFailureCode.duplicateId,
        entityId: activity.id,
      );
    }
    await db.into(db.taskActivities).insert(
          _taskActivityCompanion(activity, ownerId: ownerId),
        );
    return core.RepositoryMutationResult.success(revision: 1);
  }

  /// Returns activities matching [where].
  Future<List<core.TaskActivity>> _activitiesWhere(
    drift.Expression<bool> Function($TaskActivitiesTable table) where,
  ) async {
    final rows = await (db.select(db.taskActivities)
          ..where(where)
          ..orderBy([
            (table) => drift.OrderingTerm.asc(table.occurredAtUtc),
            (table) => drift.OrderingTerm.asc(table.id),
          ]))
        .get();
    return List<core.TaskActivity>.unmodifiable(
      rows.map(_taskActivityFromRow),
    );
  }

  /// Returns a raw activity row by id.
  Future<TaskActivityRow?> _activityById(String id) {
    return (db.select(db.taskActivities)..where((table) => table.id.equals(id)))
        .getSingleOrNull();
  }
}

/// Builds a Drift companion for [activity].
TaskActivitiesCompanion _taskActivityCompanion(
  core.TaskActivity activity, {
  required String ownerId,
}) {
  return TaskActivitiesCompanion.insert(
    id: activity.id,
    ownerId: ownerId,
    taskId: activity.taskId,
    projectId: activity.projectId,
    operationId: activity.operationId,
    code: activity.code.name,
    occurredAtUtc: activity.occurredAt.toUtc(),
    metadataJson: _metadataJson(activity.metadata),
  );
}

/// Converts a task activity row to a core activity.
core.TaskActivity _taskActivityFromRow(TaskActivityRow row) {
  return core.TaskActivity(
    id: row.id,
    operationId: row.operationId,
    code: _enumByName(core.TaskActivityCode.values, row.code),
    taskId: row.taskId,
    projectId: row.projectId,
    occurredAt: row.occurredAtUtc.toUtc(),
    metadata: _metadataFromJson(row.metadataJson),
  );
}
