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

part of '../../reminder_policy.dart';

/// Resolves reminder profiles and reminder directives.
class ReminderPolicyService {
  /// Creates a `ReminderPolicyService` 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 ReminderPolicyService({
    this.fallbackProfile = ReminderProfile.gentle,
    this.occupancyPolicy = const OccupancyPolicy(),
  });

  /// Fallback used when no task override or project profile is available.
  final ReminderProfile fallbackProfile;

  /// Occupancy classifier used to detect active protected Free Slots.
  final OccupancyPolicy occupancyPolicy;

  /// Resolve effective profile without considering learned suggestions.
  EffectiveReminderProfile resolveEffectiveProfile({
    required Task task,
    ProjectProfile? project,
  }) {
    final override = task.reminderOverride;
    if (override != null) {
      logger.finer(() => 'Reminder profile resolved from task override. '
          'taskId=${task.id} profile=${override.name}');
      return EffectiveReminderProfile(
        profile: override,
        source: EffectiveReminderProfileSource.taskOverride,
      );
    }

    if (project != null) {
      logger.finer(() => 'Reminder profile resolved from project default. '
          'taskId=${task.id} projectId=${project.id} '
          'profile=${project.defaultReminderProfile.name}');
      return EffectiveReminderProfile(
        profile: project.defaultReminderProfile,
        source: EffectiveReminderProfileSource.projectDefault,
      );
    }

    logger.finer(() => 'Reminder profile resolved from fallback. '
        'taskId=${task.id} profile=${fallbackProfile.name}');
    return EffectiveReminderProfile(
      profile: fallbackProfile,
      source: EffectiveReminderProfileSource.fallback,
    );
  }

  /// Resolve one reminder directive at [now].
  ReminderDirective directiveForTask({
    required Task task,
    required DateTime now,
    ProjectProfile? project,
    Iterable<Task> contextTasks = const <Task>[],
  }) {
    logger.debug(() => 'Reminder directive evaluation started. '
        'taskId=${task.id} type=${task.type.name} status=${task.status.name} '
        'now=${now.toIso8601String()}');
    final effective = resolveEffectiveProfile(task: task, project: project);
    final activeFreeSlot = _activeProtectedFreeSlot(
      now: now,
      contextTasks: contextTasks,
    );
    if (activeFreeSlot != null) {
      logger.finer(() => 'Reminder directive found active protected free slot. '
          'taskId=${task.id} freeSlotTaskId=${activeFreeSlot.taskId} '
          'freeSlotEnd=${activeFreeSlot.interval?.end.toIso8601String()}');
    }

    return _directiveForTask(
      task: task,
      now: now,
      effective: effective,
      activeFreeSlot: activeFreeSlot,
    );
  }

  /// Runs the `_directiveForTask` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  ReminderDirective _directiveForTask({
    required Task task,
    required DateTime now,
    required EffectiveReminderProfile effective,
    required OccupancyEntry? activeFreeSlot,
  }) {
    final start = task.scheduledStart;
    final end = task.scheduledEnd;

    ReminderDirective build({
      required ReminderDirectiveAction action,
      required ReminderDirectiveReason reason,
      DateTime? nextEligibleAt,
    }) {
      logger.debug(() => 'Reminder directive evaluation finished. '
          'taskId=${task.id} action=${action.name} reason=${reason.name} '
          'profile=${effective.profile.name} source=${effective.source.name} '
          'hasSchedule=${start != null && end != null} '
          'nextEligibleAt=${nextEligibleAt?.toIso8601String()} '
          'protectedFreeSlotTaskId=${activeFreeSlot?.taskId}');
      return ReminderDirective(
        taskId: task.id,
        action: action,
        reason: reason,
        effectiveProfile: effective.profile,
        effectiveProfileSource: effective.source,
        evaluatedAt: now,
        scheduledStart: start,
        scheduledEnd: end,
        protectedFreeSlotTaskId: activeFreeSlot?.taskId,
        nextEligibleAt: nextEligibleAt,
      );
    }

    if (effective.profile == ReminderProfile.silent) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressSilentProfile,
      );
    }

    if (task.type == TaskType.locked) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressHiddenLockedTime,
      );
    }

    if (task.type == TaskType.freeSlot) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressFreeSlotRecord,
      );
    }

    if (!_isReminderEligibleStatus(task.status)) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressInactiveStatus,
      );
    }

    if (!_isReminderEligibleType(task.type)) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressUnsupportedTaskType,
      );
    }

    if (start == null || end == null || !start.isBefore(end)) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressNoScheduledWindow,
      );
    }

    if (now.isBefore(start)) {
      return build(
        action: ReminderDirectiveAction.defer,
        reason: ReminderDirectiveReason.deferUntilTaskWindow,
        nextEligibleAt: start,
      );
    }

    final protectedRestActive = activeFreeSlot != null;
    if (protectedRestActive && task.isFlexible) {
      return build(
        action: ReminderDirectiveAction.suppress,
        reason: ReminderDirectiveReason.suppressProtectedRest,
        nextEligibleAt: activeFreeSlot.interval?.end,
      );
    }

    if (protectedRestActive && task.isRequiredVisible) {
      return _requiredDuringProtectedRest(
        task: task,
        effective: effective,
        build: build,
        freeSlotEnd: activeFreeSlot.interval?.end,
      );
    }

    if (task.type == TaskType.critical &&
        effective.profile == ReminderProfile.strict) {
      logger.finer(() => 'Reminder directive requires acknowledgement. '
          'taskId=${task.id} profile=${effective.profile.name}');
      return build(
        action: ReminderDirectiveAction.requireAcknowledgement,
        reason: ReminderDirectiveReason.requireRequiredAcknowledgement,
      );
    }

    return build(
      action: ReminderDirectiveAction.deliver,
      reason: ReminderDirectiveReason.deliverNormal,
    );
  }

  /// Runs the `_requiredDuringProtectedRest` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  ReminderDirective _requiredDuringProtectedRest({
    required Task task,
    required EffectiveReminderProfile effective,
    required ReminderDirective Function({
      required ReminderDirectiveAction action,
      required ReminderDirectiveReason reason,
      DateTime? nextEligibleAt,
    }) build,
    required DateTime? freeSlotEnd,
  }) {
    if (task.type == TaskType.critical &&
        effective.profile == ReminderProfile.strict) {
      logger.finer(() =>
          'Reminder directive requires acknowledgement during protected rest. '
          'taskId=${task.id} profile=${effective.profile.name}');
      return build(
        action: ReminderDirectiveAction.requireAcknowledgement,
        reason: ReminderDirectiveReason.requireRequiredAcknowledgement,
      );
    }

    if (task.type == TaskType.inflexible &&
        effective.profile == ReminderProfile.gentle) {
      logger.finer(() =>
          'Reminder directive deferred required task during protected rest. '
          'taskId=${task.id} profile=${effective.profile.name} '
          'nextEligibleAt=${freeSlotEnd?.toIso8601String()}');
      return build(
        action: ReminderDirectiveAction.defer,
        reason: ReminderDirectiveReason.deferRequiredDuringProtectedRest,
        nextEligibleAt: freeSlotEnd,
      );
    }

    return build(
      action: ReminderDirectiveAction.deliver,
      reason: ReminderDirectiveReason.deliverNormal,
    );
  }

  /// Runs the `_activeProtectedFreeSlot` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  OccupancyEntry? _activeProtectedFreeSlot({
    required DateTime now,
    required Iterable<Task> contextTasks,
  }) {
    for (final entry in occupancyPolicy.classify(tasks: contextTasks)) {
      final interval = entry.interval;
      if (entry.category != OccupancyCategory.protectedFreeSlot ||
          interval == null) {
        continue;
      }

      final startsAtOrBeforeNow =
          interval.start.isBefore(now) || interval.start.isAtSameMomentAs(now);
      final endsAfterNow = interval.end.isAfter(now);
      if (startsAtOrBeforeNow && endsAfterNow) {
        return entry;
      }
    }

    return null;
  }
}

/// Top-level helper that performs the `_isReminderEligibleStatus` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
bool _isReminderEligibleStatus(TaskStatus status) {
  return status == TaskStatus.planned || status == TaskStatus.active;
}

/// Top-level helper that performs the `_isReminderEligibleType` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
bool _isReminderEligibleType(TaskType type) {
  return type == TaskType.flexible ||
      type == TaskType.critical ||
      type == TaskType.inflexible;
}
