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

part of '../../project_statistics.dart';

/// Derives deterministic project suggestions from aggregate observations.
class ProjectSuggestionService {
  /// Creates a `ProjectSuggestionService` 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 ProjectSuggestionService({
    this.policy = const ProjectSuggestionPolicy(),
  });

  /// Stores the `policy` value for this object.
  /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
  final ProjectSuggestionPolicy policy;

  /// Build optional suggestions for [statistics].
  ProjectSuggestionSet suggestionsFor(ProjectStatistics statistics) {
    return ProjectSuggestionSet(
      statistics: statistics,
      durationMinutes: _durationSuggestion(statistics),
      completionTimeBucket: _modeSuggestion(
        type: ProjectSuggestionType.completionTimeBucket,
        counts: statistics.completionTimeBucketCounts,
        minimumSampleSize: policy.minimumCompletionTimeSamples,
      ),
      reward: _modeSuggestion(
        type: ProjectSuggestionType.reward,
        counts: _withoutNotSetReward(statistics.rewardCounts),
        minimumSampleSize: policy.minimumRewardSamples,
      ),
      difficulty: _modeSuggestion(
        type: ProjectSuggestionType.difficulty,
        counts: _withoutNotSetDifficulty(statistics.difficultyCounts),
        minimumSampleSize: policy.minimumDifficultySamples,
      ),
      reminderProfile: _modeSuggestion(
        type: ProjectSuggestionType.reminderProfile,
        counts: statistics.reminderProfileCounts,
        minimumSampleSize: policy.minimumReminderSamples,
      ),
    );
  }

  /// Return configured defaults and optional suggestions without merging them.
  ProjectDefaultResolution resolveDefaults({
    required ProjectProfile project,
    required ProjectStatistics statistics,
  }) {
    return ProjectDefaultResolution(
      project: project,
      suggestions: suggestionsFor(statistics),
    );
  }

  /// Runs the `_durationSuggestion` helper used inside this library.
  /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
  ProjectSuggestion<int>? _durationSuggestion(ProjectStatistics statistics) {
    final sampleSize = statistics.knownDurationSampleCount;
    if (sampleSize < policy.minimumDurationSamples) {
      return null;
    }

    final value = _weightedLowerMedian(statistics.durationMinuteCounts);
    if (value == null) {
      return null;
    }

    return ProjectSuggestion<int>(
      type: ProjectSuggestionType.durationMinutes,
      value: value,
      sampleSize: sampleSize,
      supportingSampleCount: statistics.durationMinuteCounts[value] ?? 0,
      minimumSampleSize: policy.minimumDurationSamples,
      confidence: _confidence(
        statistics.durationMinuteCounts[value] ?? 0,
        sampleSize,
      ),
    );
  }

  /// Performs the `Object>` behavior for this scheduler component.
  /// The method provides a named entry point for this operation so callers do not need to know the lower-level state or persistence steps involved.
  ProjectSuggestion<T>? _modeSuggestion<T extends Object>({
    required ProjectSuggestionType type,
    required Map<T, int> counts,
    required int minimumSampleSize,
  }) {
    final sampleSize = _countTotal(counts);
    if (sampleSize < minimumSampleSize) {
      return null;
    }

    final mode = _uniqueMode(counts);
    if (mode == null) {
      return null;
    }

    final support = counts[mode] ?? 0;
    return ProjectSuggestion<T>(
      type: type,
      value: mode,
      sampleSize: sampleSize,
      supportingSampleCount: support,
      minimumSampleSize: minimumSampleSize,
      confidence: _confidence(support, sampleSize),
    );
  }
}

/// Runs the `Object>` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
Map<K, int> _incrementCount<K extends Object>(Map<K, int> counts, K key) {
  return {
    ...counts,
    key: (counts[key] ?? 0) + 1,
  };
}

/// Top-level helper that performs the `_positiveIntKeyCounts` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
Map<int, int> _positiveIntKeyCounts(Map<int, int> counts, String name) {
  final normalized = <int, int>{};
  for (final entry in counts.entries) {
    if (entry.key <= 0) {
      throw ArgumentError.value(entry.key, name, 'Key must be positive.');
    }
    _validateNonNegative(entry.value, name);
    if (entry.value > 0) {
      normalized[entry.key] = entry.value;
    }
  }
  return normalized;
}

/// Runs the `Enum>` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
Map<T, int> _enumCounts<T extends Enum>(Map<T, int> counts, String name) {
  final normalized = <T, int>{};
  for (final entry in counts.entries) {
    _validateNonNegative(entry.value, name);
    if (entry.value > 0) {
      normalized[entry.key] = entry.value;
    }
  }
  return normalized;
}

/// Top-level helper that performs the `_validateNonNegative` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
void _validateNonNegative(int value, String name) {
  if (value < 0) {
    throw ArgumentError.value(value, name, 'Count cannot be negative.');
  }
}

/// Runs the `Object>` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
int _countTotal<K extends Object>(Map<K, int> counts) {
  var total = 0;
  for (final value in counts.values) {
    total += value;
  }
  return total;
}

/// Top-level helper that performs the `_weightedLowerMedian` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
int? _weightedLowerMedian(Map<int, int> counts) {
  if (counts.isEmpty) {
    return null;
  }
  final sampleSize = _countTotal(counts);
  final target = (sampleSize + 1) ~/ 2;
  var seen = 0;
  final sortedMinutes = counts.keys.toList()..sort();
  for (final minutes in sortedMinutes) {
    seen += counts[minutes] ?? 0;
    if (seen >= target) {
      return minutes;
    }
  }
  return null;
}

/// Runs the `Object>` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
T? _uniqueMode<T extends Object>(Map<T, int> counts) {
  T? bestKey;
  var bestCount = 0;
  var tied = false;

  for (final entry in counts.entries) {
    if (entry.value > bestCount) {
      bestKey = entry.key;
      bestCount = entry.value;
      tied = false;
      continue;
    }
    if (entry.value == bestCount && bestCount > 0) {
      tied = true;
    }
  }

  return tied ? null : bestKey;
}

/// Top-level helper that performs the `_confidence` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
ProjectSuggestionConfidence _confidence(int support, int sampleSize) {
  if (sampleSize <= 0) {
    return ProjectSuggestionConfidence.low;
  }
  if (support * 3 >= sampleSize * 2) {
    return ProjectSuggestionConfidence.high;
  }
  if (support * 2 >= sampleSize) {
    return ProjectSuggestionConfidence.medium;
  }
  return ProjectSuggestionConfidence.low;
}

/// Top-level helper that performs the `_withoutNotSetReward` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
Map<RewardLevel, int> _withoutNotSetReward(Map<RewardLevel, int> counts) {
  return {
    for (final entry in counts.entries)
      if (entry.key != RewardLevel.notSet) entry.key: entry.value,
  };
}

/// Top-level helper that performs the `_withoutNotSetDifficulty` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
Map<DifficultyLevel, int> _withoutNotSetDifficulty(
  Map<DifficultyLevel, int> counts,
) {
  return {
    for (final entry in counts.entries)
      if (entry.key != DifficultyLevel.notSet) entry.key: entry.value,
  };
}

/// Top-level helper that performs the `_dateTimeMetadata` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
DateTime? _dateTimeMetadata(Object? value) {
  if (value is DateTime) {
    return value;
  }
  if (value is String) {
    return DateTime.tryParse(value)?.toUtc();
  }
  return null;
}

/// Top-level helper that performs the `_intMetadata` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
int? _intMetadata(Object? value) {
  return value is int && value >= 0 ? value : null;
}

/// Top-level helper that performs the `_durationMinutes` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
int? _durationMinutes(TaskActivity activity, Task? task) {
  final metadataDuration = _intMetadata(activity.metadata['durationMinutes']);
  if (metadataDuration != null && metadataDuration > 0) {
    return metadataDuration;
  }

  final actualStart =
      _dateTimeMetadata(activity.metadata['actualStart']) ?? task?.actualStart;
  final actualEnd =
      _dateTimeMetadata(activity.metadata['actualEnd']) ?? task?.actualEnd;
  if (actualStart != null &&
      actualEnd != null &&
      actualStart.isBefore(actualEnd)) {
    final minutes = actualEnd.difference(actualStart).inMinutes;
    if (minutes > 0) {
      return minutes;
    }
  }

  return task?.durationMinutes;
}

/// Top-level helper that performs the `_pushesBeforeCompletion` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
int _pushesBeforeCompletion(Task? task) {
  if (task == null) {
    return 0;
  }
  return task.stats.manuallyPushedCount + task.stats.autoPushedCount;
}

/// Top-level helper that performs the `_rewardLevel` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
RewardLevel? _rewardLevel(Object? value) {
  if (value is RewardLevel) {
    return value;
  }
  if (value is String) {
    return _enumByName(RewardLevel.values, value);
  }
  return null;
}

/// Top-level helper that performs the `_difficultyLevel` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
DifficultyLevel? _difficultyLevel(Object? value) {
  if (value is DifficultyLevel) {
    return value;
  }
  if (value is String) {
    return _enumByName(DifficultyLevel.values, value);
  }
  return null;
}

/// Top-level helper that performs the `_reminderProfile` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
ReminderProfile? _reminderProfile(Object? value) {
  if (value is ReminderProfile) {
    return value;
  }
  if (value is String) {
    return _enumByName(ReminderProfile.values, value);
  }
  return null;
}

/// Runs the `Enum>` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
T? _enumByName<T extends Enum>(Iterable<T> values, String name) {
  for (final value in values) {
    if (value.name == name) {
      return value;
    }
  }
  return null;
}

/// Top-level helper that performs the `_requiredTrimmed` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
String _requiredTrimmed(String value, String name) {
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    throw ArgumentError.value(value, name, 'Value is required.');
  }
  return trimmed;
}
