/**
 * @file c6_lp_core.c
 * @brief LP-core wake-on-motion hibernation — ADR-110 Phase 5 (full).
 *
 * Two operating modes, controlled by CONFIG_C6_LP_CORE_ENABLE:
 *
 *   1. ENABLED  — real LP-core RISC-V program polls the wake GPIO at
 *      LP_TIMER cadence (default 10 ms), debounces N matching samples,
 *      and triggers an HP wake via `ulp_lp_core_wakeup_main_processor()`.
 *      HP enters deep sleep with `ESP_SLEEP_WAKEUP_ULP` as the source.
 *      Targets ~5 µA average current (datasheet figure for LP-core +
 *      RTC peripherals powered down). The LP binary is built by
 *      `ulp_embed_binary(...)` in main/CMakeLists.txt from lp_core/main.c.
 *
 *   2. DISABLED — falls back to plain deep-sleep + GPIO wake-up
 *      (`esp_deep_sleep_enable_gpio_wakeup`). No debounce, no
 *      sub-10 µA floor, but no LP toolchain dependency either.
 *      This is the path the v0.6.6 firmware shipped with.
 *
 * Both paths share `c6_lp_core_arm()` / `c6_lp_core_hibernate_and_wait()`
 * so call sites in main.c don't change between modes.
 */

#include "sdkconfig.h"

#if defined(CONFIG_IDF_TARGET_ESP32C6) && defined(CONFIG_ULP_COPROC_TYPE_LP_CORE)

#include "c6_lp_core.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "soc/soc_caps.h"
#include <string.h>

#if defined(CONFIG_C6_LP_CORE_ENABLE)
#include "ulp_lp_core.h"
/* ulp_main.h is auto-generated by `ulp_embed_binary(ulp_main, ...)` and
 * exports every `volatile` global from lp_core/main.c with the `ulp_`
 * prefix. Include is guarded so disabled builds don't try to find a
 * file the build system hasn't generated. */
#include "ulp_main.h"
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[]   asm("_binary_ulp_main_bin_end");
#endif

static const char *TAG = "c6_lp";

static int  s_wake_gpio   = -1;
static bool s_active_high = true;
static bool s_armed       = false;

#ifndef CONFIG_C6_LP_POLL_PERIOD_US
#define CONFIG_C6_LP_POLL_PERIOD_US  10000  /* 100 Hz default poll cadence */
#endif

#ifndef CONFIG_C6_LP_DEBOUNCE_SAMPLES
#define CONFIG_C6_LP_DEBOUNCE_SAMPLES 3
#endif

esp_err_t c6_lp_core_arm(int wake_gpio, bool active_high)
{
    if (wake_gpio < 0) {
        ESP_LOGE(TAG, "invalid wake_gpio=%d", wake_gpio);
        return ESP_ERR_INVALID_ARG;
    }
    s_wake_gpio   = wake_gpio;
    s_active_high = active_high;

    /* GPIO must be in the LP/RTC domain for either wake path. */
    esp_err_t ret = rtc_gpio_init(wake_gpio);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "rtc_gpio_init(%d) failed: %s", wake_gpio, esp_err_to_name(ret));
        return ret;
    }
    rtc_gpio_set_direction(wake_gpio, RTC_GPIO_MODE_INPUT_ONLY);
    /* Floating inputs in deep sleep are an antenna — disable internal pulls
     * only if the user has an external pull on the motion line; we leave
     * default pulls so a disconnected pin doesn't toggle randomly. */

#if defined(CONFIG_C6_LP_CORE_ENABLE)
    /* --- Real LP-core path --- */

    /* On C6, LP-IO maps 1:1 to GPIO for indices 0..7. Validate. */
    if (wake_gpio > 7) {
        ESP_LOGE(TAG, "LP-core path requires LP-IO 0..7, got GPIO %d", wake_gpio);
        return ESP_ERR_INVALID_ARG;
    }

    /* Load the LP-core binary blob. */
    esp_err_t err = ulp_lp_core_load_binary(
        ulp_main_bin_start,
        (size_t)(ulp_main_bin_end - ulp_main_bin_start));
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "ulp_lp_core_load_binary failed: %s", esp_err_to_name(err));
        return err;
    }

    /* Hand the GPIO parameters to the LP program via shared symbols.
     * These are declared `volatile` in lp_core/main.c so the HP write
     * is observed by LP on the next iteration. */
    ulp_wake_gpio_num    = (uint32_t)wake_gpio;
    ulp_wake_active_high = active_high ? 1u : 0u;
    ulp_debounce_samples = CONFIG_C6_LP_DEBOUNCE_SAMPLES;
    ulp_motion_count     = 0;
    ulp_poll_count       = 0;
    ulp_last_gpio_level  = 0;

    /* Configure LP-timer wakeup at the configured poll period and start the
     * LP-core. `ulp_lp_core_run` is non-blocking; the LP core begins running
     * the program immediately and the HP core can proceed to deep sleep. */
    ulp_lp_core_cfg_t cfg = {
        .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER,
        .lp_timer_sleep_duration_us = CONFIG_C6_LP_POLL_PERIOD_US,
    };
    err = ulp_lp_core_run(&cfg);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "ulp_lp_core_run failed: %s", esp_err_to_name(err));
        return err;
    }

    /* Tell deep-sleep that the LP-core is our wake source. */
    err = esp_sleep_enable_ulp_wakeup();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_sleep_enable_ulp_wakeup failed: %s", esp_err_to_name(err));
        return err;
    }

    s_armed = true;
    ESP_LOGI(TAG, "LP-core armed: gpio=%d active_%s debounce=%d poll=%d µs",
             wake_gpio, active_high ? "high" : "low",
             CONFIG_C6_LP_DEBOUNCE_SAMPLES, CONFIG_C6_LP_POLL_PERIOD_US);
    return ESP_OK;

#else
    /* --- Fallback path: plain deep-sleep GPIO wakeup (~10 µA floor) --- */
    uint64_t mask = 1ULL << wake_gpio;
    esp_deepsleep_gpio_wake_up_mode_t mode = active_high
        ? ESP_GPIO_WAKEUP_GPIO_HIGH
        : ESP_GPIO_WAKEUP_GPIO_LOW;
    esp_err_t err = esp_deep_sleep_enable_gpio_wakeup(mask, mode);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "enable_gpio_wakeup failed: %s", esp_err_to_name(err));
        return err;
    }
    s_armed = true;
    ESP_LOGI(TAG, "GPIO-wakeup armed (no LP-core): gpio=%d active_%s",
             wake_gpio, active_high ? "high" : "low");
    return ESP_OK;
#endif
}

void c6_lp_core_hibernate_and_wait(void)
{
    if (!s_armed) {
        ESP_LOGW(TAG, "hibernate called without arm — sleeping with no wake source");
    }
    /* Power down the RTC peripheral domain — the LP-core itself stays
     * powered on the LP power domain so it can keep polling. */
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);

#if defined(CONFIG_C6_LP_CORE_ENABLE)
    ESP_LOGI(TAG, "entering deep sleep — LP-core polling, target ≤5 µA");
#else
    ESP_LOGI(TAG, "entering deep sleep — GPIO wakeup, target ~10 µA");
#endif
    esp_deep_sleep_start();
    /* Never returns. */
}

bool c6_lp_core_was_motion_wake(void)
{
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
#if defined(CONFIG_C6_LP_CORE_ENABLE)
    /* Real LP-core path: wakeup cause is ULP (LP-core triggered HP). */
    if (cause == ESP_SLEEP_WAKEUP_ULP) return true;
#endif
    /* Fallback path or alternate GPIO wakeup. */
    return cause == ESP_SLEEP_WAKEUP_GPIO || cause == ESP_SLEEP_WAKEUP_EXT1;
}

#if defined(CONFIG_C6_LP_CORE_ENABLE)
uint32_t c6_lp_core_motion_count(void)
{
    return (uint32_t)ulp_motion_count;
}

uint32_t c6_lp_core_poll_count(void)
{
    return (uint32_t)ulp_poll_count;
}
#else
uint32_t c6_lp_core_motion_count(void) { return 0; }
uint32_t c6_lp_core_poll_count(void)   { return 0; }
#endif

#endif  /* CONFIG_IDF_TARGET_ESP32C6 && CONFIG_ULP_COPROC_TYPE_LP_CORE */
