// p6_nfc.c — P6 NFC / alchemical-symbols puzzle driver.
//
// All real implementation is inside #if CONFIG_ZACUS_P6_NFC_ENABLE so the
// object compiles to pure stubs when the flag is off (default). No #ifdef
// leaks into callers.
//
// Hardware HAL for the NFC reader is STUBBED: nfc_hal_read_uid() returns
// false (no card present) and logs once per arm cycle. The real HAL will be
// implemented when the PN532/PN7150 module is wired (replace the stub block
// marked with "STUB" below).
//
// Logic adapted from puzzles/p6_symboles_nfc/main/main.c, repackaged as a
// library with arm/disarm API and a solved callback.

#include "p6_nfc.h"

#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_err.h"

static const char *TAG = "p6_nfc";

#if CONFIG_ZACUS_P6_NFC_ENABLE

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include <stdbool.h>
#include <stdio.h>

// ─── I2C / SPI bus init (HAL layer) ─────────────────────────────────────────
//
// The bus init and NFC read are separated into nfc_hal_init() /
// nfc_hal_read_uid() so the stub is easy to swap for real silicon code.

#if CONFIG_ZACUS_P6_BUS_I2C
#include "driver/i2c_master.h"

static i2c_master_bus_handle_t s_i2c_bus   = NULL;
static i2c_master_dev_handle_t s_i2c_dev   = NULL;

static esp_err_t nfc_hal_init(void)
{
    i2c_master_bus_config_t bus_cfg = {
        .i2c_port      = I2C_NUM_0,
        .sda_io_num    = CONFIG_ZACUS_P6_I2C_SDA,
        .scl_io_num    = CONFIG_ZACUS_P6_I2C_SCL,
        .clk_source    = I2C_CLK_SRC_DEFAULT,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };
    esp_err_t err = i2c_new_master_bus(&bus_cfg, &s_i2c_bus);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "i2c_new_master_bus: %s", esp_err_to_name(err));
        return err;
    }

    i2c_device_config_t dev_cfg = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address  = (uint16_t)CONFIG_ZACUS_P6_I2C_ADDR,
        .scl_speed_hz    = 100000,
    };
    err = i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &s_i2c_dev);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "i2c_master_bus_add_device(addr=0x%02X): %s",
                 CONFIG_ZACUS_P6_I2C_ADDR, esp_err_to_name(err));
        return err;
    }

    ESP_LOGI(TAG, "I2C bus init OK (SDA=%d SCL=%d addr=0x%02X)",
             CONFIG_ZACUS_P6_I2C_SDA, CONFIG_ZACUS_P6_I2C_SCL,
             CONFIG_ZACUS_P6_I2C_ADDR);
    return ESP_OK;
}

// STUB: real I2C read of PN532/PN7150 comes here.
// Returns true and fills uid_out[4] when a card is present.
static bool nfc_hal_read_uid(uint8_t uid_out[4])
{
    (void)uid_out;
    // HAL stub — hardware not yet wired.  Replace with PN532/PN7150 I2C
    // read sequence once the module is connected.
    ESP_LOGD(TAG, "NFC read (stub, I2C 0x%02X)", CONFIG_ZACUS_P6_I2C_ADDR);
    return false;
}

#elif CONFIG_ZACUS_P6_BUS_SPI
#include "driver/spi_master.h"

static spi_device_handle_t s_spi = NULL;

static esp_err_t nfc_hal_init(void)
{
    spi_bus_config_t buscfg = {
        .miso_io_num     = 13,   // default SPI2 pins — override via sdkconfig if needed
        .mosi_io_num     = 11,
        .sclk_io_num     = 12,
        .quadwp_io_num   = -1,
        .quadhd_io_num   = -1,
        .max_transfer_sz = 64,
    };
    // Use SPI3_HOST to avoid conflict with possible SD card on SPI2.
    esp_err_t err = spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
    if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
        ESP_LOGE(TAG, "spi_bus_initialize(SPI3): %s", esp_err_to_name(err));
        return err;
    }

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1000000,   // 1 MHz (PN532 max SPI is 5 MHz)
        .mode           = 0,
        .spics_io_num   = 10,        // CS pin — adjust via Kconfig when available
        .queue_size     = 4,
    };
    err = spi_bus_add_device(SPI3_HOST, &devcfg, &s_spi);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "spi_bus_add_device: %s", esp_err_to_name(err));
        return err;
    }

    ESP_LOGI(TAG, "SPI bus init OK (SPI3, CS=10)");
    return ESP_OK;
}

// STUB: real SPI read of PN532 comes here.
static bool nfc_hal_read_uid(uint8_t uid_out[4])
{
    (void)uid_out;
    ESP_LOGD(TAG, "NFC read (stub, SPI)");
    return false;
}

#endif // bus type

// ─── UID comparison helper ───────────────────────────────────────────────────

// Parse a colon-separated hex UID string "A1:B2:C3:D4" into uid[4].
// Returns true on success.
static bool parse_uid(const char *str, uint8_t uid[4])
{
    unsigned int b[4];
    if (sscanf(str, "%x:%x:%x:%x", &b[0], &b[1], &b[2], &b[3]) != 4)
        return false;
    for (int i = 0; i < 4; i++) uid[i] = (uint8_t)b[i];
    return true;
}

// ─── State ──────────────────────────────────────────────────────────────────

static char s_expected_uid[20]  = {0};   // "A1:B2:C3:D4\0"
static uint8_t s_expected_raw[4] = {0};
static bool s_expected_valid    = false;

static volatile bool s_armed    = false;
static volatile bool s_solved   = false;

static p6_nfc_solved_cb_t s_solved_cb = NULL;

static bool s_initialised = false;

// ─── NFC polling task ────────────────────────────────────────────────────────

static void nfc_task(void *arg)
{
    (void)arg;

    uint8_t uid[4]      = {0};
    uint8_t last_uid[4] = {0};
    uint32_t last_read_ms = 0;

    for (;;) {
        if (!s_armed || s_solved) {
            vTaskDelay(pdMS_TO_TICKS(200));
            continue;
        }

        uint32_t t = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);

        if (nfc_hal_read_uid(uid)) {
            // Debounce: ignore same tag within 1 s.
            bool same = (memcmp(uid, last_uid, 4) == 0);
            if (!same || (t - last_read_ms) > 1000) {
                memcpy(last_uid, uid, 4);
                last_read_ms = t;

                ESP_LOGI(TAG, "tag read: %02X:%02X:%02X:%02X",
                         uid[0], uid[1], uid[2], uid[3]);

                if (s_expected_valid &&
                    memcmp(uid, s_expected_raw, 4) == 0) {
                    s_solved = true;
                    ESP_LOGI(TAG, "SOLVED: tag %s matched", s_expected_uid);
                    if (s_solved_cb) {
                        s_solved_cb();
                    }
                } else {
                    ESP_LOGW(TAG, "tag %02X:%02X:%02X:%02X not the expected %s",
                             uid[0], uid[1], uid[2], uid[3], s_expected_uid);
                }
            }
        }

        vTaskDelay(pdMS_TO_TICKS(200));   // 5 Hz polling
    }
}

// ─── Public API ─────────────────────────────────────────────────────────────

esp_err_t p6_nfc_init(void)
{
    if (s_initialised) return ESP_OK;

    esp_err_t err = nfc_hal_init();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "nfc_hal_init failed: %s", esp_err_to_name(err));
        return err;
    }

    BaseType_t rc = xTaskCreate(nfc_task, "p6_nfc", 3072, NULL, 5, NULL);
    if (rc != pdPASS) {
        ESP_LOGE(TAG, "xTaskCreate(p6_nfc) failed");
        return ESP_ERR_NO_MEM;
    }

    s_initialised = true;
#if CONFIG_ZACUS_P6_READER_PN532 && CONFIG_ZACUS_P6_BUS_I2C
    ESP_LOGI(TAG, "init OK (PN532, I2C, stub HAL)");
#elif CONFIG_ZACUS_P6_READER_PN532
    ESP_LOGI(TAG, "init OK (PN532, SPI, stub HAL)");
#else
    ESP_LOGI(TAG, "init OK (PN7150, I2C, stub HAL)");
#endif
    return ESP_OK;
}

void p6_nfc_arm(const char *expected_uid, p6_nfc_solved_cb_t solved_cb)
{
    if (!s_initialised) {
        ESP_LOGW(TAG, "arm called before init — ignored");
        return;
    }

    s_armed    = false;
    s_solved   = false;
    s_expected_valid = false;
    memset(s_expected_uid, 0, sizeof(s_expected_uid));
    memset(s_expected_raw, 0, sizeof(s_expected_raw));

    if (expected_uid && expected_uid[0]) {
        strncpy(s_expected_uid, expected_uid, sizeof(s_expected_uid) - 1);
        s_expected_uid[sizeof(s_expected_uid) - 1] = '\0';
        s_expected_valid = parse_uid(s_expected_uid, s_expected_raw);
        if (!s_expected_valid) {
            ESP_LOGW(TAG, "arm: could not parse UID \"%s\" — tag match disabled",
                     expected_uid);
        }
    }

    s_solved_cb = solved_cb;
    s_armed     = true;
    ESP_LOGI(TAG, "armed, expected UID \"%s\"", s_expected_uid);
}

void p6_nfc_disarm(void)
{
    s_armed  = false;
    s_solved = false;
    ESP_LOGI(TAG, "disarmed");
}

#else // CONFIG_ZACUS_P6_NFC_ENABLE not set — emit stubs only

esp_err_t p6_nfc_init(void)
{
    return ESP_OK;
}

void p6_nfc_arm(const char *expected_uid, p6_nfc_solved_cb_t solved_cb)
{
    (void)expected_uid;
    (void)solved_cb;
    /* stub — CONFIG_ZACUS_P6_NFC_ENABLE=n */
}

void p6_nfc_disarm(void)
{
    /* stub — CONFIG_ZACUS_P6_NFC_ENABLE=n */
}

#endif // CONFIG_ZACUS_P6_NFC_ENABLE
