/*
 * slope_estimator.c — slow-adapting terrain slope estimator (Issue #600).
 *
 * First-order IIR low-pass filter on fused IMU pitch with tau = SLOPE_TAU_S (5 s).
 * The long time constant means fast balance corrections and transients are
 * ignored; only sustained pitch bias (i.e., genuine slope) accumulates.
 *
 * Estimate is clamped to ±SLOPE_MAX_DEG (15°) per the safety requirement.
 */

#include "slope_estimator.h"
#include "jlink.h"

/* ---- slope_estimator_init() ---- */
void slope_estimator_init(slope_estimator_t *se)
{
    se->estimate_deg  = 0.0f;
    se->enabled       = true;
    /* Initialize so first send_tlm call fires immediately */
    se->last_tlm_ms   = (uint32_t)(-(uint32_t)(1000u / (SLOPE_TLM_HZ > 0u ? SLOPE_TLM_HZ : 1u)));
}

/* ---- slope_estimator_reset() ---- */
void slope_estimator_reset(slope_estimator_t *se)
{
    se->estimate_deg = 0.0f;
}

/* ---- slope_estimator_update() ---- */
void slope_estimator_update(slope_estimator_t *se, float pitch_deg, float dt)
{
    if (!se->enabled || dt <= 0.0f) return;

    /*
     * First-order IIR: alpha = dt / (tau + dt)
     * At 1 kHz (dt=0.001): alpha ≈ 0.0002  → time to ~63% ≈ 5 s
     * At 100 Hz (dt=0.01):  alpha ≈ 0.002   → time to ~63% ≈ 5 s (same tau)
     */
    float alpha = dt / (SLOPE_TAU_S + dt);
    float raw   = se->estimate_deg * (1.0f - alpha) + pitch_deg * alpha;

    /* Clamp to ±SLOPE_MAX_DEG */
    if (raw >  SLOPE_MAX_DEG) raw =  SLOPE_MAX_DEG;
    if (raw < -SLOPE_MAX_DEG) raw = -SLOPE_MAX_DEG;

    se->estimate_deg = raw;
}

/* ---- slope_estimator_get_deg() ---- */
float slope_estimator_get_deg(const slope_estimator_t *se)
{
    if (!se->enabled) return 0.0f;
    return se->estimate_deg;
}

/* ---- slope_estimator_set_enabled() ---- */
void slope_estimator_set_enabled(slope_estimator_t *se, bool en)
{
    se->enabled = en;
}

/* ---- slope_estimator_send_tlm() ---- */
void slope_estimator_send_tlm(const slope_estimator_t *se, uint32_t now_ms)
{
    if (SLOPE_TLM_HZ == 0u) return;

    uint32_t interval_ms = 1000u / SLOPE_TLM_HZ;
    if ((now_ms - se->last_tlm_ms) < interval_ms) return;
    /* Cast away const for timestamp update -- only mutable field */
    ((slope_estimator_t *)se)->last_tlm_ms = now_ms;

    jlink_tlm_slope_t tlm;
    /* Scale to ×100 for 0.01° resolution, clamped to int16 range */
    float scaled = se->estimate_deg * 100.0f;
    if (scaled >  32767.0f) scaled =  32767.0f;
    if (scaled < -32768.0f) scaled = -32768.0f;
    tlm.slope_x100 = (int16_t)scaled;
    tlm.active     = se->enabled ? 1u : 0u;
    tlm._pad       = 0u;

    jlink_send_slope_tlm(&tlm);
}
