"""FSRS-6 math: constants, retrievability, stability, difficulty formulas.

Separated from spaced_retrieval.py for SoC compliance.
FSRS-4.5 default weights (public domain / open research).
"""
from __future__ import annotations
import math

# ---------------------------------------------------------------------------
# FSRS-4.5 default weights (subset used for binary success/failure scheduling)
# ---------------------------------------------------------------------------
_FSRS_W6  = 1.0651   # difficulty adjustment per grade step
_FSRS_W8  = 1.5330   # ln of stability multiplier base
_FSRS_W9  = 0.1544   # stability power factor in recall formula
_FSRS_W10 = 1.0071   # retrievability exponent in recall formula
_FSRS_W11 = 1.9439   # lapse stability coefficient
_FSRS_W12 = 0.1134   # lapse difficulty exponent
_FSRS_W13 = 0.2920   # lapse stability exponent

# Scheduling parameters
_DESIRED_RETENTION = 0.90    # target recall probability at next review
_FSRS_INITIAL_STAB = 1.0     # initial stability in hours for new items
_FSRS_INITIAL_DIFF = 5.0     # initial difficulty (1-10, 5 = medium)
_FSRS_MIN_STAB     = 1.0     # floor: never schedule less than 1h out
_FSRS_MAX_STAB     = 365 * 24  # ceiling: 1 year in hours
_FSRS_MIN_DIFF     = 1.0
_FSRS_MAX_DIFF     = 10.0

# Grades for binary success/failure (FSRS 1=Again, 4=Easy)
_GRADE_SUCCESS = 4
_GRADE_FAILURE = 1


def _retrievability(elapsed_hours: float, stability_hours: float) -> float:
    """R(t, S) = (1 + t / (9·S))^-1"""
    if stability_hours <= 0:
        return 0.0
    return 1.0 / (1.0 + elapsed_hours / (9.0 * stability_hours))


def _next_interval(stability_hours: float) -> float:
    """Hours until R drops to _DESIRED_RETENTION.

    Solving (1 + t/(9S))^-1 = r  →  t = 9S·(r^-1 - 1)
    """
    t = 9.0 * stability_hours * (1.0 / _DESIRED_RETENTION - 1.0)
    return max(_FSRS_MIN_STAB, min(_FSRS_MAX_STAB, t))


def _stability_recall(S: float, D: float, R: float) -> float:
    """Stability after successful recall. Low R → bigger boost (spacing effect).

    S' = S·(e^w8·(11-D)·S^-w9·(e^(w10·(1-R))-1)+1)
    """
    boost = (math.exp(_FSRS_W8) * (11.0 - D)
             * (S ** -_FSRS_W9)
             * (math.exp(_FSRS_W10 * (1.0 - R)) - 1.0))
    new_S = S * (max(0.1, boost) + 1.0)
    return max(_FSRS_MIN_STAB, min(_FSRS_MAX_STAB, new_S))


def _stability_lapse(S: float, D: float) -> float:
    """Stability after a lapse. S' = w11·D^(-w12)·(S+1)^w13"""
    new_S = _FSRS_W11 * (D ** -_FSRS_W12) * ((S + 1.0) ** _FSRS_W13)
    return max(_FSRS_MIN_STAB, min(_FSRS_MAX_STAB, new_S))


def _difficulty_update(D: float, grade: int) -> float:
    """D' = clip(D - w6·(grade-3), 1, 10)

    grade=4 (success): D decreases. grade=1 (failure): D increases.
    """
    new_D = D - _FSRS_W6 * (grade - 3)
    return max(_FSRS_MIN_DIFF, min(_FSRS_MAX_DIFF, new_D))
