/**
 * DreamStack Thin Client — Waveshare ESP32-P4 10.1" Panel
 *
 * Dual transport firmware:
 *   1. ESP-NOW + Panel IR (primary) — sub-1ms signal delivery, LVGL native rendering
 *   2. WebSocket + pixel streaming (fallback) — for non-DreamStack content
 *
 * Build with -DDS_USE_ESPNOW=1 for ESP-NOW mode (default)
 * Build with -DDS_USE_ESPNOW=0 for WebSocket-only pixel mode
 */

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_lcd_panel_ops.h"

#include "ds_codec.h"
#include "ds_protocol.h"

// ─── Transport selection ───
#ifndef DS_USE_ESPNOW
#define DS_USE_ESPNOW  1
#endif

#if DS_USE_ESPNOW
#include "ds_espnow.h"
#include "ds_runtime.h"
#endif

static const char *TAG = "ds-panel";

// ─── Configuration ───
#define PANEL_WIDTH    800
#define PANEL_HEIGHT   1280
#define PIXEL_BYTES    2        // RGB565
#define FB_SIZE        (PANEL_WIDTH * PANEL_HEIGHT * PIXEL_BYTES)

#define WIFI_SSID      CONFIG_WIFI_SSID
#define WIFI_PASS      CONFIG_WIFI_PASS

#if !DS_USE_ESPNOW
#define RELAY_URL      CONFIG_RELAY_URL
#include "esp_websocket_client.h"
#endif

// ─── Framebuffers (in PSRAM, for pixel mode) ───
static uint8_t *framebuffer;
static uint8_t *scratch_buf;

// ─── Display handle ───
static esp_lcd_panel_handle_t panel_handle = NULL;

// ─── Touch state ───
static uint16_t input_seq = 0;

#if DS_USE_ESPNOW
// ═══════════════════════════════════════════════════════
// ESP-NOW + Panel IR Mode
// ═══════════════════════════════════════════════════════

static void on_signal(uint16_t signal_id, int32_t value) {
    // Feed signal updates to the Panel IR runtime
    ds_signal_update(signal_id, value);
    ESP_LOGD(TAG, "Signal %d = %d", signal_id, (int)value);
}

static void on_ir_push(const char *ir_json, size_t length) {
    // Build LVGL UI from Panel IR JSON
    ESP_LOGI(TAG, "IR push received (%zu bytes), building UI...", length);
    esp_err_t ret = ds_ui_build(ir_json, length);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "UI built: %d signals", ds_signal_count());
    } else {
        ESP_LOGE(TAG, "Failed to build UI from IR");
    }
}

static void on_action(uint8_t node_id, uint8_t action_type) {
    // Forward widget actions to hub via ESP-NOW
    ds_espnow_send_action(node_id, action_type);
}

static void espnow_init_and_run(void) {
    // Initialize Panel IR runtime (LVGL must be ready)
    // TODO: replace NULL with lv_scr_act() once LVGL is initialized
    ds_runtime_init(NULL, on_action);

    // Initialize ESP-NOW transport
    ds_espnow_config_t config = {
        .hub_mac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},  // broadcast
        .channel = DS_ESPNOW_CHANNEL,
        .on_signal = on_signal,
        .on_ir_push = on_ir_push,
    };
    esp_err_t ret = ds_espnow_init(&config);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "ESP-NOW init failed");
        return;
    }

    // Send periodic pings so hub discovers us
    while (1) {
        ds_espnow_send_ping();
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

#else
// ═══════════════════════════════════════════════════════
// WebSocket Pixel Streaming Mode (Legacy)
// ═══════════════════════════════════════════════════════

static void ws_event_handler(void *arg, esp_event_base_t base,
                             int32_t event_id, void *event_data) {
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;

    switch (event_id) {
    case WEBSOCKET_EVENT_CONNECTED:
        ESP_LOGI(TAG, "WebSocket connected to relay");
        break;

    case WEBSOCKET_EVENT_DATA:
        if (data->data_len < DS_HEADER_SIZE) break;

        ds_header_t hdr;
        if (ds_parse_header((const uint8_t *)data->data_ptr, &hdr) != 0) break;

        const uint8_t *payload = (const uint8_t *)data->data_ptr + DS_HEADER_SIZE;
        size_t payload_len = data->data_len - DS_HEADER_SIZE;

        switch (hdr.frame_type) {
        case DS_FRAME_PIXELS:
            if (payload_len == FB_SIZE) {
                memcpy(framebuffer, payload, FB_SIZE);
                esp_lcd_panel_draw_bitmap(panel_handle,
                    0, 0, PANEL_WIDTH, PANEL_HEIGHT, framebuffer);
                ESP_LOGI(TAG, "Keyframe received (%zu bytes)", payload_len);
            }
            break;

        case DS_FRAME_DELTA:
            if (ds_apply_delta_rle(framebuffer, FB_SIZE,
                                   payload, payload_len, scratch_buf) == 0) {
                esp_lcd_panel_draw_bitmap(panel_handle,
                    0, 0, PANEL_WIDTH, PANEL_HEIGHT, framebuffer);
            } else {
                ESP_LOGW(TAG, "Delta decode failed (len=%zu)", payload_len);
            }
            break;

        case DS_FRAME_PING:
            break;

        case DS_FRAME_END:
            ESP_LOGI(TAG, "Stream ended");
            break;
        }
        break;

    case WEBSOCKET_EVENT_DISCONNECTED:
        ESP_LOGW(TAG, "WebSocket disconnected, reconnecting...");
        break;

    case WEBSOCKET_EVENT_ERROR:
        ESP_LOGE(TAG, "WebSocket error");
        break;
    }
}

static void send_touch_event(esp_websocket_client_handle_t ws,
                             uint8_t id, uint16_t x, uint16_t y, uint8_t phase) {
    uint8_t buf[DS_HEADER_SIZE + sizeof(ds_touch_event_t)];
    ds_touch_event_t touch = { .id = id, .x = x, .y = y, .phase = phase };
    size_t len = ds_encode_touch(buf, input_seq++,
                                 (uint32_t)(esp_timer_get_time() / 1000),
                                 &touch);
    esp_websocket_client_send_bin(ws, (const char *)buf, len, portMAX_DELAY);
}

#endif // DS_USE_ESPNOW

// ─── Touch polling task ───
static void touch_task(void *arg) {
    while (1) {
        // TODO: Read from GT9271 touch controller via I2C
        // gt9271_touch_data_t td;
        // if (gt9271_read(&td) == ESP_OK && td.num_points > 0) {
        //     #if DS_USE_ESPNOW
        //         ds_espnow_send_touch(0, 0, td.points[0].x, td.points[0].y);
        //     #else
        //         send_touch_event(ws, ...);
        //     #endif
        // }
        vTaskDelay(pdMS_TO_TICKS(10));  // 100Hz touch polling
    }
}

// ─── Display initialization ───
static esp_err_t display_init(void) {
    // TODO: Initialize MIPI DSI display using Waveshare component
    ESP_LOGI(TAG, "Display initialized (%dx%d RGB565)", PANEL_WIDTH, PANEL_HEIGHT);
    return ESP_OK;
}

// ─── WiFi initialization ───
static void wifi_init(void) {
    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    wifi_config_t wifi_cfg = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASS,
        },
    };
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
    esp_wifi_start();

#if DS_USE_ESPNOW
    // For ESP-NOW: don't need to connect to AP, just start WiFi
    ESP_LOGI(TAG, "WiFi started (ESP-NOW mode, no AP connection needed)");
#else
    esp_wifi_connect();
    ESP_LOGI(TAG, "WiFi connecting to %s...", WIFI_SSID);
#endif
}

// ─── Main ───
void app_main(void) {
#if DS_USE_ESPNOW
    ESP_LOGI(TAG, "DreamStack Panel v0.2 (ESP-NOW + Panel IR)");
#else
    ESP_LOGI(TAG, "DreamStack Panel v0.2 (WebSocket + Pixel)");
#endif
    ESP_LOGI(TAG, "Display: %dx%d @ %d bpp = %d bytes",
             PANEL_WIDTH, PANEL_HEIGHT, PIXEL_BYTES * 8, FB_SIZE);

    nvs_flash_init();

    // Allocate framebuffers in PSRAM (needed for pixel mode, optional for IR mode)
    framebuffer = heap_caps_calloc(1, FB_SIZE, MALLOC_CAP_SPIRAM);
    scratch_buf = heap_caps_calloc(1, FB_SIZE, MALLOC_CAP_SPIRAM);
    if (!framebuffer || !scratch_buf) {
        ESP_LOGE(TAG, "Failed to allocate framebuffers in PSRAM (%d bytes each)", FB_SIZE);
        return;
    }

    display_init();
    wifi_init();
    vTaskDelay(pdMS_TO_TICKS(2000));

#if DS_USE_ESPNOW
    // ESP-NOW mode: init transport + runtime, wait for IR push
    espnow_init_and_run();
#else
    // WebSocket mode: connect to relay, receive pixel frames
    esp_websocket_client_config_t ws_cfg = {
        .uri = RELAY_URL,
        .buffer_size = 64 * 1024,
    };
    esp_websocket_client_handle_t ws = esp_websocket_client_init(&ws_cfg);
    esp_websocket_register_events(ws, WEBSOCKET_EVENT_ANY, ws_event_handler, NULL);
    esp_websocket_client_start(ws);
    ESP_LOGI(TAG, "WebSocket connecting to %s...", RELAY_URL);

    xTaskCreate(touch_task, "touch", 4096, ws, 5, NULL);
#endif

    ESP_LOGI(TAG, "Panel running. Waiting for frames...");
}

