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

/// Tests Backlog board screen card rendering.
library;

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:scheduler_core/scheduler_core.dart';

import 'package:focus_flow_flutter/controllers/backlog_board_controller.dart';
import 'package:focus_flow_flutter/controllers/scheduler_command_controller.dart';
import 'package:focus_flow_flutter/controllers/selected_date_controller.dart';
import 'package:focus_flow_flutter/theme/focus_flow_theme.dart';
import 'package:focus_flow_flutter/theme/focus_flow_tokens.dart';
import 'package:focus_flow_flutter/widgets/backlog/backlog_board_screen.dart';

/// Runs Backlog board widget tests.
void main() {
  testWidgets('renders card metadata child previews and selected state', (
    tester,
  ) async {
    final detailReads = <String>[];
    final item = _boardItemWithChildren();
    final board = _boardWithItem(item);
    final selectedDates = SelectedDateController(
      initialDate: CivilDate(2026, 7, 7),
    );
    final controller = BacklogBoardController(
      readBoard: (_) async => ApplicationResult.success(board),
      readTaskDetail: (taskId, localDate) async {
        detailReads.add(taskId);
        return ApplicationResult.success(_detailFor(item));
      },
      selectedDates: selectedDates,
      dateLabelFor: (date) => date.toIsoString(),
    );
    addTearDown(controller.dispose);
    addTearDown(selectedDates.dispose);

    await controller.load();
    await _pumpBacklogScreen(tester, controller);

    expect(find.text('Do Next'), findsOneWidget);
    expect(find.text('Need Time Block'), findsOneWidget);
    expect(find.text('Break Up First'), findsOneWidget);
    expect(find.text('Not Now'), findsOneWidget);
    final card = find.byKey(const ValueKey('backlog-card-plan-week'));
    expect(card, findsOneWidget);
    expect(find.text('Plan next week'), findsOneWidget);
    expect(find.text('Outline priorities + tasks'), findsOneWidget);
    expect(
      find.descendant(of: card, matching: find.text('30 min')),
      findsOneWidget,
    );
    expect(
      find.descendant(of: card, matching: find.text('Work')),
      findsOneWidget,
    );
    expect(
      find.descendant(of: card, matching: find.text('High')),
      findsOneWidget,
    );
    expect(
      find.descendant(of: card, matching: find.text('Difficulty')),
      findsOneWidget,
    );
    expect(
      find.descendant(of: card, matching: find.text('Reward')),
      findsOneWidget,
    );
    expect(
      find.byKey(const ValueKey('backlog-child-define-weekly-goals')),
      findsOneWidget,
    );
    expect(find.text('Define weekly goals'), findsOneWidget);
    expect(find.text('15 min'), findsOneWidget);

    await tester.tap(find.byKey(const ValueKey('backlog-card-plan-week')));
    await tester.pumpAndSettle();

    expect(detailReads, ['plan-week']);
    expect(controller.selectedTaskId, 'plan-week');
    final cardContainer = tester.widget<AnimatedContainer>(
      find.byKey(const ValueKey('backlog-card-container-plan-week')),
    );
    final decoration = cardContainer.decoration! as BoxDecoration;
    final border = decoration.border! as Border;
    expect(border.top.color, FocusFlowTokens.accentMagenta);
  });

  testWidgets('drawer overlays board and close clears selected state', (
    tester,
  ) async {
    final item = _boardItemWithChildren();
    final board = _boardWithItem(item);
    final selectedDates = SelectedDateController(
      initialDate: CivilDate(2026, 7, 7),
    );
    final controller = BacklogBoardController(
      readBoard: (_) async => ApplicationResult.success(board),
      readTaskDetail: (taskId, localDate) async {
        return ApplicationResult.success(_detailFor(item));
      },
      selectedDates: selectedDates,
      dateLabelFor: (date) => date.toIsoString(),
    );
    addTearDown(controller.dispose);
    addTearDown(selectedDates.dispose);

    await controller.load();
    await _pumpBacklogScreen(tester, controller);

    final cardFinder = find.byKey(const ValueKey('backlog-card-plan-week'));
    final beforeOpen = tester.getTopLeft(cardFinder);

    await tester.tap(cardFinder);
    await tester.pumpAndSettle();

    final drawer = find.byKey(const ValueKey('backlog-task-drawer'));
    expect(drawer, findsOneWidget);
    expect(
      find.descendant(of: drawer, matching: find.text('Plan next week')),
      findsOneWidget,
    );
    expect(tester.getTopLeft(cardFinder), beforeOpen);
    expect(controller.selectedTaskId, 'plan-week');

    await tester.tap(find.byKey(const ValueKey('backlog-drawer-close-button')));
    await tester.pumpAndSettle();

    expect(drawer, findsNothing);
    expect(controller.selectedTaskId, isNull);
    final cardContainer = tester.widget<AnimatedContainer>(
      find.byKey(const ValueKey('backlog-card-container-plan-week')),
    );
    final decoration = cardContainer.decoration! as BoxDecoration;
    final border = decoration.border! as Border;
    expect(border.top.color, isNot(FocusFlowTokens.accentMagenta));
  });

  testWidgets('detail read failure stays inside drawer overlay', (
    tester,
  ) async {
    final item = _boardItemWithChildren();
    final board = _boardWithItem(item);
    final selectedDates = SelectedDateController(
      initialDate: CivilDate(2026, 7, 7),
    );
    final controller = BacklogBoardController(
      readBoard: (_) async => ApplicationResult.success(board),
      readTaskDetail: (taskId, localDate) async {
        return ApplicationResult.failure(
          const ApplicationFailure(
            code: ApplicationFailureCode.notFound,
            detailCode: 'taskNotFound',
          ),
        );
      },
      selectedDates: selectedDates,
      dateLabelFor: (date) => date.toIsoString(),
    );
    addTearDown(controller.dispose);
    addTearDown(selectedDates.dispose);

    await controller.load();
    await _pumpBacklogScreen(tester, controller);

    await tester.tap(find.byKey(const ValueKey('backlog-card-plan-week')));
    await tester.pumpAndSettle();

    final drawer = find.byKey(const ValueKey('backlog-task-drawer'));
    expect(drawer, findsOneWidget);
    expect(find.text('Could not load task details'), findsOneWidget);
    expect(find.text('taskNotFound'), findsOneWidget);
    expect(find.text('Could not load backlog'), findsNothing);
    expect(find.text('Plan next week'), findsOneWidget);
    expect(controller.selectedTaskId, 'plan-week');
  });

  testWidgets('new task creates backlog item through command controller', (
    tester,
  ) async {
    final harness = _BacklogCommandHarness.empty();
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    await tester.tap(find.byKey(const ValueKey('backlog-new-task-button')));
    await tester.pumpAndSettle();
    await tester.enterText(
      find.byKey(const ValueKey('backlog-new-task-title-field')),
      'Draft outline',
    );
    await tester.tap(
      find.byKey(const ValueKey('backlog-new-task-create-button')),
    );
    await tester.pumpAndSettle();

    expect(harness.commandController.state, isA<SchedulerCommandSuccess>());
    expect(find.text('Task captured'), findsOneWidget);
    expect(find.text('Draft outline'), findsOneWidget);
  });

  testWidgets('new task dialog validates an empty title without writing', (
    tester,
  ) async {
    final harness = _BacklogCommandHarness.empty();
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    await tester.tap(find.byKey(const ValueKey('backlog-new-task-button')));
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-new-task-create-button')),
    );
    await tester.pumpAndSettle();

    expect(find.text('Title is required.'), findsOneWidget);
    expect(harness.commandController.state, isA<SchedulerCommandIdle>());
    expect(harness.store.currentTasks, isEmpty);
  });

  testWidgets('schedule action uses command controller and refreshes board', (
    tester,
  ) async {
    final harness = _BacklogCommandHarness.withTasks([
      _domainBacklogTask(
        id: 'plan-week',
        title: 'Plan next week',
        durationMinutes: 30,
      ),
    ]);
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    expect(find.text('Plan next week'), findsOneWidget);

    await tester.tap(find.byKey(const ValueKey('backlog-card-plan-week')));
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-drawer-schedule-button')),
    );
    await tester.pumpAndSettle();

    expect(harness.commandController.state, isA<SchedulerCommandSuccess>());
    expect(find.text('Task scheduled'), findsOneWidget);
    expect(find.byKey(const ValueKey('backlog-task-drawer')), findsNothing);
    expect(find.byKey(const ValueKey('backlog-card-plan-week')), findsNothing);
  });

  testWidgets(
    'schedule action explains missing duration without command write',
    (tester) async {
      final harness = _BacklogCommandHarness.withTasks([
        _domainBacklogTask(
          id: 'missing-duration',
          title: 'Estimate me',
          durationMinutes: null,
        ),
      ]);
      addTearDown(harness.dispose);

      await harness.boardController.load();
      await _pumpBacklogScreen(
        tester,
        harness.boardController,
        commandController: harness.commandController,
      );

      await harness.boardController.selectTask('missing-duration');
      await tester.pumpAndSettle();
      await tester.tap(
        find.byKey(const ValueKey('backlog-drawer-schedule-button')),
      );
      await tester.pumpAndSettle();

      expect(find.text('Add an estimate before scheduling.'), findsOneWidget);
      expect(harness.commandController.state, isA<SchedulerCommandIdle>());
      expect(find.byKey(const ValueKey('backlog-task-drawer')), findsOneWidget);
    },
  );

  testWidgets('break up action creates child tasks and refreshes board', (
    tester,
  ) async {
    final harness = _BacklogCommandHarness.withTasks([
      _domainBacklogTask(
        id: 'outline-report',
        title: 'Outline report',
        durationMinutes: 30,
      ),
    ]);
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    await tester.tap(find.byKey(const ValueKey('backlog-card-outline-report')));
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-drawer-break-up-button')),
    );
    await tester.pumpAndSettle();

    expect(find.text('Break up task'), findsOneWidget);
    await tester.enterText(
      find.byKey(const ValueKey('backlog-break-up-title-field-0')),
      'Draft sections',
    );
    await tester.enterText(
      find.byKey(const ValueKey('backlog-break-up-duration-field-0')),
      '15',
    );
    await tester.enterText(
      find.byKey(const ValueKey('backlog-break-up-title-field-1')),
      'Review outline',
    );
    await tester.tap(
      find.byKey(const ValueKey('backlog-break-up-save-button')),
    );
    await tester.pumpAndSettle();

    expect(harness.commandController.state, isA<SchedulerCommandSuccess>());
    expect(find.text('Task broken up'), findsOneWidget);
    expect(find.text('Draft sections'), findsWidgets);
    expect(find.text('Review outline'), findsWidgets);
    expect(
      harness.store.currentTasks.where((task) => task.parentTaskId != null),
      hasLength(2),
    );
  });

  testWidgets('break up dialog validates two child titles', (tester) async {
    final harness = _BacklogCommandHarness.withTasks([
      _domainBacklogTask(
        id: 'plan-event',
        title: 'Plan event',
        durationMinutes: 30,
      ),
    ]);
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    await tester.tap(find.byKey(const ValueKey('backlog-card-plan-event')));
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-drawer-break-up-button')),
    );
    await tester.pumpAndSettle();
    await tester.enterText(
      find.byKey(const ValueKey('backlog-break-up-title-field-0')),
      'Choose date',
    );
    await tester.tap(
      find.byKey(const ValueKey('backlog-break-up-save-button')),
    );
    await tester.pumpAndSettle();

    expect(find.text('Add at least two titled steps.'), findsOneWidget);
    expect(harness.commandController.state, isA<SchedulerCommandIdle>());
    expect(
      harness.store.currentTasks.where((task) => task.parentTaskId != null),
      isEmpty,
    );
  });

  testWidgets('push to someday refreshes selected detail and board bucket', (
    tester,
  ) async {
    final harness = _BacklogCommandHarness.withTasks([
      _domainBacklogTask(
        id: 'maybe-later',
        title: 'Maybe later',
        durationMinutes: 30,
      ),
    ]);
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    await tester.tap(find.byKey(const ValueKey('backlog-card-maybe-later')));
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-drawer-push-someday-button')),
    );
    await _settleAsyncUi(tester);

    expect(harness.commandController.state, isA<SchedulerCommandSuccess>());
    expect(find.text('Moved to Someday'), findsOneWidget);
    expect(
      _taskById(harness.store.currentTasks, 'maybe-later').backlogTags,
      contains(BacklogTag.wishlist),
    );
    expect(harness.boardController.selectedTaskDetail?.tags, ['someday']);
    final state = harness.boardController.state as BacklogBoardReady;
    final notNow = state.data.columns.singleWhere(
      (column) => column.bucket == BacklogBoardBucket.notNow,
    );
    expect(notNow.items.single.taskId, 'maybe-later');
  });

  testWidgets('remove action can cancel then archive active backlog item', (
    tester,
  ) async {
    final harness = _BacklogCommandHarness.withTasks([
      _domainBacklogTask(
        id: 'archive-me',
        title: 'Archive me',
        durationMinutes: 30,
      ),
    ]);
    addTearDown(harness.dispose);

    await harness.boardController.load();
    await _pumpBacklogScreen(
      tester,
      harness.boardController,
      commandController: harness.commandController,
    );

    await tester.tap(find.byKey(const ValueKey('backlog-card-archive-me')));
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-drawer-remove-button')),
    );
    await tester.pumpAndSettle();

    expect(find.text('Remove from Backlog?'), findsOneWidget);
    await tester.tap(find.text('Cancel'));
    await tester.pumpAndSettle();

    expect(harness.commandController.state, isA<SchedulerCommandIdle>());
    expect(
      _taskById(harness.store.currentTasks, 'archive-me').status,
      TaskStatus.backlog,
    );
    expect(find.byKey(const ValueKey('backlog-task-drawer')), findsOneWidget);

    await tester.tap(
      find.byKey(const ValueKey('backlog-drawer-remove-button')),
    );
    await tester.pumpAndSettle();
    await tester.tap(
      find.byKey(const ValueKey('backlog-remove-confirm-button')),
    );
    await _settleAsyncUi(tester);

    expect(harness.commandController.state, isA<SchedulerCommandSuccess>());
    expect(find.text('Removed from active Backlog.'), findsOneWidget);
    expect(find.byKey(const ValueKey('backlog-task-drawer')), findsNothing);
    expect(find.byKey(const ValueKey('backlog-card-archive-me')), findsNothing);
    expect(
      _taskById(harness.store.currentTasks, 'archive-me').status,
      TaskStatus.noLongerRelevant,
    );
  });

  testWidgets('controls update board read requests', (tester) async {
    final requests = <BacklogBoardReadRequest>[];
    final item = _boardItemWithChildren();
    final board = _boardWithItem(
      item,
      projectOptions: const [
        ProjectOptionReadModel(
          projectId: 'work',
          name: 'Work',
          colorToken: 'project-work',
          isArchived: false,
        ),
      ],
    );
    final selectedDates = SelectedDateController(
      initialDate: CivilDate(2026, 7, 7),
    );
    final controller = BacklogBoardController(
      readBoard: (request) async {
        requests.add(request);
        return ApplicationResult.success(board);
      },
      readTaskDetail: (taskId, localDate) async {
        return ApplicationResult.success(_detailFor(item));
      },
      selectedDates: selectedDates,
      dateLabelFor: (date) => date.toIsoString(),
    );
    addTearDown(controller.dispose);
    addTearDown(selectedDates.dispose);

    await controller.load();
    await _pumpBacklogScreen(tester, controller);

    await tester.enterText(
      find.byKey(const ValueKey('backlog-search-field')),
      'course',
    );
    await tester.pumpAndSettle();

    expect(requests.last.searchText, 'course');
    expect(find.byKey(const ValueKey('backlog-search-clear')), findsOneWidget);

    await tester.tap(find.byKey(const ValueKey('backlog-search-clear')));
    await tester.pumpAndSettle();

    expect(requests.last.searchText, isNull);

    await tester.tap(find.byKey(const ValueKey('backlog-sort-menu-button')));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Reward vs Effort').last);
    await tester.pumpAndSettle();

    expect(requests.last.sortKey, BacklogSortKey.rewardVsEffort);
    expect(find.text('Sort: Reward'), findsOneWidget);

    await tester.tap(find.byKey(const ValueKey('backlog-group-menu-button')));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Project').last);
    await tester.pumpAndSettle();

    expect(requests.last.groupMode, BacklogBoardGroupMode.project);
    expect(find.text('Group: Project'), findsOneWidget);

    await tester.tap(find.byKey(const ValueKey('backlog-filter-button')));
    await tester.pumpAndSettle();

    Future<void> tapFilterCheckbox(String label) async {
      final checkbox = find.widgetWithText(CheckboxListTile, label);
      await tester.ensureVisible(checkbox);
      await tester.tap(checkbox);
      await tester.pump();
    }

    await tapFilterCheckbox('High priority');
    await tapFilterCheckbox('Work');
    await tapFilterCheckbox('0-30 min');
    await tapFilterCheckbox('Need Time Block');
    await tapFilterCheckbox('No reward set');
    await tester.tap(find.text('Apply'));
    await tester.pumpAndSettle();

    expect(requests.last.filters, [BacklogFilter.noRewardSet]);
    expect(
      requests.last.boardFilters.priorityGroups,
      contains(BacklogBoardPriorityFilter.high),
    );
    expect(requests.last.boardFilters.projectIds, contains('work'));
    expect(
      requests.last.boardFilters.durationBuckets,
      contains(BacklogBoardDurationFilter.zeroToThirty),
    );
    expect(
      requests.last.boardFilters.buckets,
      contains(BacklogBoardBucket.needTimeBlock),
    );
    expect(find.text('Filters 5'), findsOneWidget);

    await tester.tap(find.byKey(const ValueKey('backlog-filter-button')));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Clear'));
    await tester.pumpAndSettle();

    expect(requests.last.filters, isEmpty);
    expect(requests.last.boardFilters.isEmpty, isTrue);
    expect(find.text('Filters'), findsOneWidget);
  });

  testWidgets('layout avoids overflow at review desktop widths', (
    tester,
  ) async {
    final item = _boardItemWithChildren();
    final board = _boardWithItem(item);
    final selectedDates = SelectedDateController(
      initialDate: CivilDate(2026, 7, 7),
    );
    final controller = BacklogBoardController(
      readBoard: (_) async => ApplicationResult.success(board),
      readTaskDetail: (taskId, localDate) async {
        return ApplicationResult.success(_detailFor(item));
      },
      selectedDates: selectedDates,
      dateLabelFor: (date) => date.toIsoString(),
    );
    addTearDown(controller.dispose);
    addTearDown(selectedDates.dispose);

    await controller.load();
    for (final surfaceSize in _reviewSurfaceSizes) {
      await _pumpBacklogScreen(
        tester,
        controller,
        surfaceSize: surfaceSize,
        frameSize: Size(surfaceSize.width - 24, surfaceSize.height - 40),
      );
      expect(
        tester.takeException(),
        isNull,
        reason: 'Unexpected layout exception at $surfaceSize.',
      );
    }

    await controller.selectTask('plan-week');
    await _pumpBacklogScreen(
      tester,
      controller,
      surfaceSize: const Size(1100, 760),
      frameSize: const Size(1076, 720),
    );
    expect(find.byKey(const ValueKey('backlog-task-drawer')), findsOneWidget);
    expect(tester.takeException(), isNull);
  });
}

/// Pumps [controller] inside the dark FocusFlow app shell test harness.
Future<void> _pumpBacklogScreen(
  WidgetTester tester,
  BacklogBoardController controller, {
  SchedulerCommandController? commandController,
  Size surfaceSize = const Size(1320, 820),
  Size frameSize = const Size(1300, 780),
}) async {
  await tester.binding.setSurfaceSize(surfaceSize);
  addTearDown(() => tester.binding.setSurfaceSize(null));
  await tester.pumpWidget(
    MaterialApp(
      theme: FocusFlowTheme.dark(),
      home: Scaffold(
        body: SizedBox(
          width: frameSize.width,
          height: frameSize.height,
          child: BacklogBoardScreen(
            controller: controller,
            commandController: commandController,
          ),
        ),
      ),
    ),
  );
  await tester.pumpAndSettle();
}

/// Desktop widths used for final Backlog layout smoke tests.
const _reviewSurfaceSizes = [
  Size(1672, 940),
  Size(1440, 860),
  Size(1280, 820),
  Size(1100, 760),
];

/// Lets unawaited UI command futures finish and repaint their final state.
Future<void> _settleAsyncUi(WidgetTester tester) async {
  await tester.pumpAndSettle();
  await tester.runAsync(() async {
    await Future<void>.delayed(Duration.zero);
  });
  await tester.pumpAndSettle();
}

/// Test harness that wires Backlog UI controllers to in-memory scheduler use cases.
class _BacklogCommandHarness {
  /// Creates a harness from initial [tasks].
  _BacklogCommandHarness._({required List<Task> tasks})
    : selectedDates = SelectedDateController(initialDate: _testDate),
      store = InMemoryApplicationUnitOfWork(
        initialProjects: [
          ProjectProfile(id: 'home', name: 'Home', colorKey: 'home'),
        ],
        initialOwnerSettings: [
          OwnerSettings(ownerId: _ownerId, timeZoneId: 'UTC'),
        ],
        initialTasks: tasks,
      ) {
    managementUseCases = V1ApplicationManagementUseCases(
      applicationStore: store,
    );
    commandUseCases = V1ApplicationCommandUseCases(
      applicationStore: store,
      timeZoneResolver: const FixedOffsetTimeZoneResolver.utc(),
    );
    boardController = BacklogBoardController(
      selectedDates: selectedDates,
      dateLabelFor: (date) => date.toIsoString(),
      readBoard: (request) {
        return managementUseCases.getBacklogBoard(
          GetBacklogBoardRequest(
            context: _context('read-board'),
            localDate: request.localDate,
            searchText: request.searchText,
            filters: request.filters,
            boardFilters: request.boardFilters,
            sortKey: request.sortKey,
            groupMode: request.groupMode,
          ),
        );
      },
      readTaskDetail: (taskId, localDate) {
        return managementUseCases.getBacklogTaskDetail(
          GetBacklogTaskDetailRequest(
            context: _context('read-detail-$taskId'),
            localDate: localDate,
            taskId: taskId,
          ),
        );
      },
    );
    commandController = SchedulerCommandController(
      commands: commandUseCases,
      contextFor: _context,
      selectedDate: () => selectedDates.date,
      refreshReads: boardController.load,
      quickCaptureProjectId: 'home',
      operationIdPrefix: 'backlog-widget-command',
      now: () => _now,
    );
  }

  /// Creates an empty harness.
  factory _BacklogCommandHarness.empty() {
    return _BacklogCommandHarness._(tasks: const []);
  }

  /// Creates a harness seeded with [tasks].
  factory _BacklogCommandHarness.withTasks(List<Task> tasks) {
    return _BacklogCommandHarness._(tasks: tasks);
  }

  /// Selected date controller.
  final SelectedDateController selectedDates;

  /// In-memory scheduler application store.
  final InMemoryApplicationUnitOfWork store;

  /// Management use cases.
  late final V1ApplicationManagementUseCases managementUseCases;

  /// Command use cases.
  late final V1ApplicationCommandUseCases commandUseCases;

  /// Backlog board controller.
  late final BacklogBoardController boardController;

  /// Scheduler command controller.
  late final SchedulerCommandController commandController;

  /// Releases harness-owned controllers.
  void dispose() {
    commandController.dispose();
    boardController.dispose();
    selectedDates.dispose();
  }
}

/// Owner id used by command integration widget tests.
const _ownerId = 'owner-1';

/// Selected date used by command integration widget tests.
final _testDate = CivilDate(2026, 7, 7);

/// Stable command clock used by command integration widget tests.
final _now = DateTime.utc(2026, 7, 7, 15);

/// Creates an operation context for [operationId].
ApplicationOperationContext _context(String operationId, {DateTime? now}) {
  return ApplicationOperationContext.start(
    operationId: operationId,
    ownerTimeZone: OwnerTimeZoneContext(ownerId: _ownerId, timeZoneId: 'UTC'),
    clock: FixedClock(now ?? _now),
    idGenerator: SequentialIdGenerator(prefix: operationId),
  );
}

/// Builds a domain Backlog task for command integration widget tests.
Task _domainBacklogTask({
  required String id,
  required String title,
  required int? durationMinutes,
}) {
  return Task(
    id: id,
    title: title,
    projectId: 'home',
    type: TaskType.flexible,
    status: TaskStatus.backlog,
    priority: PriorityLevel.medium,
    reward: RewardLevel.medium,
    difficulty: DifficultyLevel.easy,
    durationMinutes: durationMinutes,
    createdAt: _now,
    updatedAt: _now,
  );
}

/// Finds the task with [taskId] in [tasks].
Task _taskById(List<Task> tasks, String taskId) {
  return tasks.singleWhere((task) => task.id == taskId);
}

/// Builds a board item with child preview rows for widget tests.
BacklogBoardItemReadModel _boardItemWithChildren() {
  return BacklogBoardItemReadModel(
    taskId: 'plan-week',
    title: 'Plan next week',
    subtitle: 'Outline priorities + tasks',
    projectId: 'work',
    projectName: 'Work',
    projectColorToken: 'project-work',
    durationMinutes: 30,
    priority: PriorityLevel.high,
    reward: RewardLevel.high,
    difficulty: DifficultyLevel.medium,
    bucket: BacklogBoardBucket.breakUpFirst,
    bucketReason: 'Already has child-task planning.',
    createdAt: DateTime.utc(2026, 7, 7),
    updatedAt: DateTime.utc(2026, 7, 7),
    childPreview: const [
      BacklogChildPreviewReadModel(
        taskId: 'define-weekly-goals',
        title: 'Define weekly goals',
        durationMinutes: 15,
        isCompleted: false,
      ),
    ],
    tags: const ['someday'],
  );
}

/// Builds a Backlog board containing [item] and the other stable empty columns.
BacklogBoardQueryResult _boardWithItem(
  BacklogBoardItemReadModel item, {
  List<ProjectOptionReadModel> projectOptions = const [],
}) {
  return BacklogBoardQueryResult(
    ownerId: 'owner-1',
    localDate: CivilDate(2026, 7, 7),
    columns: [
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.doNext,
        title: 'Do Next',
        subtitle: 'Ready when you have a small opening.',
        accentToken: 'backlog-do-next',
        items: const [],
      ),
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.needTimeBlock,
        title: 'Need Time Block',
        subtitle: 'Give these a protected block.',
        accentToken: 'backlog-time-block',
        items: const [],
      ),
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.breakUpFirst,
        title: 'Break Up First',
        subtitle: 'Split these before scheduling.',
        accentToken: 'backlog-break-up',
        items: [item],
      ),
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.notNow,
        title: 'Not Now',
        subtitle: 'Keep these out of today pressure.',
        accentToken: 'backlog-not-now',
        items: const [],
      ),
    ],
    summary: BacklogBoardSummaryReadModel(
      totalTaskCount: 1,
      totalEstimatedMinutes: item.durationMinutes ?? 0,
      priorityDistribution: BacklogDistributionReadModel(
        title: 'By priority',
        entries: const [
          BacklogDistributionEntryReadModel(
            id: 'high',
            label: 'High',
            count: 1,
            percentage: 1,
            colorToken: 'priority-high',
          ),
        ],
      ),
      projectDistribution: BacklogDistributionReadModel(
        title: 'By project',
        entries: const [
          BacklogDistributionEntryReadModel(
            id: 'work',
            label: 'Work',
            count: 1,
            percentage: 1,
            colorToken: 'project-work',
          ),
        ],
      ),
      durationDistribution: BacklogDistributionReadModel(
        title: 'Duration distribution',
        entries: const [],
      ),
      planningTip: 'Pick one task.',
    ),
    projectOptions: projectOptions,
  );
}

/// Builds selected-task detail for [item].
BacklogTaskDetailReadModel _detailFor(BacklogBoardItemReadModel item) {
  return BacklogTaskDetailReadModel(
    item: item,
    addedLabel: 'Added today',
    tags: item.tags,
    projectOptions: const [],
    suggestedSlots: const [],
  );
}
