<?php

declare(strict_types=1);

namespace App\Domain\Reputation;

/**
 * Pure decay math per SPEC §5. Stateless — easy to unit-test against
 * hand-computed values.
 *
 *  - Linear     : max(0, 1 − age_days / decay_param)
 *  - Exponential: 0.5 ^ (age_days / decay_param)        (half-life)
 *
 * `age_days` may be fractional. Negative ages (future-dated reports —
 * shouldn't happen, but guard anyway) clamp to zero so a fresh report
 * always counts at full weight.
 */
final class Decay
{
    public static function value(DecayFunction $function, float $ageDays, float $decayParam): float
    {
        if ($decayParam <= 0.0) {
            return 0.0;
        }

        $age = max(0.0, $ageDays);

        return match ($function) {
            DecayFunction::Linear => max(0.0, 1.0 - ($age / $decayParam)),
            DecayFunction::Exponential => 0.5 ** ($age / $decayParam),
        };
    }

    /**
     * Inverse of `value()`: roughly how many days until a current score
     * decays below `$threshold`, assuming no further reports.
     *
     * Exponential is exact (the sum scales by 0.5^(Δ/T) regardless of
     * report ages). Linear treats the current score as a lump at "now"
     * — an upper bound on time-to-threshold; the real curve drops faster
     * as individual reports age out. Used by the policy preview to give
     * operators an at-a-glance "when does this entry fall off?".
     *
     * Returns 0.0 if the score is already at or below the threshold,
     * `null` if the threshold is zero/negative (entry would never fall
     * off naturally).
     */
    public static function daysUntilThreshold(
        DecayFunction $function,
        float $decayParam,
        float $currentScore,
        float $threshold,
    ): ?float {
        if ($decayParam <= 0.0) {
            return 0.0;
        }
        if ($threshold <= 0.0) {
            return null;
        }
        if ($currentScore <= $threshold) {
            return 0.0;
        }

        return match ($function) {
            DecayFunction::Linear => $decayParam * (1.0 - $threshold / $currentScore),
            DecayFunction::Exponential => $decayParam * (log($currentScore / $threshold) / log(2.0)),
        };
    }
}
