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

part of '../../child_tasks.dart';

/// Creates direct child tasks from ordered child-entry rows.
class ChildTaskBreakUpService {
  /// Creates a `ChildTaskBreakUpService` 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 ChildTaskBreakUpService();

  /// Break `request.parentTaskId` into direct child tasks.
  ///
  /// This service validates the full requested set before returning any
  /// mutations. Persistence and transaction wiring are handled by later blocks.
  ChildTaskBreakUpResult breakUp({
    required List<Task> tasks,
    required ChildTaskBreakUpRequest request,
  }) {
    logger.debug(
        () => 'Breaking up parent task. parentTaskId=${request.parentTaskId} '
            'entryCount=${request.entries.length} taskCount=${tasks.length} '
            'operationId=${request.operationId}');
    final parent = _taskById(tasks, request.parentTaskId);
    if (parent == null) {
      logger.warn(() => 'Child task break-up failed: parent not found. '
          'parentTaskId=${request.parentTaskId} operationId=${request.operationId}');
      throw ArgumentError.value(
        request.parentTaskId,
        'parentTaskId',
        'Parent not found.',
      );
    }
    if (request.entries.isEmpty) {
      logger.warn(() => 'Child task break-up failed: no child entries. '
          'parentTaskId=${request.parentTaskId} operationId=${request.operationId}');
      throw ArgumentError.value(
        request.entries,
        'entries',
        'At least one child task is required.',
      );
    }

    final existingIds = tasks.map((task) => task.id).toSet();
    final requestedIds = <String>{};
    for (final entry in request.entries) {
      final childId = entry.id.trim();
      if (childId.isEmpty) {
        logger.warn(() => 'Child task break-up failed: blank child id. '
            'parentTaskId=${parent.id} operationId=${request.operationId}');
        throw ArgumentError.value(entry.id, 'id', 'Child id is required.');
      }
      if (childId == parent.id) {
        logger
            .warn(() => 'Child task break-up failed: child id matched parent. '
                'parentTaskId=${parent.id} operationId=${request.operationId}');
        throw ArgumentError.value(
          entry.id,
          'id',
          'Child id cannot match the parent id.',
        );
      }
      if (!requestedIds.add(childId)) {
        logger.warn(() => 'Child task break-up failed: duplicate child id. '
            'parentTaskId=${parent.id} childTaskId=$childId '
            'operationId=${request.operationId}');
        throw ArgumentError.value(
          entry.id,
          'id',
          'Child ids must be unique within one break-up command.',
        );
      }
      if (existingIds.contains(childId)) {
        logger
            .warn(() => 'Child task break-up failed: child id already exists. '
                'parentTaskId=${parent.id} childTaskId=$childId '
                'operationId=${request.operationId}');
        throw ArgumentError.value(
          entry.id,
          'id',
          'Child id is already used by another task.',
        );
      }
    }

    final childTasks = request.entries
        .map((entry) => entry.toTask(parent: parent))
        .toList(growable: false);
    logger.debug(() => 'Parent task break-up produced children. '
        'parentTaskId=${parent.id} childCount=${childTasks.length} '
        'operationId=${request.operationId}');

    return ChildTaskBreakUpResult(
      tasks: List<Task>.unmodifiable([...tasks, ...childTasks]),
      parentTask: parent,
      childTasks: childTasks,
      operationId: request.operationId,
      createdChildTaskIds:
          childTasks.map((child) => child.id).toList(growable: false),
    );
  }
}
