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

/// Quick actions available from a flexible task card.
enum FlexibleTaskQuickAction {
  done,
  push,
  backlog,
  breakUp,
}

/// Explicit push destinations shown after choosing the push quick action.
enum PushDestination {
  nextAvailableSlot,
  tomorrowTopOfQueue,
  backlog,
}

/// Domain result for a flexible task quick action.
class FlexibleTaskActionResult {
  const FlexibleTaskActionResult({
    required this.action,
    required this.task,
    this.pushDestinations = const <PushDestination>[],
    this.startsChildTaskFlow = false,
  });

  final FlexibleTaskQuickAction action;
  final Task task;
  final List<PushDestination> pushDestinations;
  final bool startsChildTaskFlow;

  bool get changedTask => !startsChildTaskFlow && pushDestinations.isEmpty;
}

/// Result from applying a selected push destination.
class PushDestinationResult {
  const PushDestinationResult({
    required this.destination,
    required this.schedulingResult,
  });

  final PushDestination destination;
  final SchedulingResult schedulingResult;

  bool get placesAtTomorrowTopOfQueue {
    return destination == PushDestination.tomorrowTopOfQueue;
  }
}

/// Applies low-friction quick actions for flexible task cards.
class FlexibleTaskActionService {
  const FlexibleTaskActionService({
    this.schedulingEngine = const SchedulingEngine(),
  });

  final SchedulingEngine schedulingEngine;

  FlexibleTaskActionResult apply({
    required Task task,
    required FlexibleTaskQuickAction action,
    DateTime? updatedAt,
  }) {
    if (!task.isFlexible) {
      throw ArgumentError.value(
          task.type, 'task.type', 'Task must be flexible.');
    }

    switch (action) {
      case FlexibleTaskQuickAction.done:
        return FlexibleTaskActionResult(
          action: action,
          task: task.copyWith(
            status: TaskStatus.completed,
            updatedAt: updatedAt ?? DateTime.now(),
          ),
        );
      case FlexibleTaskQuickAction.push:
        return FlexibleTaskActionResult(
          action: action,
          task: task,
          pushDestinations: const [
            PushDestination.nextAvailableSlot,
            PushDestination.tomorrowTopOfQueue,
            PushDestination.backlog,
          ],
        );
      case FlexibleTaskQuickAction.backlog:
        return FlexibleTaskActionResult(
          action: action,
          task: schedulingEngine.moveToBacklog(task, updatedAt: updatedAt),
        );
      case FlexibleTaskQuickAction.breakUp:
        return FlexibleTaskActionResult(
          action: action,
          task: task,
          startsChildTaskFlow: true,
        );
    }
  }

  PushDestinationResult applyPushDestination({
    required PushDestination destination,
    required SchedulingInput input,
    required String taskId,
    DateTime? updatedAt,
  }) {
    final result = switch (destination) {
      PushDestination.nextAvailableSlot =>
        schedulingEngine.pushFlexibleTaskToNextAvailableSlot(
          input: input,
          taskId: taskId,
          updatedAt: updatedAt,
        ),
      PushDestination.tomorrowTopOfQueue =>
        schedulingEngine.pushFlexibleTaskToTomorrowTopOfQueue(
          input: input,
          taskId: taskId,
          updatedAt: updatedAt,
        ),
      PushDestination.backlog => _moveTaskToBacklog(
          input: input,
          taskId: taskId,
          updatedAt: updatedAt,
        ),
    };

    return PushDestinationResult(
      destination: destination,
      schedulingResult: result,
    );
  }

  SchedulingResult _moveTaskToBacklog({
    required SchedulingInput input,
    required String taskId,
    DateTime? updatedAt,
  }) {
    final task = _taskById(input.tasks, taskId);
    if (task == null) {
      return SchedulingResult(
        tasks: input.tasks,
        notices: [
          SchedulingNotice(
            'Task was not found.',
            type: SchedulingNoticeType.noFit,
            taskId: taskId,
          ),
        ],
      );
    }

    if (!task.isFlexible || task.status != TaskStatus.planned) {
      return SchedulingResult(
        tasks: input.tasks,
        notices: [
          SchedulingNotice(
            'Only planned flexible tasks can be moved to backlog.',
            type: SchedulingNoticeType.noFit,
            taskId: task.id,
          ),
        ],
      );
    }

    final movedTask = schedulingEngine.moveToBacklog(
      task,
      updatedAt: updatedAt,
    );

    return SchedulingResult(
      tasks: List<Task>.unmodifiable(
        input.tasks.map((current) {
          if (current.id == task.id) {
            return movedTask;
          }

          return current;
        }),
      ),
      notices: [
        SchedulingNotice(
          'Flexible task moved to backlog.',
          type: SchedulingNoticeType.moved,
          taskId: task.id,
        ),
      ],
      changes: [
        SchedulingChange(
          taskId: task.id,
          previousStart: task.scheduledStart,
          previousEnd: task.scheduledEnd,
          nextStart: null,
          nextEnd: null,
        ),
      ],
    );
  }
}

Task? _taskById(List<Task> tasks, String taskId) {
  for (final task in tasks) {
    if (task.id == taskId) {
      return task;
    }
  }

  return null;
}
