/* hw_button.c — hardware button debounce + gesture detection (Issue #682)
 *
 * Debounce FSM:
 *   IDLE     → (raw press detected)       → DEBOUNCING
 *   DEBOUNCING → (still pressed after BTN_DEBOUNCE_MS) → HELD
 *   HELD     → (released)                → classify press type, back to IDLE
 *
 * Press types:
 *   SHORT  held < BTN_LONG_MIN_MS from confirmed start
 *   LONG   held >= BTN_LONG_MIN_MS
 *
 * Sequence detection (operates on classified presses):
 *   Buffer up to 3 presses.  Recognised patterns:
 *     [SHORT, SHORT, LONG]  ->  BTN_EVENT_REARM_COMBO  (fires on LONG release)
 *     [SHORT] + BTN_COMMIT_MS timeout  ->  BTN_EVENT_PARK
 *   Sequence reset after BTN_SEQ_TIMEOUT_MS from first press.
 */

#include "hw_button.h"
#include "config.h"

#ifndef TEST_HOST
#include "stm32f7xx_hal.h"
#endif

/* ---- Timing defaults (override in config.h) ---- */
#ifndef BTN_DEBOUNCE_MS
#define BTN_DEBOUNCE_MS    20u
#endif
#ifndef BTN_LONG_MIN_MS
#define BTN_LONG_MIN_MS    1500u
#endif
#ifndef BTN_COMMIT_MS
#define BTN_COMMIT_MS      500u
#endif
#ifndef BTN_SEQ_TIMEOUT_MS
#define BTN_SEQ_TIMEOUT_MS 3000u
#endif

/* ---- Press type ---- */
typedef enum {
    _PT_SHORT = 1u,
    _PT_LONG  = 2u,
} _press_type_t;

/* ---- Debounce state ---- */
typedef enum {
    _BTN_IDLE,
    _BTN_DEBOUNCING,
    _BTN_HELD,
} _btn_state_t;

static _btn_state_t s_state     = _BTN_IDLE;
static uint32_t     s_trans_ms  = 0u;   /* timestamp of last FSM transition */
static bool         s_pressed   = false;

/* ---- Sequence buffer ---- */
#define _SEQ_MAX 3u
static _press_type_t s_seq[_SEQ_MAX];
static uint8_t       s_seq_len       = 0u;
static uint32_t      s_seq_first_ms  = 0u;
static uint32_t      s_seq_last_ms   = 0u;

/* ---- GPIO read ---- */
#ifdef TEST_HOST
static bool s_test_raw = false;
void hw_button_inject(bool pressed) { s_test_raw = pressed; }
static bool _read_raw(void) { return s_test_raw; }
#else
static bool _read_raw(void)
{
    return HAL_GPIO_ReadPin(BTN_PORT, BTN_PIN) == GPIO_PIN_RESET; /* active-low */
}
#endif

void hw_button_init(void)
{
#ifndef TEST_HOST
    __HAL_RCC_GPIOC_CLK_ENABLE();      /* BTN_PORT assumed GPIOC; adjust if needed */
    GPIO_InitTypeDef g = {0};
    g.Pin  = BTN_PIN;
    g.Mode = GPIO_MODE_INPUT;
    g.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(BTN_PORT, &g);
#endif
    s_state    = _BTN_IDLE;
    s_seq_len  = 0u;
    s_pressed  = false;
}

bool hw_button_is_pressed(void)
{
    return s_pressed;
}

/* Record a classified press into the sequence buffer and check for patterns. */
static hw_btn_event_t _record_press(_press_type_t pt, uint32_t now_ms)
{
    if (s_seq_len == 0u) {
        s_seq_first_ms = now_ms;
    }
    s_seq_last_ms = now_ms;

    if (s_seq_len < _SEQ_MAX) {
        s_seq[s_seq_len++] = pt;
    }

    /* Check REARM_COMBO: SHORT + SHORT + LONG */
    if (s_seq_len == 3u &&
        s_seq[0] == _PT_SHORT &&
        s_seq[1] == _PT_SHORT &&
        s_seq[2] == _PT_LONG) {
        s_seq_len = 0u;
        return BTN_EVENT_REARM_COMBO;
    }

    return BTN_EVENT_NONE;
}

hw_btn_event_t hw_button_tick(uint32_t now_ms)
{
    bool raw = _read_raw();

    /* ---- Debounce FSM ---- */
    switch (s_state) {
    case _BTN_IDLE:
        if (raw) {
            s_state    = _BTN_DEBOUNCING;
            s_trans_ms = now_ms;
        }
        break;

    case _BTN_DEBOUNCING:
        if (!raw) {
            /* Released before debounce elapsed — bounce, ignore */
            s_state = _BTN_IDLE;
        } else if ((now_ms - s_trans_ms) >= BTN_DEBOUNCE_MS) {
            s_state   = _BTN_HELD;
            s_trans_ms = now_ms;   /* record confirmed press-start time */
            s_pressed  = true;
        }
        break;

    case _BTN_HELD:
        if (!raw) {
            s_pressed  = false;
            uint32_t held_ms = now_ms - s_trans_ms;
            _press_type_t pt = (held_ms >= BTN_LONG_MIN_MS) ? _PT_LONG : _PT_SHORT;
            s_state = _BTN_IDLE;
            hw_btn_event_t ev = _record_press(pt, now_ms);
            if (ev != BTN_EVENT_NONE) {
                return ev;
            }
        }
        break;
    }

    /* ---- Sequence timeout / commit check (only when not currently held) ---- */
    if (s_state == _BTN_IDLE && s_seq_len > 0u) {
        uint32_t since_first = now_ms - s_seq_first_ms;
        uint32_t since_last  = now_ms - s_seq_last_ms;

        /* Whole sequence window expired — abandon */
        if (since_first >= BTN_SEQ_TIMEOUT_MS) {
            s_seq_len = 0u;
            return BTN_EVENT_NONE;
        }

        /* Single short press + BTN_COMMIT_MS of quiet -> PARK */
        if (s_seq_len == 1u &&
            s_seq[0] == _PT_SHORT &&
            since_last >= BTN_COMMIT_MS) {
            s_seq_len = 0u;
            return BTN_EVENT_PARK;
        }
    }

    return BTN_EVENT_NONE;
}
