"""Layer 1 Physical Invariants for LIF Neuron Model.

Each invariant is a Python function that validates neuron parameters against
non-negotiable biological constraints sourced from NeuroML's LIF definition
(iafTauCell / iafTauRefCell).

Merged from v-copilot (sophisticated decay check, registry, documentation)
and v-jules (defensive .get() parameter access).
"""


def threshold_above_resting(params: dict) -> bool:
    """Threshold must be greater than resting membrane potential.

    NeuroML parameter: thresh > leakReversal
    A neuron that is already above threshold at rest would fire continuously
    without input, which is physically meaningless.
    """
    threshold = params.get("threshold")
    resting_potential = params.get("resting_potential")
    if threshold is None or resting_potential is None:
        return False
    return threshold > resting_potential


def refractory_period_positive(params: dict) -> bool:
    """Refractory period must be greater than zero.

    NeuroML parameter: refract > 0
    A zero refractory period means the neuron can spike at infinite frequency,
    which is biologically impossible.
    """
    refractory_period = params.get("refractory_period")
    if refractory_period is None:
        return False
    return refractory_period > 0


def time_constant_positive(params: dict) -> bool:
    """Time constant tau must be greater than zero.

    NeuroML parameter: tau > 0
    A non-positive time constant produces undefined or divergent membrane
    dynamics.
    """
    tau = params.get("tau")
    if tau is None:
        return False
    return tau > 0


def reset_at_or_below_threshold(params: dict) -> bool:
    """Reset potential must be less than or equal to threshold.

    NeuroML parameter: reset <= thresh
    Resetting above threshold would cause immediate re-firing, creating an
    unphysical infinite-frequency loop.
    """
    reset_potential = params.get("reset_potential")
    threshold = params.get("threshold")
    if reset_potential is None or threshold is None:
        return False
    return reset_potential <= threshold


def membrane_potential_decays_toward_rest(params: dict) -> bool:
    """Membrane potential must decay toward resting potential during inactivity.

    NeuroML dynamics: dv/dt = (leakReversal - v) / tau
    This is guaranteed when tau > 0 and dynamics follow the standard LIF
    equation. Validates that tau > 0 and that the voltage would move toward
    resting potential given no input.
    """
    tau = params.get("tau")
    if tau is None or tau <= 0:
        return False
    resting_potential = params.get("resting_potential", 0.0)
    v = params.get("current_voltage", resting_potential + 0.1)
    # dv/dt = (rest - v) / tau
    # If v > rest, dv/dt < 0 (voltage decreases toward rest)
    # If v < rest, dv/dt > 0 (voltage increases toward rest)
    # If v == rest, dv/dt == 0 (at rest)
    dv_dt = (resting_potential - v) / tau
    if v > resting_potential:
        return dv_dt < 0
    elif v < resting_potential:
        return dv_dt > 0
    else:
        return dv_dt == 0


# Registry of all invariants for programmatic access
ALL_INVARIANTS = {
    "threshold_above_resting": threshold_above_resting,
    "refractory_period_positive": refractory_period_positive,
    "time_constant_positive": time_constant_positive,
    "reset_at_or_below_threshold": reset_at_or_below_threshold,
    "membrane_potential_decays_toward_rest": membrane_potential_decays_toward_rest,
}
