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

part of '../quick_capture.dart';

/// Coordinates quick capture defaults and optional scheduling.
///
/// This service is intentionally thin: it builds a [Task], validates the extra
/// requirements for immediate scheduling, and delegates placement to
/// [SchedulingEngine]. It keeps quick-capture UI code from needing to understand
/// every scheduler precondition.
class QuickCaptureService {
  /// Creates a `QuickCaptureService` 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.
  const QuickCaptureService({
    this.engine = const SchedulingEngine(),
    this.clock = const SystemClock(),
    this.idGenerator,
  });

  /// Scheduling dependency. Defaults to the starter engine but can be swapped in
  /// tests or future implementations.
  final SchedulingEngine engine;

  /// Clock boundary used by convenience capture entry points.
  final Clock clock;

  /// Optional ID boundary for convenience capture entry points.
  final IdGenerator? idGenerator;

  /// Capture a task and optionally place it into the next available slot.
  ///
  /// Flow:
  /// 1. Build the task using lightweight defaults.
  /// 2. If immediate scheduling was not requested, return a backlog success.
  /// 3. Validate the requirements for immediate scheduling.
  /// 4. Call [SchedulingEngine.insertBacklogTaskIntoNextAvailableSlot].
  /// 5. Convert the lower-level scheduling result into a capture result.
  QuickCaptureResult capture(
    QuickCaptureRequest request, {
    SchedulingInput? schedulingInput,
    DateTime? updatedAt,
  }) {
    logger.debug(() => 'Quick capture requested. taskId=${request.id} '
        'projectId=${request.projectId} taskType=${request.type.name} '
        'hasDuration=${request.durationMinutes != null} '
        'addToNextAvailableSlot=${request.addToNextAvailableSlot}');
    final capturedTaskWithoutDuration = Task.quickCapture(
      id: request.id,
      title: request.title,
      createdAt: request.createdAt,
      projectId: request.projectId,
      type: request.type,
      priority: request.priority,
      reward: request.reward,
      difficulty: request.difficulty,
      backlogTags: request.backlogTags,
      updatedAt: updatedAt,
    );

    if (request.durationMinutes != null && request.durationMinutes! <= 0) {
      logger.warn(() =>
          'Quick capture validation failed: non-positive duration. '
          'taskId=${request.id} durationMinutes=${request.durationMinutes}');
      return QuickCaptureResult(
        task: capturedTaskWithoutDuration,
        status: QuickCaptureStatus.validationError,
        messages: const ['Duration must be positive when provided.'],
      );
    }

    final capturedTask = capturedTaskWithoutDuration.copyWith(
      durationMinutes: request.durationMinutes,
    );

    if (!request.addToNextAvailableSlot) {
      logger.debug(
          () => 'Quick capture added to backlog. taskId=${capturedTask.id}');
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.addedToBacklog,
      );
    }

    if (request.durationMinutes == null || request.durationMinutes! <= 0) {
      logger.warn(
          () => 'Quick capture scheduling validation failed: missing duration. '
              'taskId=${capturedTask.id}');
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        messages: const ['Duration is required to schedule quick capture.'],
      );
    }

    if (request.type != TaskType.flexible) {
      logger.warn(() =>
          'Quick capture scheduling validation failed: non-flexible task. '
          'taskId=${capturedTask.id} taskType=${request.type.name}');
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        messages: const ['Scheduled quick capture must be flexible.'],
      );
    }

    if (schedulingInput == null) {
      logger.warn(() =>
          'Quick capture scheduling validation failed: missing scheduling input. '
          'taskId=${capturedTask.id}');
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        messages: const ['Scheduling input is required.'],
      );
    }

    final result = engine.insertBacklogTaskIntoNextAvailableSlot(
      input: SchedulingInput(
        tasks: [capturedTask, ...schedulingInput.tasks],
        window: schedulingInput.window,
        lockedIntervals: schedulingInput.lockedIntervals,
        requiredVisibleIntervals: schedulingInput.requiredVisibleIntervals,
      ),
      taskId: capturedTask.id,
      updatedAt: updatedAt,
    );
    final scheduledTask = _taskById(result.tasks, capturedTask.id);

    if (scheduledTask == null || scheduledTask.isBacklog) {
      logger.warn(() => 'Quick capture scheduling did not place task. '
          'taskId=${capturedTask.id} outcome=${result.outcomeCode.name} '
          'noticeCount=${result.notices.length}');
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        schedulingResult: result,
        messages: result.notices.map((notice) => notice.message).toList(),
      );
    }

    logger.debug(() => 'Quick capture scheduled. taskId=${scheduledTask.id} '
        'start=${scheduledTask.scheduledStart?.toIso8601String()} '
        'end=${scheduledTask.scheduledEnd?.toIso8601String()} '
        'changeCount=${result.changes.length} noticeCount=${result.notices.length}');
    return QuickCaptureResult(
      task: scheduledTask,
      status: QuickCaptureStatus.scheduled,
      schedulingResult: result,
    );
  }

  /// Convenience outer-boundary wrapper that supplies id and creation time.
  QuickCaptureResult captureNew({
    required String title,
    bool addToNextAvailableSlot = false,
    String projectId = 'inbox',
    PriorityLevel priority = PriorityLevel.medium,
    RewardLevel reward = RewardLevel.notSet,
    DifficultyLevel difficulty = DifficultyLevel.notSet,
    TaskType type = TaskType.flexible,
    int? durationMinutes,
    Set<BacklogTag> backlogTags = const <BacklogTag>{},
    SchedulingInput? schedulingInput,
    DateTime? createdAt,
    DateTime? updatedAt,
  }) {
    final generator = idGenerator;
    if (generator == null) {
      logger.error(
          () => 'Quick capture rejected because id generator is missing.');
      throw StateError('Quick capture needs an id generator.');
    }

    final now = createdAt ?? updatedAt ?? clock.now();
    logger.debug(() => 'Quick capture new task requested. '
        'hasTitle=${title.trim().isNotEmpty} projectId=$projectId '
        'type=${type.name} addToNextAvailableSlot=$addToNextAvailableSlot '
        'occurredAt=${now.toIso8601String()}');

    return capture(
      QuickCaptureRequest(
        id: generator.nextId(),
        title: title,
        createdAt: now,
        addToNextAvailableSlot: addToNextAvailableSlot,
        projectId: projectId,
        priority: priority,
        reward: reward,
        difficulty: difficulty,
        type: type,
        durationMinutes: durationMinutes,
        backlogTags: backlogTags,
      ),
      schedulingInput: schedulingInput,
      updatedAt: updatedAt ?? now,
    );
  }
}

/// Find a task in a returned scheduling result.
///
/// This duplicates a small helper rather than exposing scheduler internals. The
/// capture service only needs to retrieve the newly created task after placement.
Task? _taskById(List<Task> tasks, String id) {
  for (final task in tasks) {
    if (task.id == id) {
      return task;
    }
  }

  return null;
}
