package dev.privacyllc.period.core.data

import kotlin.math.abs

/**
 * What §16 shows the user: "5 of your last 6 predictions were within one day."
 *
 * Computed on the device from stored snapshots. Nothing about accuracy leaves
 * the phone — §46 is explicit that `prediction_error=` is not an analytics
 * event.
 */
data class PredictionAccuracy(
    val scoredCount: Int,
    val lastErrorDays: Int?,
    /** Negative means the forecast was early; positive, late. Signed, because "1 day early" is what the user is told. */
    val lastSignedErrorDays: Int?,
    val meanAbsoluteErrorDays: Double?,
    val medianAbsoluteErrorDays: Double?,
    val withinOneDay: Int,
    val withinTwoDays: Int,
) {
    val hasEnoughToShow: Boolean get() = scoredCount >= MINIMUM_TO_SHOW

    companion object {
        /**
         * Below this, an accuracy claim is noise dressed as a measurement. One
         * lucky prediction reading "average error: 0 days" is the kind of
         * overstatement §27 warns against.
         */
        const val MINIMUM_TO_SHOW = 3

        val Empty = PredictionAccuracy(0, null, null, null, null, 0, 0)

        /** [errors] newest first, each the signed difference predicted − actual. */
        fun from(errors: List<Int>): PredictionAccuracy {
            if (errors.isEmpty()) return Empty
            val abs = errors.map { abs(it) }
            val sorted = abs.sorted()
            val mid = sorted.size / 2
            return PredictionAccuracy(
                scoredCount = errors.size,
                lastErrorDays = abs.first(),
                lastSignedErrorDays = errors.first(),
                meanAbsoluteErrorDays = abs.average(),
                medianAbsoluteErrorDays =
                    if (sorted.size % 2 == 1) sorted[mid].toDouble() else (sorted[mid - 1] + sorted[mid]) / 2.0,
                withinOneDay = abs.count { it <= 1 },
                withinTwoDays = abs.count { it <= 2 },
            )
        }
    }
}
