import '../about/external/data/Commitment.dart';
import '../about/internal/application/CapacityProfile.dart';

/// The result of a capacity check at plan time.
class CapacityVerdict {
  /// Minutes the plan actually implies, after the estimation multipliers.
  double projectedMinutes;

  /// Minutes history says get done on this weekday, at p50.
  double historicalMinutes;

  /// The ceiling — historical minutes times the headroom factor.
  double allowedMinutes;

  bool blocked;

  /// Minutes that must come out of the plan before it will be accepted.
  double excessMinutes;

  String message;

  CapacityVerdict({
    this.projectedMinutes = 0,
    this.historicalMinutes = 0,
    this.allowedMinutes = 0,
    this.blocked = false,
    this.excessMinutes = 0,
    this.message = "",
  });
}

/// Chronic overdue is usually an overcommitment problem misdiagnosed as a
/// laziness problem. This is the check that catches it.
///
/// ```
/// projected_load = Sum (est_minutes x your_multiplier[category])
/// if projected_load > 0.85 x p50(historical_completed_minutes[weekday]):
///     block, and force removal
/// ```
class CapacityEngine {
  /// Plan against 85% of what you historically get done, not 100%.
  static const double headroom = 0.85;

  /// The plan as the app believes it, not as you estimated it. Your estimates
  /// are wrong and the multiplier is applied silently.
  static double projectedLoad(
    List<Commitment> plan,
    CapacityProfile profile,
  ) {
    double total = 0;
    for (Commitment commitment in plan) {
      total =
          total + (commitment.estMinutes * profile.multiplierFor(commitment.category));
    }
    return total;
  }

  static CapacityVerdict check(
    List<Commitment> plan,
    CapacityProfile profile,
    int weekday,
  ) {
    final double projected = projectedLoad(plan, profile);
    final double historical = profile.capacityFor(weekday);
    final double allowed = historical * headroom;

    // No history yet — the app has not earned the right to block anything.
    if (historical <= 0) {
      return CapacityVerdict(
        projectedMinutes: projected,
        historicalMinutes: 0,
        allowedMinutes: 0,
        blocked: false,
        message: "Not enough history yet to judge this plan.",
      );
    }

    if (projected <= allowed) {
      return CapacityVerdict(
        projectedMinutes: projected,
        historicalMinutes: historical,
        allowedMinutes: allowed,
        blocked: false,
        message: "This plan fits what you actually get done.",
      );
    }

    final double excess = projected - allowed;

    return CapacityVerdict(
      projectedMinutes: projected,
      historicalMinutes: historical,
      allowedMinutes: allowed,
      blocked: true,
      excessMinutes: excess,
      message:
          "You have allocated ${_hours(projected)} of tasks into a day where you historically complete ${_hours(historical)}. Cut ${_hours(excess)}.",
    );
  }

  /// The learned multiplier for a category, from what you said against what it
  /// took. Surfaced in the weekly review rather than hidden.
  static double learnMultiplier(
    List<double> estimatedMinutes,
    List<double> actualMinutes,
  ) {
    if (estimatedMinutes.isEmpty ||
        estimatedMinutes.length != actualMinutes.length) {
      return 1.0;
    }

    double estimated = 0;
    double actual = 0;

    for (int index = 0; index < estimatedMinutes.length; index++) {
      estimated = estimated + estimatedMinutes[index];
      actual = actual + actualMinutes[index];
    }

    if (estimated <= 0) {
      return 1.0;
    }

    return actual / estimated;
  }

  static String _hours(double minutes) {
    if (minutes < 60) {
      return "${minutes.round()}min";
    }
    final double hours = minutes / 60;
    return "${hours.toStringAsFixed(1)}h";
  }
}
