// buttons_input.c — 5-way analog ladder scan (IDF port of ButtonManager).
//
// Faithful to ui_freenove_allinone/src/drivers/input/button_manager.cpp:
//   - ladder thresholds (mV): {0, 447, 730, 1008, 1307, 1659} for keys 1..5
//   - "no button" floor: 2800 mV
//   - decode: midpoint splits between adjacent thresholds
//   - debounce: 30 ms of stable raw key before an event fires
//   - scan period: 20 ms (~50 Hz)
// Differences vs the original: long-press detection is not ported yet (no
// consumer needs it); events go straight to display_ui_handle_key().

#include "buttons_input.h"
#include "display_ui.h"
#include "board_pins_mediakit.h"

#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG = "buttons";

// GPIO19 → ADC2_CHANNEL_8 on the ESP32-S3.
#define BTN_ADC_UNIT    ADC_UNIT_2
#define BTN_ADC_CHANNEL ADC_CHANNEL_8

#define BTN_DEBOUNCE_MS 30
#define BTN_SCAN_MS     20

static const int kThresholdsMv[6] = {0, 447, 730, 1008, 1307, 1659};
static const int kNoButtonMv      = 2800;

static adc_oneshot_unit_handle_t s_adc;
static adc_cali_handle_t         s_cali;
static bool                      s_cali_ok;

static uint8_t decode_key(int mv) {
    if (mv >= (kThresholdsMv[5] + kNoButtonMv) / 2) return 0;   // released
    if (mv < (kThresholdsMv[1] + kThresholdsMv[2]) / 2) return 1;
    if (mv < (kThresholdsMv[2] + kThresholdsMv[3]) / 2) return 2;
    if (mv < (kThresholdsMv[3] + kThresholdsMv[4]) / 2) return 3;
    if (mv < (kThresholdsMv[4] + kThresholdsMv[5]) / 2) return 4;
    return 5;
}

static void scan_task(void *arg) {
    (void) arg;
    uint8_t    raw_key       = 0;
    TickType_t raw_changed   = xTaskGetTickCount();
    uint8_t    reported_key  = 0;

    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(BTN_SCAN_MS));

        int raw = 0;
        // ADC2 + Wi-Fi arbitration can time out — skip the sample.
        if (adc_oneshot_read(s_adc, BTN_ADC_CHANNEL, &raw) != ESP_OK) continue;

        int mv;
        if (s_cali_ok) {
            if (adc_cali_raw_to_voltage(s_cali, raw, &mv) != ESP_OK) continue;
        } else {
            // crude conversion without calibration: 12-bit, ~3100 mV span
            mv = (raw * 3100) / 4095;
        }

        const uint8_t key = decode_key(mv);
        const TickType_t now = xTaskGetTickCount();

        if (key != raw_key) {
            raw_key = key;
            raw_changed = now;
            continue;
        }
        if ((now - raw_changed) < pdMS_TO_TICKS(BTN_DEBOUNCE_MS)) continue;

        // Stable. Fire on press transitions only (release → key).
        if (key != reported_key) {
            reported_key = key;
            if (key != 0) {
                display_ui_handle_key(key);
            }
        }
    }
}

esp_err_t buttons_input_init(void) {
    adc_oneshot_unit_init_cfg_t ucfg = {
        .unit_id  = BTN_ADC_UNIT,
        .ulp_mode = ADC_ULP_MODE_DISABLE,
    };
    esp_err_t err = adc_oneshot_new_unit(&ucfg, &s_adc);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "adc_oneshot_new_unit: %s", esp_err_to_name(err));
        return err;
    }

    adc_oneshot_chan_cfg_t ccfg = {
        .atten    = ADC_ATTEN_DB_12,   // full-range, like the original 11dB
        .bitwidth = ADC_BITWIDTH_12,
    };
    err = adc_oneshot_config_channel(s_adc, BTN_ADC_CHANNEL, &ccfg);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "adc channel cfg: %s", esp_err_to_name(err));
        adc_oneshot_del_unit(s_adc);
        s_adc = NULL;
        return err;
    }

    adc_cali_curve_fitting_config_t cal = {
        .unit_id  = BTN_ADC_UNIT,
        .chan     = BTN_ADC_CHANNEL,
        .atten    = ADC_ATTEN_DB_12,
        .bitwidth = ADC_BITWIDTH_12,
    };
    s_cali_ok = (adc_cali_create_scheme_curve_fitting(&cal, &s_cali) == ESP_OK);
    if (!s_cali_ok) {
        ESP_LOGW(TAG, "no ADC calibration — using crude raw→mV conversion");
    }

    if (xTaskCreate(scan_task, "btn_scan", 3072, NULL, 4, NULL) != pdPASS) {
        ESP_LOGE(TAG, "scan task create failed");
        adc_oneshot_del_unit(s_adc);
        s_adc = NULL;
        return ESP_ERR_NO_MEM;
    }

    ESP_LOGI(TAG, "5-way ladder up (GPIO19/ADC2_CH8, debounce %d ms)",
             BTN_DEBOUNCE_MS);
    return ESP_OK;
}
