/*
 * main.c — PLIP voice annex application entry point.
 *
 * Board: AI-Thinker ESP32-A1S Audio Kit V2.2 (ESP32, ES8388, SD, mic, HP)
 * Target: ESP-IDF 5.4, esp32 target (NOT esp32s3)
 *
 * Boot sequence:
 *   1. NVS init
 *   2. audio_init()  — ES8388 + I2S (Phase A)
 *   3. audio_play_tone(440Hz, 1s)  — Phase A proof (sound before WiFi)
 *   4. net_init()   — WiFi STA + HTTP server (Phase B)
 *   5. scenario_mesh_init()  — ESP-NOW CMD receiver (Phase C)
 *   6. hook_client_init()    — hook event forwarder (Phase D)
 *   7. phone_init()          — off-hook GPIO monitor (Phase D)
 */

#include <stdio.h>
#include <string.h>

#include "esp_log.h"
#include "esp_err.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "board_config.h"
#include "audio.h"
#include "net.h"
#include "cmd_exec.h"
#include "hook_client.h"
#include "phone.h"
#include "slic.h"
#include "scenario_mesh.h"
#include "dialer.h"
#include "conversation.h"

static const char *TAG = "plip-main";

/* ── ESP-NOW CMD/EVT text callback ─────────────────────────────────────────── */

static void on_mesh_text(uint8_t kind, const uint8_t src_mac[6], const char *text)
{
    if (kind == SCENARIO_MESH_TEXT_CMD) {
        ESP_LOGI(TAG, "ESP-NOW CMD from %02x:%02x:%02x:%02x:%02x:%02x: %.80s",
                 src_mac[0], src_mac[1], src_mac[2],
                 src_mac[3], src_mac[4], src_mac[5], text);
        esp_err_t err = cmd_exec_handle(text, strlen(text));
        if (err != ESP_OK && err != ESP_ERR_NOT_SUPPORTED) {
            ESP_LOGW(TAG, "cmd_exec_handle: %s", esp_err_to_name(err));
        }
    } else {
        ESP_LOGI(TAG, "ESP-NOW EVT from %02x:%02x:%02x:%02x:%02x:%02x: %.80s",
                 src_mac[0], src_mac[1], src_mac[2],
                 src_mac[3], src_mac[4], src_mac[5], text);
    }
}

/* ── Boot task — runs all phases with sufficient stack ────────────────────── */

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

    /* ── 1b. SLIC power-up (before audio — PD must be released early) ── */
    esp_err_t ret = slic_init();
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "slic_init FAILED: %s — SLIC remains in power-down (no audio path, no hook sense)",
                 esp_err_to_name(ret));
    } else {
        ESP_LOGI(TAG, "SLIC powered up: PD=GPIO%d OD-HIGH, SHK=GPIO%d active-HIGH, RM=GPIO%d, FR=GPIO%d",
                 PLIP_SLIC_PD, PLIP_SLIC_SHK, PLIP_SLIC_RM, PLIP_SLIC_FR);
    }

    /* ── 2. Audio init (Phase A) ── */
    ret = audio_init();
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "audio_init FAILED: %s", esp_err_to_name(ret));
        ESP_LOGE(TAG, "PHASE A BLOCKED — check ES8388 I2C wiring (SDA=33, SCL=32)");
        /* Continue anyway — WiFi + hook still work. */
    } else {
        /* ── 3. Phase A proof: 440 Hz test tone ── */
        ESP_LOGI(TAG, "Phase A: playing 440 Hz test tone (1s) — LISTEN FOR SOUND");
        audio_play_tone(440.0f, 1000);
        vTaskDelay(pdMS_TO_TICKS(200));
        ESP_LOGI(TAG, "Phase A: tone done. If you heard it, Phase A is VERIFIED.");
    }

    /* ── 4. Network + HTTP server (Phase B) ── */
    ret = net_init();
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Phase B: WiFi + httpd up. Verify: curl http://<PLIP_IP>/status");
    } else if (ret == ESP_ERR_TIMEOUT) {
        ESP_LOGW(TAG, "Phase B: WiFi timeout — running offline (ESP-NOW still active)");
    } else {
        ESP_LOGW(TAG, "Phase B: net_init error %s", esp_err_to_name(ret));
    }

    /* ── 5. ESP-NOW CMD receiver + scenario mesh (Phase C) ── */
    /* scenario_mesh_init() requires WiFi to be started (done in net_init). */
    ret = scenario_mesh_init(NULL);   /* receive CMD only — no scenario apply */
    if (ret != ESP_OK) {
        ESP_LOGW(TAG, "scenario_mesh_init: %s — ESP-NOW unavailable",
                 esp_err_to_name(ret));
    } else {
        ret = scenario_mesh_set_text_cb(on_mesh_text);
        if (ret == ESP_OK) {
            ESP_LOGI(TAG, "Phase C: ESP-NOW CMD receiver up");
        }
    }

    /* ── 6. Hook client (Phase D) ── */
    ret = hook_client_init(NULL);  /* uses CONFIG_PLIP_MASTER_URL */
    if (ret != ESP_OK) {
        ESP_LOGW(TAG, "hook_client_init: %s", esp_err_to_name(ret));
    }

    /* ── Stage 1: dialer + conversation state machine ── */
    dialer_init();
    conversation_init();
    ESP_LOGI(TAG, "Stage 1: dialer + conversation state machine up");

    /* ── 7. Phone task (Phase D) ── */
    ret = phone_init();
    if (ret != ESP_OK) {
        ESP_LOGW(TAG, "phone_init: %s", esp_err_to_name(ret));
    } else {
        ESP_LOGI(TAG, "Phase D: off-hook GPIO monitor up (GPIO=%d)",
                 CONFIG_PLIP_HOOK_GPIO);
    }

    ESP_LOGI(TAG, "Boot complete. Free heap: %lu bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "Press BOOT button (GPIO%d) to simulate hook pickup.",
             CONFIG_PLIP_HOOK_GPIO);
    vTaskDelete(NULL);
}

/* ── Application entry point ─────────────────────────────────────────────── */

void app_main(void)
{
    ESP_LOGI(TAG, "============================================");
    ESP_LOGI(TAG, "  Zacus PLIP Voice Annex");
    ESP_LOGI(TAG, "  Board: AI-Thinker ESP32-A1S (ES8388)");
    ESP_LOGI(TAG, "  IDF:   %s", esp_get_idf_version());
    ESP_LOGI(TAG, "  Free heap: %lu bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "============================================");

    /* ── 1. NVS ── */
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
        ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    /* Delegate all subsequent phases to a task with adequate stack.
     * app_main's default stack (8192) is too tight for I2S + WiFi init
     * when called from the main task (IDF internal stack usage adds up). */
    xTaskCreatePinnedToCore(boot_task, "boot", 12288, NULL, 5, NULL, 0);
}
