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

part of '../../../scheduling_engine.dart';

/// In-memory input for scheduling operations.
///
/// This is the complete snapshot the pure scheduling engine needs. It contains
/// tasks plus the fixed intervals the scheduler must avoid. It deliberately does
/// not know where the data came from: UI state, a database, tests, or generated
/// examples can all build this same object.
class SchedulingInput {
  /// Creates a `SchedulingInput` 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.
  SchedulingInput({
    required List<Task> tasks,
    required this.window,
    List<TimeInterval> lockedIntervals = const <TimeInterval>[],
    List<TimeInterval> requiredVisibleIntervals = const <TimeInterval>[],
  })  : tasks = List<Task>.unmodifiable(tasks),
        lockedIntervals = List<TimeInterval>.unmodifiable(lockedIntervals),
        requiredVisibleIntervals =
            List<TimeInterval>.unmodifiable(requiredVisibleIntervals);

  /// All tasks available to this operation. The scheduler returns a replacement
  /// list rather than mutating this one.
  final List<Task> tasks;

  /// Date/time range that the operation is allowed to plan inside.
  final SchedulingWindow window;

  /// External locked time intervals, usually produced by `locked_time.dart`.
  final List<TimeInterval> lockedIntervals;

  /// Extra fixed visible intervals supplied by the caller. This lets UI/backend
  /// code reserve required time even when that time is not represented as a
  /// [Task] in the current list.
  final List<TimeInterval> requiredVisibleIntervals;

  /// Tasks that the flexible movement algorithms are allowed to consider.
  List<Task> get flexibleTasks {
    return tasks.where((task) => task.isFlexible).toList(growable: false);
  }

  /// Central occupancy classification for this scheduling snapshot.
  List<OccupancyEntry> get occupancyEntries {
    return _occupancyPolicy.classify(
      tasks: tasks,
      lockedIntervals: lockedIntervals,
      requiredVisibleIntervals: requiredVisibleIntervals,
    );
  }

  /// Planned flexible task records that automatic scheduling may move.
  List<Task> get movablePlannedFlexibleTasks {
    return occupancyEntries
        .where((entry) => entry.isMovablePlannedFlexible)
        .map((entry) => entry.task)
        .whereType<Task>()
        .toList(growable: false);
  }

  /// Locked task records in [tasks], if the caller represents locked time as
  /// tasks instead of only passing [lockedIntervals].
  List<Task> get lockedTasks {
    return tasks.where((task) => task.isLocked).toList(growable: false);
  }

  /// Critical and inflexible task records that should block flexible placement.
  List<Task> get requiredVisibleTasks {
    return tasks
        .where((task) => task.isRequiredVisible)
        .toList(growable: false);
  }

  /// Scheduled intervals for flexible tasks only. Useful for analysis/debugging.
  List<TimeInterval> get flexibleIntervals {
    return _scheduledIntervalsFor(flexibleTasks);
  }

  /// All intervals that flexible scheduling must avoid.
  ///
  /// This is derived from the central occupancy policy rather than from UI card
  /// categories. The result is sorted to make interval scanning deterministic.
  List<TimeInterval> get blockedIntervals {
    final intervals = occupancyEntries
        .where((entry) => entry.blocksAutomaticFlexiblePlacement)
        .map((entry) => entry.interval)
        .whereType<TimeInterval>()
        .toList(growable: false)
      ..sort((a, b) => a.start.compareTo(b.start));

    return List<TimeInterval>.unmodifiable(intervals);
  }

  /// Occupancy entries that should be surfaced as conflicts when overlapped.
  List<OccupancyEntry> get conflictReportingOccupancies {
    return occupancyEntries
        .where((entry) => entry.reportsConflict)
        .toList(growable: false);
  }
}
