// Low-friction capture flow.
//
// Quick capture is the "dump the thought before it disappears" path. The goal
// is to accept minimal data, preserve the user's input, and only ask for more
// structure when the user wants immediate scheduling.

import 'models.dart';
import 'scheduling_engine.dart';
import 'time_contracts.dart';

/// Outcome of a quick-capture request.
///
/// The UI can use this status to decide whether to show a passive success, draw
/// a scheduled card on the timeline, or display validation messages. It is not
/// an exception-based flow because quick capture should fail gently and keep the
/// user's typed task available.
enum QuickCaptureStatus {
  /// Capture succeeded and the task remains unscheduled in backlog.
  addedToBacklog,

  /// Capture succeeded and the task was placed on the timeline.
  scheduled,

  /// Capture could not complete the requested flow; see result messages.
  validationError,
}

/// Input for low-friction task capture.
///
/// This object represents what the UI knows at the moment of capture. It mirrors
/// the product goal: adding a thought should require as little structure as
/// possible, but the user can optionally provide enough detail to immediately
/// schedule it into the next open flexible slot.
class QuickCaptureRequest {
  QuickCaptureRequest({
    required this.id,
    required this.title,
    required this.createdAt,
    this.addToNextAvailableSlot = false,
    this.projectId = 'inbox',
    this.priority = PriorityLevel.medium,
    this.reward = RewardLevel.notSet,
    this.difficulty = DifficultyLevel.notSet,
    this.type = TaskType.flexible,
    this.durationMinutes,
    Set<BacklogTag> backlogTags = const <BacklogTag>{},
  }) : backlogTags = Set<BacklogTag>.unmodifiable(backlogTags);

  /// Caller-generated id. Keeping id generation outside this service makes the
  /// domain layer independent from persistence/database choices.
  final String id;

  /// Raw user-entered title. The [Task.quickCapture] factory trims it.
  final String title;

  /// Capture timestamp supplied by the caller for testability.
  final DateTime createdAt;

  /// Whether capture should attempt immediate timeline placement.
  final bool addToNextAvailableSlot;

  /// Project id to assign; defaults to the inbox for uncategorized thoughts.
  final String projectId;

  /// Initial priority used by backlog/scheduler heuristics.
  final PriorityLevel priority;

  /// Initial reward estimate.
  final RewardLevel reward;

  /// Initial difficulty estimate.
  final DifficultyLevel difficulty;

  /// Captured task type. Immediate scheduling currently requires flexible tasks.
  final TaskType type;

  /// Optional duration estimate. Required only when scheduling immediately.
  final int? durationMinutes;

  /// Optional backlog flags such as wishlist/someday.
  final Set<BacklogTag> backlogTags;
}

/// Result of a quick-capture request.
///
/// The result always carries a [task], even on validation failure, so the UI can
/// preserve the user's input and show what needs to be fixed. When scheduling
/// was attempted, [schedulingResult] exposes the lower-level engine notices and
/// changes for debugging or timeline updates.
class QuickCaptureResult {
  QuickCaptureResult({
    required this.task,
    required this.status,
    this.schedulingResult,
    List<String> messages = const <String>[],
  }) : messages = List<String>.unmodifiable(messages);

  /// Captured task, scheduled or unscheduled depending on [status].
  final Task task;

  /// High-level outcome of the capture attempt.
  final QuickCaptureStatus status;

  /// Detailed scheduling output when immediate placement was attempted.
  final SchedulingResult? schedulingResult;

  /// Human-readable validation or scheduling messages.
  final List<String> messages;

  /// Convenience check for UI branches that only care whether capture succeeded.
  bool get isValid => status != QuickCaptureStatus.validationError;
}

/// 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 {
  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,
  }) {
    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) {
      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) {
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.addedToBacklog,
      );
    }

    if (request.durationMinutes == null || request.durationMinutes! <= 0) {
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        messages: const ['Duration is required to schedule quick capture.'],
      );
    }

    if (request.type != TaskType.flexible) {
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        messages: const ['Scheduled quick capture must be flexible.'],
      );
    }

    if (schedulingInput == null) {
      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) {
      return QuickCaptureResult(
        task: capturedTask,
        status: QuickCaptureStatus.validationError,
        schedulingResult: result,
        messages: result.notices.map((notice) => notice.message).toList(),
      );
    }

    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) {
      throw StateError('Quick capture needs an id generator.');
    }

    final now = createdAt ?? updatedAt ?? clock.now();

    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;
}
