# parc_sdk/updates.py
"""
PARC Update Rule v0.1
Takes previous state and new observation → updates the PARC vector.

prev_state: {"c":..., "m":..., "f":..., "k":...}
obs_state:  {"c":..., "m":..., "f":..., "k":...}
"""

from typing import Dict

def update_state(
    prev_state: Dict[str, float],
    obs_state: Dict[str, float],
    *,
    gamma: float = 0.3,   # learning rate (correctness growth)
    delta: float = 0.1,   # misconception decay base
    beta: float = 0.5,    # misconception decay boost per correctness
    rho: float = 0.5      # confidence calibration mix
) -> Dict[str, float]:

    # unpack previous state
    c_prev = prev_state.get("c", 0)
    m_prev = prev_state.get("m", 0)
    k_prev = prev_state.get("k", 0)

    # unpack observation
    c_obs = obs_state.get("c", 0)
    m_obs = obs_state.get("m", 0)
    k_obs = obs_state.get("k", 0)

    # blend observation into previous state (simple EMA)
    c_t = 0.5 * c_prev + 0.5 * c_obs
    m_t = 0.5 * m_prev + 0.5 * m_obs

    # PARC dynamics
    gamma_eff = gamma * (1 - m_t)
    delta_eff = delta + beta * c_t

    m_new_raw = m_t * (1 - delta_eff)
    c_new_raw = c_t + gamma_eff * (1 - c_t - m_t)

    # normalize total if > 1
    total = c_new_raw + m_new_raw
    if total > 1:
        c_new = c_new_raw / total
        m_new = m_new_raw / total
        f_new = 0.0
    else:
        c_new = c_new_raw
        m_new = m_new_raw
        f_new = 1 - c_new - m_new

    # confidence = structural + observed mix
    sigma = max(c_new, m_new)
    k_struct = sigma * (1 - f_new)
    k_new = rho * k_obs + (1 - rho) * k_struct

    # clamp to [0,1]
    return {
        "c": max(0.0, min(1.0, c_new)),
        "m": max(0.0, min(1.0, m_new)),
        "f": max(0.0, min(1.0, f_new)),
        "k": max(0.0, min(1.0, k_new)),
    }

