/**
 * @file pwm.h (variant/T06235/compact_src)
 * @brief Compact single-header API for the T06235 PWM controller.
 *
 * Variant note: this header was copied verbatim from variant/T06215/compact_src
 * per the pillar-port-verbatim rule (docs/pillar-functions.md). T06235 is a
 * recompile of T06215 with a +0x14 cal-block insertion and one RAM slot move
 * (reset_flag 0x002e → 0x002c) — see variant/T06235/docs/algorithm-diff-vs-T06215.md.
 * Runtime-struct field names are purely semantic; the address binding for
 * any field lives in `pwm_addr_map.h` (documentation-only; not included
 * by pwm.c).
 *
 * Public API:
 *   pwm_init()    — one-time setup
 *   pwm_service() — per-cycle update (5-stage pipeline)
 *
 * External inputs arrive through a getter vtable; callbacks return one
 * signal in native PWM units.
 */
#ifndef PWM_H
#define PWM_H

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* ── MCS-96 arithmetic primitives ───────────────────────────────────── */
#define MUL_S16(a, b)  ((int32_t)(int16_t)(a) * (int32_t)(int16_t)(b))
#define MULU_U16(a, b) ((uint32_t)(uint16_t)(a) * (uint32_t)(uint16_t)(b))
#define CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : (v) > (hi) ? (hi) : (v))

static inline int16_t shra16(int16_t v, unsigned n) {
    if (!n) return v;
    uint16_t u = (uint16_t)v, sign = (u >> 15) & 1u;
    uint16_t m = sign ? (uint16_t)(((uint16_t)~0u) << (16u - n)) : 0u;
    return (int16_t)((u >> n) | m);
}
static inline int32_t shra32(int32_t v, unsigned n) {
    if (!n) return v;
    uint32_t u = (uint32_t)v, sign = (u >> 31) & 1u;
    uint32_t m = sign ? (~(uint32_t)0u << (32u - n)) : 0u;
    return (int32_t)((u >> n) | m);
}

/* ══════════════════════════════════════════════════════════════════════
 * 1D INTERPOLATION SLOT
 * ══════════════════════════════════════════════════════════════════════ */
typedef struct pwm_interp_slot {
    int16_t row_stride;
    int16_t x_interval;
    int16_t x_offset;
    int16_t y_byte_off;
} pwm_interp_slot_t;

/* ══════════════════════════════════════════════════════════════════════
 * EXTERNAL INPUTS
 * ══════════════════════════════════════════════════════════════════════ */
typedef struct pwm_inputs {
    int16_t  ckp_in;          /* sensed position */
    uint16_t rpm;             /* engine RPM */
    int16_t  angle_dec_cmd;   /* accepted for cross-family API parity; unused here */
    int16_t  inj_qty_demand;  /* CAN: injection-quantity demand */
    int16_t  b_fb_kw;         /* CAN: plunger feedback baseline (drives target) */
    int16_t  cl_gate_input;   /* CAN: open/closed-loop discriminant (was state_130) */
    uint16_t supply_voltage;  /* shape_eval submap input */
    int16_t  temperature;     /* accepted for cross-family API parity; unused here */
} pwm_inputs_t;

typedef struct pwm_input_getters {
    int16_t  (*ckp_in)        (void *ctx);
    uint16_t (*rpm)           (void *ctx);
    int16_t  (*angle_dec_cmd) (void *ctx);
    int16_t  (*inj_qty_demand)(void *ctx);
    int16_t  (*b_fb_kw)       (void *ctx);
    int16_t  (*cl_gate_input) (void *ctx);
    uint16_t (*supply_voltage)(void *ctx);
    int16_t  (*temperature)   (void *ctx);
    void     *ctx;
} pwm_input_getters_t;

typedef struct pwm_calibration pwm_calibration_t;
typedef struct pwm_submap_descr pwm_submap_descr_t;

/* ══════════════════════════════════════════════════════════════════════
 * RUNTIME STATE
 * Field names are purely semantic. ROM-address bindings live in
 * pwm_addr_map.h (documentation-only).
 * ══════════════════════════════════════════════════════════════════════ */
typedef struct pwm_runtime {
    /* External inputs — refreshed each cycle from getters */
    pwm_inputs_t inputs;

    /* Async flags. */
    uint8_t reset_flag;
    uint8_t system_flags_110;     /* bit 5 = OL force; bit 0 = recovery latch */

    /* ── Setpoint architecture ── */
    /* target_5e: PRIMARY CAN-decoded setpoint (RW5E in t06215). */
    int16_t target_5e;
    /* pi_high_clamp_ceiling: SECONDARY RPM-derived ceiling — written each
     * cycle by pwm_service from the setpoint submap interp; consumed as
     * the PI integrator output upper-clamp inside s_pi_update and the
     * supervisor error ceiling. (DAT_0344 in t06215; was misnamed
     * target_336 in the original fork from t06211.) */
    int16_t pi_high_clamp_ceiling;

    /* CAN-staging buffers — written before pwm_service so s_setpoint_can_decode
     * can transform them into target_5e. */
    int16_t can_raw_b_fb_kw;
    int16_t can_aux_12e;
    int16_t can_half_12a;
    int16_t setpoint_offset;      /* runtime copy of cal->setpoint_offset */
    int16_t rw42_state;
    int16_t angle_error_raw;      /* DAT_02f0 — supervisor's published error */
    int16_t pi_integ_hi_snapshot; /* DAT_02ec — supervisor reset stash; dead store but kept for parity */
    uint16_t cl_enable_counter;   /* DAT_02ee */

    /* ── CL correction ── */
    int16_t cl_correction_raw;
    int16_t angle_offset;
    int16_t supervisor_state;
    int16_t pos_error_normalizer; /* runtime copy of cal->pos_error_normalizer */
    int16_t neg_error_normalizer; /* runtime copy of cal->neg_error_normalizer */

    /* ── Publish + PI ── */
    int16_t estimated_angle;
    int16_t angle_error_pi;       /* target_5e − estimated_angle (DAT_0278) */
    int16_t active_request;       /* PI output = pre-clamp output (RW46 + DAT_0274) */

    /* PI flag byte (DAT_028c). Bit layout (s_pi_update @ 0x542f):
     *   bit 0 = open-loop indicator (consumed by integrator gate)
     *   bit 1 = P-shape large-error arm taken
     *   bit 2 = output clamped to pi_low_clamp this cycle
     *   bit 3 = output clamped to pi_high_clamp_ceiling this cycle
     *   bit 4 = error > large_pos_error_thresh this cycle
     *   bit 5 = error < large_neg_error_thresh this cycle
     *   bits 6,7 = previous cycle's bits 4,5 (rotated by trailing op) */
    uint8_t pi_shape_flag;

    /* PI working state used by s_pi_update */
    int16_t pi_p_term;            /* DAT_0276 — output of P-shape segment */
    int16_t pi_p_gain_active;     /* DAT_0286 — selected gain for integrator step */
    int16_t pi_preclamp_out;      /* DAT_0274 — pre-publish active_request */

    /* PI integrator pair {hi:lo} = {DAT_028a:DAT_0288} */
    int16_t pi_integ_lo;
    int16_t pi_integ_hi;

    /* P-shape segment bounds (boot-init from cal+0x10A/0x10C). */
    int16_t p_shape_bound_pos;
    int16_t p_shape_bound_neg;

    /* P-shape gains and integrator steps (boot-init from cal). */
    int16_t p_gain_normal;        /* DAT_0454 — normal-range P-term multiplier */
    int16_t integ_step_normal;    /* DAT_0456 — normal-range integrator multiplier */
    int16_t p_slope_large_pos;    /* DAT_027e */
    int16_t p_slope_large_neg;    /* DAT_0280 */
    int16_t integ_step_large_pos; /* DAT_0282 */
    int16_t integ_step_large_neg; /* DAT_0284 */
    int16_t open_loop_p_gain;     /* DAT_033e (byte, boot-clamped to 15) */

    int16_t pi_state_118;         /* recovery counter */
    int16_t pi_state_c2;          /* cooldown counter */

    /* ── PWM output ── */
    uint16_t pwm_duty;
    uint16_t pwm_on_time;
    uint16_t pwm_off_time;
    uint16_t pwm_period;
    uint8_t  pwm_duty_range_flag;
    pwm_interp_slot_t pwm_slot_a;
    pwm_interp_slot_t pwm_slot_b;
    int16_t  pwm_shape_state[6];
    int16_t  pwm_slew_increment;

    /* ── Bindings (set once by pwm_init) ── */
    const pwm_calibration_t   *bound_cal;
    const pwm_input_getters_t *bound_getters;
} pwm_runtime_t;

/* ── Calibration (decoded ROM values) ───────────────────────────────── */
struct pwm_calibration {
    /* PI controller error-band thresholds */
    int16_t large_pos_error_thresh;   /* cal+0x10E */
    int16_t large_neg_error_thresh;   /* cal+0x110 */
    int16_t pi_low_clamp;             /* cal+0x128 — output low clamp + open-loop lower bound */
    int16_t pi_high_clamp;            /* fallback upper clamp; runtime uses pi_high_clamp_ceiling */

    /* CAN-decoded setpoint (FUN_64c3 family) cal constants */
    int16_t b_fb_kw_upper_bound;      /* cal+0x004 */
    int16_t b_fb_kw_lower_bound;      /* cal+0x006 */
    int16_t setpoint_offset;          /* = cal+0x4c − cal+0x4e */
    int16_t target_5e_min_clamp;      /* cal+0x12A */
    int16_t can_aux_12e_max;          /* cal+0x002 */

    /* CKP-zero acquisition (FUN_55e0 + FUN_6a4b chain) cal constants */
    int16_t ckp_zero_anchor;          /* cal+0x050 — FUN_6a4b additive anchor */
    int16_t can_dckp_offset_bias;     /* cal+0x052 — FUN_55e0/FUN_6a4b bias */
    int16_t ckp_modulus;              /* cal+0x0A2 — FUN_6a4b modulo wrap (also reused
									   *   by the FUN_6c70 process-tooth derivation
									   *   below; high byte = ckp_modulus>>8 = 30). */
	/* CKP process-tooth derivation (FUN_6c70 @ 0x6c70 — analog of default-
	 * family FUN_87ea @ 0x87ea). Computes the byte stored to R90: a
	 * segment-counter view of (CKP_ZERO_OFFSET + advance) with wrap rules.
	 * Consumed by get_ckp_process_tooth() in ckp_acquisition.c. */
	int16_t ckp_advance_per_tick;     /* cal+0x140 — angular advance added to
									   *   word[0x152] before tooth derivation
									   *   (T06235: 1707 = 20° at 85.33/°). */
	int16_t ckp_seg_wrap_threshold;   /* cal+0x09F — tooth-counter test:
									   *   if tooth > threshold → tooth -= 30
									   *   (T06235: 29). */
	int16_t ckp_teeth_per_seg;        /* cal+0x0A0 — per-segment clamp:
									   *   if tooth > teeth_per_seg → tooth = 0
									   *   (T06235: 26 teeth / 90° segment). */

    /* Recovery / sustained-error machinery */
    int16_t pi_state_c2_reload;       /* cal+0x114 — reload value for pi_state_c2 on latch */
    int16_t inj_qty_thresh;           /* cal+0x116 — inj-qty threshold for recovery vs reset */
    int16_t pi_sat_count_threshold;   /* cal+0x112 — recovery counter latch threshold */
    int16_t rpm_threshold_recovery;   /* cal+0x126 — s_recovery RPM gate (was wrongly cal+0x11E in t06211 idiom) */
    int16_t pi_cl_rpm_floor;          /* flash[0x605C] — s_pi_update OL gate threshold */

    /* PI runtime-reset values (boot-derived in ROM; cal-resident here so
     * each variant carries its own values without code edits). */
    int16_t init_p_shape_bound_pos;   /* cal+0x10A — copied to rt->p_shape_bound_pos */
    int16_t init_p_shape_bound_neg;   /* cal+0x10C — copied to rt->p_shape_bound_neg */
    int16_t init_p_gain_normal;       /* cal+0x118 — copied to rt->p_gain_normal */
    int16_t init_integ_step_normal;   /* cal+0x11E — copied to rt->integ_step_normal */
    int16_t init_p_slope_large_pos;   /* cal+0x11A — copied to rt->p_slope_large_pos */
    int16_t init_p_slope_large_neg;   /* cal+0x11C — copied to rt->p_slope_large_neg */
    int16_t init_integ_step_large_pos;/* cal+0x120 — copied to rt->integ_step_large_pos */
    int16_t init_integ_step_large_neg;/* cal+0x122 — copied to rt->integ_step_large_neg */
    int16_t init_open_loop_p_gain;    /* cal+0x124 byte, clamped to 15 — copied to rt->open_loop_p_gain */

    /* CL correction normalizers (RAM in ROM, cal-resident here for parity). */
    int16_t init_pos_error_normalizer;
    int16_t init_neg_error_normalizer;

    /* PWM stage scalars */
    int16_t pwm_detail_x0;
    int16_t pwm_detail_x1;
    int16_t pwm_cached_ptr_0F2;
    int16_t pwm_cached_ptr_102;
    int16_t pwm_const_104;

    int16_t pwm_rpm_windows[8];
    int16_t pwm_window_halfwidth;
    int16_t pwm_slew_step;

    const int16_t *pwm_y_table;
    const int16_t *shape_y_table;

    int16_t closed_loop_gain_const;

    uint16_t pwm_period_min;
    uint16_t pwm_period_max;
    uint16_t pwm_min;
    uint16_t pwm_max;
};

/* ── Submap descriptor ──────────────────────────────────────────────── */
struct pwm_submap_descr {
    uint16_t        flags;
    const int16_t  *input_ptr;
    uint16_t        count;
    const int16_t  *x;
    uint16_t        input_addr;
};

static inline void pwm_bind_submap_inputs(
    pwm_runtime_t *rt,
    pwm_submap_descr_t *descrs,
    uint16_t n)
{
    for (uint16_t i = 0; i < n; i++) {
        switch (descrs[i].input_addr) {
            case 0x0040: descrs[i].input_ptr = (const int16_t *)&rt->inputs.rpm;            break;
            case 0x0044: descrs[i].input_ptr = &rt->inputs.inj_qty_demand;                  break;
            case 0x0046: descrs[i].input_ptr = &rt->active_request;                         break;
            case 0x0142: descrs[i].input_ptr = (const int16_t *)&rt->inputs.supply_voltage; break;
            default:     descrs[i].input_ptr = NULL;                                        break;
        }
    }
}

/* ══════════════════════════════════════════════════════════════════════
 * PUBLIC API
 * ══════════════════════════════════════════════════════════════════════ */

typedef struct pwm_flash { char _unused; } pwm_flash_t;

void pwm_init(pwm_runtime_t *rt,
              const pwm_calibration_t   *cal,
              const pwm_flash_t         *flash,
              const pwm_input_getters_t *getters);

void pwm_service(pwm_runtime_t *rt);

static inline void pwm_ckp_isr(pwm_runtime_t *rt) { rt->reset_flag = 1; }

int16_t pwm_interp_lookup(const int16_t *x, const int16_t *y,
                                  uint16_t n, int16_t in);

uint16_t pwm_lut_duty(const pwm_calibration_t *cal,
                              uint16_t rpm, int16_t fbkw);

extern const pwm_calibration_t pwm_cal_rom;
extern const pwm_flash_t       pwm_flash_rom;

enum pwm_submap_id {
    PWM_SUBMAP_SETPOINT_INTERP = 0,
    PWM_SUBMAP_PWM_A           = 1,
    PWM_SUBMAP_PWM_B           = 2,
    PWM_SUBMAP_SHAPE_EVAL      = 3,
    PWM_SUBMAP_COUNT           = 4
};

extern pwm_submap_descr_t pwm_submap_descrs[PWM_SUBMAP_COUNT];
extern const int16_t *pwm_submap_y_of(uint16_t idx);

#endif /* PWM_H */
