// UI/platform-independent reminder policy.
//
// This file decides whether the core would deliver, suppress, defer, or require
// acknowledgement for a reminder at a specific instant. It does not schedule OS
// notifications, run background timers, or move tasks.

import 'models.dart';
import 'occupancy_policy.dart';

/// High-level reminder directive for application/platform layers.
enum ReminderDirectiveAction {
  deliver,
  suppress,
  defer,
  requireAcknowledgement,
}

/// Stable reason codes for reminder directives.
enum ReminderDirectiveReason {
  deliverNormal,
  requireRequiredAcknowledgement,
  deferUntilTaskWindow,
  deferRequiredDuringProtectedRest,
  suppressSilentProfile,
  suppressProtectedRest,
  suppressNoScheduledWindow,
  suppressUnsupportedTaskType,
  suppressInactiveStatus,
  suppressHiddenLockedTime,
  suppressFreeSlotRecord,
}

/// Result of resolving the effective reminder profile for one task.
class EffectiveReminderProfile {
  const EffectiveReminderProfile({
    required this.profile,
    required this.source,
  });

  /// Resolved reminder profile.
  final ReminderProfile profile;

  /// Where [profile] came from.
  final EffectiveReminderProfileSource source;
}

/// Source for an effective reminder profile.
enum EffectiveReminderProfileSource {
  taskOverride,
  projectDefault,
  fallback,
}

/// UI/platform-independent directive for one task reminder check.
class ReminderDirective {
  const ReminderDirective({
    required this.taskId,
    required this.action,
    required this.reason,
    required this.effectiveProfile,
    required this.effectiveProfileSource,
    required this.evaluatedAt,
    this.scheduledStart,
    this.scheduledEnd,
    this.protectedFreeSlotTaskId,
    this.nextEligibleAt,
  });

  /// Task evaluated for reminder behavior.
  final String taskId;

  /// What an application layer may do.
  final ReminderDirectiveAction action;

  /// Stable reason code for tests, telemetry, and later UI copy.
  final ReminderDirectiveReason reason;

  /// Effective reminder profile used for the decision.
  final ReminderProfile effectiveProfile;

  /// Source of [effectiveProfile].
  final EffectiveReminderProfileSource effectiveProfileSource;

  /// Instant used to evaluate the directive.
  final DateTime evaluatedAt;

  /// Task scheduled start, when present.
  final DateTime? scheduledStart;

  /// Task scheduled end, when present.
  final DateTime? scheduledEnd;

  /// Active protected Free Slot that affected the decision, when any.
  final String? protectedFreeSlotTaskId;

  /// Next known time the application may re-check, when deterministic.
  final DateTime? nextEligibleAt;
}

/// Resolves reminder profiles and reminder directives.
class ReminderPolicyService {
  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) {
      return EffectiveReminderProfile(
        profile: override,
        source: EffectiveReminderProfileSource.taskOverride,
      );
    }

    if (project != null) {
      return EffectiveReminderProfile(
        profile: project.defaultReminderProfile,
        source: EffectiveReminderProfileSource.projectDefault,
      );
    }

    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>[],
  }) {
    final effective = resolveEffectiveProfile(task: task, project: project);
    final activeFreeSlot = _activeProtectedFreeSlot(
      now: now,
      contextTasks: contextTasks,
    );

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

  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,
    }) {
      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) {
      return build(
        action: ReminderDirectiveAction.requireAcknowledgement,
        reason: ReminderDirectiveReason.requireRequiredAcknowledgement,
      );
    }

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

  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) {
      return build(
        action: ReminderDirectiveAction.requireAcknowledgement,
        reason: ReminderDirectiveReason.requireRequiredAcknowledgement,
      );
    }

    if (task.type == TaskType.inflexible &&
        effective.profile == ReminderProfile.gentle) {
      return build(
        action: ReminderDirectiveAction.defer,
        reason: ReminderDirectiveReason.deferRequiredDuringProtectedRest,
        nextEligibleAt: freeSlotEnd,
      );
    }

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

  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;
  }
}

bool _isReminderEligibleStatus(TaskStatus status) {
  return status == TaskStatus.planned || status == TaskStatus.active;
}

bool _isReminderEligibleType(TaskType type) {
  return type == TaskType.flexible ||
      type == TaskType.critical ||
      type == TaskType.inflexible;
}
