namespace ClaudeDo.Worker.Usage;

/// <summary>
/// The soft/hard/gate percentages of a single usage bucket. Soft caps parallelism at 2 slots, hard
/// at 1, gate blocks the queue refill entirely. A threshold of 0 disables that stage.
/// </summary>
public readonly record struct UsageThresholds(int SoftPct, int HardPct, int GatePct);

/// <summary>
/// Pure staged throttle sitting ahead of <see cref="UsageGate"/>'s hard pause: as a usage window
/// fills up, the queue's effective parallelism steps down before it hits zero, instead of running
/// at full tilt right up to the gate threshold. Each bucket carries its own thresholds (the 5h and
/// 7d windows fill at very different rates) and the strictest bucket decides. A missing bucket
/// (null) never throttles — callers with no snapshot at all should skip this and use
/// <paramref name="configuredSlots"/> directly (fail-open), same policy as <see cref="UsageGate"/>.
/// </summary>
public static class UsageThrottle
{
    public static int EffectiveSlots(
        int configuredSlots,
        double? fiveHourPct,
        UsageThresholds fiveHour,
        double? sevenDayPct,
        UsageThresholds sevenDay)
    {
        var slots = Math.Max(1, configuredSlots);

        return Math.Min(
            BucketSlots(slots, fiveHourPct, fiveHour),
            BucketSlots(slots, sevenDayPct, sevenDay));
    }

    private static int BucketSlots(int slots, double? pct, UsageThresholds thresholds)
    {
        if (pct is not { } utilization) return slots;

        if (thresholds.GatePct > 0 && utilization >= thresholds.GatePct) return 0;
        if (thresholds.HardPct > 0 && utilization >= thresholds.HardPct) return Math.Min(slots, 1);
        if (thresholds.SoftPct > 0 && utilization >= thresholds.SoftPct) return Math.Min(slots, 2);

        return slots;
    }
}
