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

/// Tests Timeline State behavior for the Scheduler Core package.
library;

import 'package:scheduler_core/scheduler_core.dart';
import 'package:test/test.dart';

/// Entry point for this executable Dart file.
/// It wires the local command, test, or application behavior needed by this repository without exposing additional public API.
void main() {
  group('Timeline item view model', () {
    final createdAt = DateTime(2026, 6, 20, 8);
    final start = DateTime(2026, 6, 20, 9);
    final end = DateTime(2026, 6, 20, 9, 30);
    final project = ProjectProfile(
      id: 'home',
      name: 'Home',
      colorKey: 'project-home',
    );
    final mapper = TimelineItemMapper.fromProjects([project]);

    test('maps task fields to display tokens', () {
      final task = scheduledTask(
        id: 'dishes',
        title: 'Wash dishes',
        projectId: project.id,
        type: TaskType.flexible,
        reward: RewardLevel.high,
        difficulty: DifficultyLevel.hard,
        createdAt: createdAt,
        start: start,
        end: end,
      );

      final item = mapper.fromTask(task);

      expect(item.id, 'dishes');
      expect(item.displayTitle, 'Wash dishes');
      expect(item.taskType, TaskType.flexible);
      expect(item.projectColorToken, 'project-home');
      expect(item.backgroundToken, TimelineBackgroundToken.flexible);
      expect(item.rewardIconToken, TimelineRewardIconToken.high);
      expect(item.difficultyIconToken, TimelineDifficultyIconToken.hard);
      expect(item.category, TimelineItemCategory.taskCard);
      expect(item.quickActions, [
        TimelineQuickAction.done,
        TimelineQuickAction.push,
        TimelineQuickAction.backlog,
        TimelineQuickAction.breakUp,
      ]);
    });

    test('background token maps from every task type', () {
      final expectedTokens = {
        TaskType.flexible: TimelineBackgroundToken.flexible,
        TaskType.inflexible: TimelineBackgroundToken.inflexible,
        TaskType.critical: TimelineBackgroundToken.critical,
        TaskType.locked: TimelineBackgroundToken.locked,
        TaskType.surprise: TimelineBackgroundToken.surprise,
        TaskType.freeSlot: TimelineBackgroundToken.freeSlot,
      };

      for (final entry in expectedTokens.entries) {
        final task = scheduledTask(
          id: entry.key.name,
          title: entry.key.name,
          projectId: project.id,
          type: entry.key,
          createdAt: createdAt,
          start: start,
          end: end,
        );

        expect(
          mapper.fromTask(task).backgroundToken,
          entry.value,
          reason: entry.key.name,
        );
      }
    });

    test('reward icon token handles every reward level plus not set', () {
      final expectedTokens = {
        RewardLevel.notSet: TimelineRewardIconToken.notSet,
        RewardLevel.veryLow: TimelineRewardIconToken.veryLow,
        RewardLevel.low: TimelineRewardIconToken.low,
        RewardLevel.medium: TimelineRewardIconToken.medium,
        RewardLevel.high: TimelineRewardIconToken.high,
        RewardLevel.veryHigh: TimelineRewardIconToken.veryHigh,
      };

      for (final entry in expectedTokens.entries) {
        final task = scheduledTask(
          id: entry.key.name,
          title: entry.key.name,
          projectId: project.id,
          type: TaskType.flexible,
          reward: entry.key,
          createdAt: createdAt,
          start: start,
          end: end,
        );

        expect(
          mapper.fromTask(task).rewardIconToken,
          entry.value,
          reason: entry.key.name,
        );
      }
    });

    test('difficulty icon token handles every difficulty level plus not set',
        () {
      final expectedTokens = {
        DifficultyLevel.notSet: TimelineDifficultyIconToken.notSet,
        DifficultyLevel.veryEasy: TimelineDifficultyIconToken.veryEasy,
        DifficultyLevel.easy: TimelineDifficultyIconToken.easy,
        DifficultyLevel.medium: TimelineDifficultyIconToken.medium,
        DifficultyLevel.hard: TimelineDifficultyIconToken.hard,
        DifficultyLevel.veryHard: TimelineDifficultyIconToken.veryHard,
      };

      for (final entry in expectedTokens.entries) {
        final task = scheduledTask(
          id: entry.key.name,
          title: entry.key.name,
          projectId: project.id,
          type: TaskType.flexible,
          difficulty: entry.key,
          createdAt: createdAt,
          start: start,
          end: end,
        );

        expect(
          mapper.fromTask(task).difficultyIconToken,
          entry.value,
          reason: entry.key.name,
        );
      }
    });

    test('flexible task cards display duration while retaining position', () {
      final task = scheduledTask(
        id: 'laundry',
        title: 'Fold laundry',
        projectId: project.id,
        type: TaskType.flexible,
        durationMinutes: 25,
        createdAt: createdAt,
        start: start,
        end: start.add(const Duration(minutes: 25)),
      );

      final item = mapper.fromTask(task);

      expect(item.showsExplicitTime, isFalse);
      expect(item.durationMinutes, 25);
      expect(item.start, start);
      expect(item.end, start.add(const Duration(minutes: 25)));
    });

    test('flexible duration can fall back to scheduled interval', () {
      final task = scheduledTask(
        id: 'admin',
        title: 'Handle admin',
        projectId: project.id,
        type: TaskType.flexible,
        createdAt: createdAt,
        start: start,
        end: start.add(const Duration(minutes: 45)),
      );

      final item = mapper.fromTask(task);

      expect(item.showsExplicitTime, isFalse);
      expect(item.durationMinutes, 45);
    });

    test('inflexible critical and locked items show explicit times', () {
      for (final type in [
        TaskType.inflexible,
        TaskType.critical,
        TaskType.locked,
      ]) {
        final task = scheduledTask(
          id: type.name,
          title: type.name,
          projectId: project.id,
          type: type,
          createdAt: createdAt,
          start: start,
          end: end,
        );

        final item = mapper.fromTask(task);

        expect(item.showsExplicitTime, isTrue, reason: type.name);
        expect(item.start, start, reason: type.name);
        expect(item.end, end, reason: type.name);
        expect(item.durationMinutes, isNull, reason: type.name);
      }
    });

    test('locked item is an overlay without normal task quick actions', () {
      final task = scheduledTask(
        id: 'sleep',
        title: 'Sleep',
        projectId: 'locked',
        type: TaskType.locked,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 0),
        end: DateTime(2026, 6, 20, 7),
      );

      final item = mapper.fromTask(task);

      expect(item.category, TimelineItemCategory.overlay);
      expect(item.projectColorToken, 'locked');
      expect(item.backgroundToken, TimelineBackgroundToken.locked);
      expect(item.quickActions, isEmpty);
    });

    test('hidden locked occurrences stay hidden until reveal mode is active',
        () {
      final occurrence = LockedBlockOccurrence(
        name: 'Work',
        interval: TimeInterval(
          start: DateTime(2026, 6, 20, 9),
          end: DateTime(2026, 6, 20, 17),
        ),
        hiddenByDefault: true,
        lockedBlockId: 'work-hours',
        projectId: project.id,
      );

      final hiddenItem = mapper.fromLockedOccurrence(
        occurrence,
        revealHiddenLockedBlocks: false,
      );
      final revealedItem = mapper.fromLockedOccurrence(
        occurrence,
        revealHiddenLockedBlocks: true,
      );

      expect(hiddenItem, isNull);
      expect(revealedItem, isNotNull);
      expect(revealedItem!.id, 'locked-block:work-hours');
      expect(revealedItem.displayTitle, 'Work');
      expect(revealedItem.start, DateTime(2026, 6, 20, 9));
      expect(revealedItem.end, DateTime(2026, 6, 20, 17));
      expect(revealedItem.showsExplicitTime, isTrue);
      expect(revealedItem.category, TimelineItemCategory.overlay);
      expect(revealedItem.projectColorToken, 'project-home');
      expect(revealedItem.quickActions, isEmpty);
    });

    test('visible locked occurrences can produce overlays without reveal mode',
        () {
      final occurrence = LockedBlockOccurrence(
        name: 'Appointment',
        interval: TimeInterval(
          start: DateTime(2026, 6, 20, 11),
          end: DateTime(2026, 6, 20, 12),
        ),
        hiddenByDefault: false,
        overrideId: 'appointment-override',
      );

      final item = mapper.fromLockedOccurrence(
        occurrence,
        revealHiddenLockedBlocks: false,
      );

      expect(item, isNotNull);
      expect(item!.id, 'locked-override:appointment-override');
      expect(item.displayTitle, 'Appointment');
      expect(item.category, TimelineItemCategory.overlay);
      expect(item.taskType, TaskType.locked);
      expect(item.backgroundToken, TimelineBackgroundToken.locked);
      expect(item.rewardIconToken, TimelineRewardIconToken.notSet);
      expect(item.difficultyIconToken, TimelineDifficultyIconToken.notSet);
      expect(item.showsExplicitTime, isTrue);
      expect(item.quickActions, isEmpty);
    });

    test('manual compact mode flag controls compact output selection', () {
      final current = scheduledTask(
        id: 'current',
        title: 'Current task',
        projectId: project.id,
        type: TaskType.flexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 9),
        end: DateTime(2026, 6, 20, 10),
      );
      final nextRequired = scheduledTask(
        id: 'next-required',
        title: 'Next appointment',
        projectId: project.id,
        type: TaskType.inflexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 11),
        end: DateTime(2026, 6, 20, 11, 30),
      );

      final disabled = mapper.compactStateForTasks(
        tasks: [current, nextRequired],
        now: DateTime(2026, 6, 20, 9, 15),
        manualCompactModeEnabled: false,
      );
      final enabled = mapper.compactStateForTasks(
        tasks: [current, nextRequired],
        now: DateTime(2026, 6, 20, 9, 15),
        manualCompactModeEnabled: true,
      );

      expect(disabled.manualCompactModeEnabled, isFalse);
      expect(disabled.compactItems, isEmpty);
      expect(disabled.fullTimelineVisible, isTrue);

      expect(enabled.manualCompactModeEnabled, isTrue);
      expect(enabled.currentItem!.id, 'current');
      expect(enabled.nextRequiredItem!.id, 'next-required');
      expect(enabled.compactItems.map((item) => item.id), [
        'current',
        'next-required',
      ]);
      expect(enabled.fullTimelineVisible, isFalse);
    });

    test('compact mode can include the next flexible task when requested', () {
      final required = scheduledTask(
        id: 'required',
        title: 'Required',
        projectId: project.id,
        type: TaskType.critical,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 10),
        end: DateTime(2026, 6, 20, 10, 30),
      );
      final flexible = scheduledTask(
        id: 'flexible',
        title: 'Flexible',
        projectId: project.id,
        type: TaskType.flexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 11),
        end: DateTime(2026, 6, 20, 11, 30),
      );

      final state = mapper.compactStateForTasks(
        tasks: [flexible, required],
        now: DateTime(2026, 6, 20, 9),
        manualCompactModeEnabled: true,
        includeNextFlexibleTask: true,
      );

      expect(state.currentItem, isNull);
      expect(state.nextRequiredItem!.id, 'required');
      expect(state.nextFlexibleItem!.id, 'flexible');
      expect(state.compactItems.map((item) => item.id), [
        'required',
        'flexible',
      ]);
    });

    test('compact mode does not activate after pushed or missed tasks', () {
      final pushed = scheduledTask(
        id: 'pushed',
        title: 'Pushed task',
        projectId: project.id,
        type: TaskType.flexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 14),
        end: DateTime(2026, 6, 20, 14, 30),
      );
      final missed = scheduledTask(
        id: 'missed',
        title: 'Missed appointment',
        projectId: project.id,
        type: TaskType.inflexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 8),
        end: DateTime(2026, 6, 20, 8, 30),
      ).copyWith(status: TaskStatus.missed);

      final state = mapper.compactStateForTasks(
        tasks: [pushed, missed],
        now: DateTime(2026, 6, 20, 9),
        manualCompactModeEnabled: false,
      );

      expect(state.manualCompactModeEnabled, isFalse);
      expect(state.compactItems, isEmpty);
      expect(state.fullTimelineVisible, isTrue);
    });

    test('compact mode ignores locked items unless overlay reveal is separate',
        () {
      final locked = scheduledTask(
        id: 'locked',
        title: 'Hidden blocked time',
        projectId: 'locked',
        type: TaskType.locked,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 9),
        end: DateTime(2026, 6, 20, 10),
      );
      final required = scheduledTask(
        id: 'required',
        title: 'Visible required task',
        projectId: project.id,
        type: TaskType.critical,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 10),
        end: DateTime(2026, 6, 20, 10, 30),
      );

      final state = mapper.compactStateForTasks(
        tasks: [locked, required],
        now: DateTime(2026, 6, 20, 9, 15),
        manualCompactModeEnabled: true,
      );

      expect(state.currentItem, isNull);
      expect(state.nextRequiredItem!.id, 'required');
      expect(state.compactItems.map((item) => item.id), ['required']);
    });

    test('compact mode returns only the reduced current and next items', () {
      final current = scheduledTask(
        id: 'current',
        title: 'Current',
        projectId: project.id,
        type: TaskType.flexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 9),
        end: DateTime(2026, 6, 20, 10),
      );
      final nextRequired = scheduledTask(
        id: 'next-required',
        title: 'Next required',
        projectId: project.id,
        type: TaskType.inflexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 10),
        end: DateTime(2026, 6, 20, 10, 30),
      );
      final laterRequired = scheduledTask(
        id: 'later-required',
        title: 'Later required',
        projectId: project.id,
        type: TaskType.critical,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 12),
        end: DateTime(2026, 6, 20, 12, 30),
      );
      final laterFlexible = scheduledTask(
        id: 'later-flexible',
        title: 'Later flexible',
        projectId: project.id,
        type: TaskType.flexible,
        createdAt: createdAt,
        start: DateTime(2026, 6, 20, 13),
        end: DateTime(2026, 6, 20, 13, 30),
      );

      final state = mapper.compactStateForTasks(
        tasks: [laterFlexible, laterRequired, nextRequired, current],
        now: DateTime(2026, 6, 20, 9, 15),
        manualCompactModeEnabled: true,
      );

      expect(state.compactItems.map((item) => item.id), [
        'current',
        'next-required',
      ]);
      expect(
        state.compactItems.map((item) => item.id),
        isNot(contains('later-required')),
      );
      expect(
        state.compactItems.map((item) => item.id),
        isNot(contains('later-flexible')),
      );
    });
  });
}

/// Runs the `scheduledTask` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
Task scheduledTask({
  required String id,
  required String title,
  required String projectId,
  required TaskType type,
  required DateTime createdAt,
  required DateTime start,
  required DateTime end,
  RewardLevel reward = RewardLevel.notSet,
  DifficultyLevel difficulty = DifficultyLevel.notSet,
  int? durationMinutes,
}) {
  return Task(
    id: id,
    title: title,
    projectId: projectId,
    type: type,
    status: TaskStatus.planned,
    reward: reward,
    difficulty: difficulty,
    durationMinutes: durationMinutes,
    scheduledStart: start,
    scheduledEnd: end,
    createdAt: createdAt,
    updatedAt: createdAt,
  );
}
