// ============================================================================
// watchdog.cpp — Task watchdog timer + crash coredump handler
// ============================================================================

#include "watchdog.h"

#ifdef ARDUINO
#include <Arduino.h>
#include <esp_task_wdt.h>
#include <esp_system.h>
#include <LittleFS.h>

// ---------------------------------------------------------------------------
// Coredump rotation state — persisted in .rodata, mutated at runtime only
// ---------------------------------------------------------------------------
static uint8_t s_next_slot = 0;  // next slot index to write (0..WATCHDOG_MAX_COREDUMPS-1)

// ---------------------------------------------------------------------------
// Panic handler — installed by watchdog_register_panic_dump()
// NOTE: This runs from the ESP-IDF panic path; keep it minimal and avoid
// dynamic allocation.  We write a fixed-size header + register context.
// ---------------------------------------------------------------------------

// Header layout (32 bytes total, all little-endian):
//   [0..3]   magic  0xC0DED054
//   [4..7]   reason_code (0=unknown, 1=abort, 2=exception, 3=wdt, 4=stack)
//   [8..11]  pc register (if available)
//   [12..15] sp register (if available)
//   [16..19] timestamp millis (may be 0 if timer not running)
//   [20..31] reason string, null-terminated, max 12 chars

static const uint32_t COREDUMP_MAGIC = 0xC0DED054u;

static void IRAM_ATTR coredump_panic_handler(void* info) {
    // Build a minimal header in a stack-local buffer — no heap allowed here.
    uint8_t header[32];
    uint32_t* w = (uint32_t*)header;
    w[0] = COREDUMP_MAGIC;
    w[1] = 2;   // generic exception — richer classification would require XtExcFrame
    w[2] = 0;   // PC not extracted (would need XtExcFrame cast from info)
    w[3] = 0;   // SP not extracted
    w[4] = (uint32_t)millis();
    // reason string
    const char* reason = "exception";
    uint8_t* str_field = &header[20];
    for (int i = 0; i < 11 && reason[i]; i++) str_field[i] = (uint8_t)reason[i];
    str_field[11] = 0;

    // Determine file path for this slot
    static char path[28];
    path[0]  = '/';
    path[1]  = 'c';
    path[2]  = 'o';
    path[3]  = 'r';
    path[4]  = 'e';
    path[5]  = 'd';
    path[6]  = 'u';
    path[7]  = 'm';
    path[8]  = 'p';
    path[9]  = '_';
    path[10] = (char)('0' + (s_next_slot % WATCHDOG_MAX_COREDUMPS));
    path[11] = '.';
    path[12] = 'b';
    path[13] = 'i';
    path[14] = 'n';
    path[15] = '\0';

    // Advance slot counter for next crash
    s_next_slot = (uint8_t)((s_next_slot + 1) % WATCHDOG_MAX_COREDUMPS);

    // LittleFS open may fail if FS is not mounted — tolerate silently
    File f = LittleFS.open(path, FILE_WRITE);
    if (f) {
        f.write(header, sizeof(header));
        f.close();
    }

    // Do not loop — let the default panic handler continue for reset
    (void)info;
}

#endif // ARDUINO

// ---------------------------------------------------------------------------
void watchdog_init(uint32_t timeout_seconds) {
#ifdef ARDUINO
  // ESP-IDF 5.x prefers a config struct; IDF 4.x (used by espressif32@6.x
  // Arduino framework) takes (timeout_seconds, panic_on_timeout).
#  if ESP_IDF_VERSION_MAJOR >= 5
    esp_task_wdt_config_t cfg = {
        .timeout_ms    = timeout_seconds * 1000u,
        .idle_core_mask = 0,
        .trigger_panic  = true
    };
    esp_task_wdt_init(&cfg);
#  else
    esp_task_wdt_init(timeout_seconds, true);
#  endif
    esp_task_wdt_add(nullptr);
#else
    (void)timeout_seconds;
#endif
}

// ---------------------------------------------------------------------------
void watchdog_feed() {
#ifdef ARDUINO
    esp_task_wdt_reset();
#endif
}

// ---------------------------------------------------------------------------
void watchdog_register_panic_dump() {
#ifdef ARDUINO
    esp_register_shutdown_handler([]() {
        // shutdown handler — not strictly the panic path but fires on
        // esp_restart() calls so we can leave a breadcrumb.
    });

    // Install custom panic handler.  ESP-IDF 5.x exposes
    // esp_set_panic_handler(); for IDF 4.x we rely on the weak-link override.
    // On IDF 5.x this registers our handler in the panic dispatch chain.
#if ESP_IDF_VERSION_MAJOR >= 5
    esp_set_panic_handler(coredump_panic_handler);
#endif
    // IDF 4.x: coredump_panic_handler is compiled in; integrator may call it
    // from a custom abort() override if needed (see BATCH_A_INTEGRATION_NOTES).
#endif
}
