#include <tt_app.h>
#include <tt_lvgl.h>
#include <tt_lvgl_toolbar.h>
#include <tt_wifi.h>

#include <tactility/device.h>
#include <tactility/drivers/i2s_controller.h>

#include "websocket.h"
#include <cJSON.h>

#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#define TAG "ReynaBot"

typedef enum {
    STATE_IDLE,
    STATE_CONNECTING,
    STATE_LISTENING,
    STATE_THINKING,
    STATE_SPEAKING,
    STATE_ERROR
} ReynaBotState;

typedef struct {
    AppHandle app;
    bool visible;
    ReynaBotState state;
    char server_url[128];
    char device_id[64];
    char api_key[128];
    
    char last_transcript[256];
    char last_response[512];
    char error_message[128];
    
    // UI components
    lv_obj_t* lbl_status;
    lv_obj_t* lbl_transcript;
    lv_obj_t* lbl_response;
    lv_obj_t* btn_ptt;
    lv_obj_t* btn_ptt_label;
    lv_obj_t* btn_grace;
    lv_obj_t* btn_elias;
    
    bool speaker_selected;
    lv_obj_t* speaker_select_cont;
    lv_obj_t* app_main_cont;
    
    // PTT control
    bool is_pressed;
    bool start_session;
    bool stop_session;
    bool cancel_session;
    
    // FreeRTOS Tasks
    TaskHandle_t worker_task;
    TaskHandle_t rx_task;
    
    // Hardware
    struct Device* i2s_dev;
    
    // WebSocket
    int ws_fd;
    bool ws_connected;
    bool ws_done;
    
    bool ui_update_pending;
} ReynaBotCtx;

/* ─── Forward Declarations ─── */
static void reynabot_task(void* arg);
static void reynabot_rx_task(void* arg);
static void update_ui(ReynaBotCtx* ctx);
static void load_config(ReynaBotCtx* ctx);

/* ─── Helper for URL parsing ─── */
static bool parse_ws_url(const char* url, char* host, int* port, char* path) {
    if (strncmp(url, "ws://", 5) != 0) return false;
    const char* p = url + 5;
    const char* colon = strchr(p, ':');
    const char* slash = strchr(p, '/');
    
    if (slash == NULL) {
        strcpy(path, "/");
    } else {
        strcpy(path, slash);
    }
    
    if (colon != NULL && (slash == NULL || colon < slash)) {
        int host_len = colon - p;
        memcpy(host, p, host_len);
        host[host_len] = '\0';
        *port = atoi(colon + 1);
    } else {
        int host_len = slash ? (slash - p) : strlen(p);
        memcpy(host, p, host_len);
        host[host_len] = '\0';
        *port = 80;
    }
    return true;
}

/* ─── Config Loading/Saving ─── */
static void load_config(ReynaBotCtx* ctx) {
    // Default fallback config
    snprintf(ctx->server_url, sizeof(ctx->server_url), "ws://192.168.68.126:8643/api/esp32/voice/ws");
    snprintf(ctx->device_id, sizeof(ctx->device_id), "reynabot_screen");
    snprintf(ctx->api_key, sizeof(ctx->api_key), "hmek_sXB7921bZ9FXTVKARExqUZ7ttBxtEoURHRU0JCB-gNY");
    
    char path[256];
    size_t path_size = sizeof(path);
    tt_app_get_user_data_child_path(ctx->app, "config.json", path, &path_size);
    
    FILE* file = fopen(path, "r");
    if (file == NULL) {
        // Create user data directory if it doesn't exist
        char dir_path[256];
        size_t dir_size = sizeof(dir_path);
        tt_app_get_user_data_path(ctx->app, dir_path, &dir_size);
        mkdir(dir_path, 0755);
        
        // Write default config file
        file = fopen(path, "w");
        if (file != NULL) {
            fprintf(file, "{\n  \"server_url\": \"ws://192.168.68.126:8643/api/esp32/voice/ws\",\n  \"device_id\": \"reynabot_screen\",\n  \"api_key\": \"mcT1YA1vOr9wXSiHpCYalweEGGZKX-PIfZv2drp8BSg\"\n}\n");
            fclose(file);
        }
        ESP_LOGI(TAG, "Created default config.json at %s", path);
        return;
    }
    
    fseek(file, 0, SEEK_END);
    long size = ftell(file);
    fseek(file, 0, SEEK_SET);
    
    if (size > 0 && size < 4096) {
        char* buf = malloc(size + 1);
        if (buf != NULL) {
            size_t read_bytes = fread(buf, 1, size, file);
            buf[read_bytes] = '\0';
            cJSON* json = cJSON_Parse(buf);
            if (json != NULL) {
                cJSON* url_item = cJSON_GetObjectItem(json, "server_url");
                if (url_item != NULL && url_item->valuestring != NULL) {
                    strncpy(ctx->server_url, url_item->valuestring, sizeof(ctx->server_url) - 1);
                }
                cJSON* dev_item = cJSON_GetObjectItem(json, "device_id");
                if (dev_item != NULL && dev_item->valuestring != NULL) {
                    strncpy(ctx->device_id, dev_item->valuestring, sizeof(ctx->device_id) - 1);
                }
                cJSON* key_item = cJSON_GetObjectItem(json, "api_key");
                if (key_item != NULL && key_item->valuestring != NULL) {
                    strncpy(ctx->api_key, key_item->valuestring, sizeof(ctx->api_key) - 1);
                }
                cJSON_Delete(json);
            }
            free(buf);
        }
    }
    fclose(file);
    ESP_LOGI(TAG, "Loaded config: server=%s device=%s", ctx->server_url, ctx->device_id);
}

/* ─── JSON Message Processing ─── */
static void parse_json_message(ReynaBotCtx* ctx, const char* json_str) {
    cJSON* json = cJSON_Parse(json_str);
    if (json == NULL) return;
    
    cJSON* evt_item = cJSON_GetObjectItem(json, "event");
    if (evt_item != NULL && evt_item->valuestring != NULL) {
        const char* evt = evt_item->valuestring;
        ESP_LOGI(TAG, "WS Event: %s", evt);
        
        if (strcmp(evt, "ready") == 0) {
            ESP_LOGI(TAG, "Server ready");
        } else if (strcmp(evt, "listening") == 0) {
            ctx->state = STATE_LISTENING;
            ctx->ui_update_pending = true;
        } else if (strcmp(evt, "transcript") == 0) {
            cJSON* txt_item = cJSON_GetObjectItem(json, "text");
            if (txt_item != NULL && txt_item->valuestring != NULL) {
                strncpy(ctx->last_transcript, txt_item->valuestring, sizeof(ctx->last_transcript) - 1);
                ctx->ui_update_pending = true;
            }
        } else if (strcmp(evt, "thinking") == 0) {
            ctx->state = STATE_THINKING;
            ctx->ui_update_pending = true;
        } else if (strcmp(evt, "response_text") == 0) {
            cJSON* txt_item = cJSON_GetObjectItem(json, "text");
            if (txt_item != NULL && txt_item->valuestring != NULL) {
                strncpy(ctx->last_response, txt_item->valuestring, sizeof(ctx->last_response) - 1);
                ctx->ui_update_pending = true;
            }
        } else if (strcmp(evt, "audio_start") == 0) {
            ctx->state = STATE_SPEAKING;
            ctx->ui_update_pending = true;
            
            // I2S is already configured globally at session startup
        } else if (strcmp(evt, "audio_end") == 0) {
            ESP_LOGI(TAG, "Audio response ended");
        } else if (strcmp(evt, "done") == 0) {
            ctx->ws_done = true;
        } else if (strcmp(evt, "error") == 0) {
            cJSON* msg_item = cJSON_GetObjectItem(json, "message");
            if (msg_item != NULL && msg_item->valuestring != NULL) {
                strncpy(ctx->error_message, msg_item->valuestring, sizeof(ctx->error_message) - 1);
            } else {
                snprintf(ctx->error_message, sizeof(ctx->error_message), "Unknown server error");
            }
            ctx->state = STATE_ERROR;
            ctx->ui_update_pending = true;
        }
    }
    cJSON_Delete(json);
}

/* ─── WebSocket RX (Receive) Task ─── */
static void reynabot_rx_task(void* arg) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)arg;
    uint8_t* rx_buf = malloc(8192);
    if (rx_buf == NULL) {
        ESP_LOGE(TAG, "Failed to allocate RX buffer");
        ctx->ws_done = true;
        ctx->rx_task = NULL;
        vTaskDelete(NULL);
        return;
    }
    int opcode;
    
    ESP_LOGI(TAG, "WS Receive task started");
    
    while (ctx->ws_fd >= 0) {
        int r = ws_recv(ctx->ws_fd, &opcode, rx_buf, 8191);
        if (r < 0) {
            if (r == -1) {
                ESP_LOGI(TAG, "WS connection closed or read error, errno=%d", errno);
            } else {
                ESP_LOGE(TAG, "WS rx buffer overflow");
            }
            ctx->ws_done = true;
            break;
        }
        
        if (opcode == 0x01) { // Text frame (JSON)
            rx_buf[r] = '\0';
            parse_json_message(ctx, (char*)rx_buf);
        } else if (opcode == 0x02) { // Binary frame (Audio data)
            if (ctx->state == STATE_SPEAKING && ctx->i2s_dev != NULL) {
                const uint8_t* payload_ptr = rx_buf;
                size_t payload_len = r;
                
                // Skip WAV header if present in the first chunk
                if (payload_len > 44 && memcmp(payload_ptr, "RIFF", 4) == 0) {
                    payload_ptr += 44;
                    payload_len -= 44;
                }
                
                size_t written = 0;
                device_lock(ctx->i2s_dev);
                i2s_controller_write(ctx->i2s_dev, payload_ptr, payload_len, &written, pdMS_TO_TICKS(100));
                device_unlock(ctx->i2s_dev);
            }
        } else if (opcode == 0x09) { // PING frame
            ESP_LOGI(TAG, "WS PING received, sending PONG");
            ws_send_pong(ctx->ws_fd, rx_buf, r);
        }
    }
    
    free(rx_buf);
    ctx->rx_task = NULL;
    vTaskDelete(NULL);
}

/* ─── UI Speaker Selection Event Callback ─── */
static void speaker_event_cb(lv_event_t* e) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)lv_event_get_user_data(e);
    lv_obj_t* target = lv_event_get_target(e);
    lv_event_code_t code = lv_event_get_code(e);
    
    if (code == LV_EVENT_CLICKED && !ctx->speaker_selected) {
        if (target == ctx->btn_grace) {
            strcpy(ctx->device_id, "grace-esp32");
        } else if (target == ctx->btn_elias) {
            strcpy(ctx->device_id, "elias-esp32");
        }
        
        ctx->speaker_selected = true;
        
        // Hide selection screen, show main screen
        lv_obj_add_flag(ctx->speaker_select_cont, LV_OBJ_FLAG_HIDDEN);
        lv_obj_remove_flag(ctx->app_main_cont, LV_OBJ_FLAG_HIDDEN);
        
        // Trigger UI update to refresh status and PTT button
        ctx->ui_update_pending = true;
    }
}

/* ─── UI Press-to-Talk Event Callback ─── */
static void ptt_event_cb(lv_event_t* e) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)lv_event_get_user_data(e);
    lv_event_code_t code = lv_event_get_code(e);
    
    if (code == LV_EVENT_PRESSED) {
        ctx->is_pressed = true;
        if (ctx->state == STATE_IDLE || ctx->state == ERROR_NONE) {
            ctx->start_session = true;
        }
    } else if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
        ctx->is_pressed = false;
        if (ctx->state == STATE_LISTENING) {
            ctx->stop_session = true;
        } else if (ctx->state == STATE_CONNECTING) {
            ctx->cancel_session = true;
        }
    }
}

/* ─── Core UI Update Function ─── */
static void update_ui(ReynaBotCtx* ctx) {
    if (!ctx->visible) return;
    
    if (tt_lvgl_lock(pdMS_TO_TICKS(500))) {
        if (!ctx->visible) {
            tt_lvgl_unlock();
            return;
        }
        
        if (!ctx->speaker_selected) {
            lv_label_set_text(ctx->lbl_status, "Choose Speaker");
            lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0x9E9E9E), LV_PART_MAIN);
            tt_lvgl_unlock();
            return;
        }
        
        switch (ctx->state) {
            case STATE_IDLE:
                lv_label_set_text(ctx->lbl_status, "Ready");
                lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0x9E9E9E), LV_PART_MAIN);
                lv_label_set_text(ctx->btn_ptt_label, LV_SYMBOL_AUDIO " Hold to Talk");
                lv_obj_set_style_bg_color(ctx->btn_ptt, lv_color_hex(0x6200EE), LV_PART_MAIN);
                lv_obj_remove_flag(ctx->btn_ptt, LV_OBJ_FLAG_HIDDEN);
                break;
            case STATE_CONNECTING:
                lv_label_set_text(ctx->lbl_status, "Connecting...");
                lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0xFFC107), LV_PART_MAIN);
                lv_label_set_text(ctx->btn_ptt_label, "Connecting...");
                lv_obj_set_style_bg_color(ctx->btn_ptt, lv_color_hex(0x757575), LV_PART_MAIN);
                lv_obj_remove_flag(ctx->btn_ptt, LV_OBJ_FLAG_HIDDEN);
                break;
            case STATE_LISTENING:
                lv_label_set_text(ctx->lbl_status, "Listening...");
                lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0x00E676), LV_PART_MAIN);
                lv_label_set_text(ctx->btn_ptt_label, "Release to Stop");
                lv_obj_set_style_bg_color(ctx->btn_ptt, lv_color_hex(0xD50000), LV_PART_MAIN);
                lv_obj_remove_flag(ctx->btn_ptt, LV_OBJ_FLAG_HIDDEN);
                break;
            case STATE_THINKING:
                lv_label_set_text(ctx->lbl_status, "Thinking...");
                lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0x00B0FF), LV_PART_MAIN);
                lv_obj_add_flag(ctx->btn_ptt, LV_OBJ_FLAG_HIDDEN);
                break;
            case STATE_SPEAKING:
                lv_label_set_text(ctx->lbl_status, "Speaking...");
                lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0xAA00FF), LV_PART_MAIN);
                lv_obj_add_flag(ctx->btn_ptt, LV_OBJ_FLAG_HIDDEN);
                break;
            case STATE_ERROR:
                lv_label_set_text(ctx->lbl_status, "Error");
                lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0xFF1744), LV_PART_MAIN);
                lv_label_set_text(ctx->btn_ptt_label, LV_SYMBOL_REFRESH " Try Again");
                lv_obj_set_style_bg_color(ctx->btn_ptt, lv_color_hex(0xFF1744), LV_PART_MAIN);
                lv_obj_remove_flag(ctx->btn_ptt, LV_OBJ_FLAG_HIDDEN);
                
                // Show the full error message in the bot's response bubble
                if (ctx->error_message[0] != '\0') {
                    snprintf(ctx->last_response, sizeof(ctx->last_response), "Error: %s", ctx->error_message);
                }
                break;
        }
        
        lv_label_set_text(ctx->lbl_transcript, ctx->last_transcript[0] != '\0' ? ctx->last_transcript : "(Your question will appear here)");
        lv_label_set_text(ctx->lbl_response, ctx->last_response[0] != '\0' ? ctx->last_response : "(Answer will appear here)");
        
        tt_lvgl_unlock();
    }
}

/* ─── Streaming & Main Worker Task ─── */
static void reynabot_task(void* arg) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)arg;
    char host[64];
    char path[128];
    int port = 80;
    
    ctx->ws_fd = -1;
    
    
    while (ctx->visible) {
        if (ctx->start_session) {
            ctx->start_session = false;
            ctx->stop_session = false;
            ctx->cancel_session = false;
            
            ctx->state = STATE_CONNECTING;
            ctx->last_transcript[0] = '\0';
            ctx->last_response[0] = '\0';
            ctx->error_message[0] = '\0';
            ctx->ws_done = false;
            update_ui(ctx);
            
            if (!parse_ws_url(ctx->server_url, host, &port, path)) {
                ctx->state = STATE_ERROR;
                snprintf(ctx->error_message, sizeof(ctx->error_message), "Invalid Server URL");
                update_ui(ctx);
                continue;
            }
            
            ESP_LOGI(TAG, "Connecting: host=%s port=%d path=%s", host, port, path);
            int fd = ws_connect(host, port, path, ctx->device_id, ctx->api_key);
            if (fd < 0) {
                ctx->state = STATE_ERROR;
                snprintf(ctx->error_message, sizeof(ctx->error_message), "Connect failed");
                update_ui(ctx);
                continue;
            }
            
            ctx->ws_fd = fd;
            ctx->ws_connected = true;
            
            // Configure I2S on demand for this session (16 kHz, 16-bit, mono)
            struct I2sConfig session_cfg = {
                .communication_format = I2S_FORMAT_STAND_I2S,
                .sample_rate = 16000,
                .bits_per_sample = 16,
                .channel_left = 0,
                .channel_right = I2S_CHANNEL_NONE
            };
            if (ctx->i2s_dev != NULL) {
                device_lock(ctx->i2s_dev);
                i2s_controller_set_config(ctx->i2s_dev, &session_cfg);
                device_unlock(ctx->i2s_dev);
            }
            
            // Spawn background RX task to read and parse events
            xTaskCreate(reynabot_rx_task, "reynabot_rx", 4096, ctx, 6, &ctx->rx_task);
            
            // Send start event handshake
            char start_json[256];
            snprintf(start_json, sizeof(start_json),
                     "{\"event\":\"start\",\"device_id\":\"%s\",\"sample_rate\":16000,\"channels\":1,\"sample_width\":2,\"format\":\"pcm_s16le\"}",
                     ctx->device_id);
            if (ws_send(ctx->ws_fd, (const uint8_t*)start_json, strlen(start_json), false) < 0) {
                ctx->state = STATE_ERROR;
                snprintf(ctx->error_message, sizeof(ctx->error_message), "Handshake send failed");
                ws_close(ctx->ws_fd);
                ctx->ws_fd = -1;
                ctx->ws_connected = false;
                update_ui(ctx);
                continue;
            }
            
            // Wait for server to switch us to LISTENING
            int timeout_ms = 4000;
            while (ctx->state != STATE_LISTENING && timeout_ms > 0 && !ctx->cancel_session && !ctx->ws_done) {
                vTaskDelay(pdMS_TO_TICKS(50));
                timeout_ms -= 50;
            }
            
            if (ctx->state != STATE_LISTENING) {
                if (ctx->state != STATE_ERROR) {
                    ctx->state = STATE_ERROR;
                    snprintf(ctx->error_message, sizeof(ctx->error_message), "Handshake timeout");
                }
                ws_close(ctx->ws_fd);
                ctx->ws_fd = -1;
                ctx->ws_connected = false;
                update_ui(ctx);
                continue;
            }
            
            // I2S is already configured globally at session startup
            
            uint8_t* buffer = malloc(1024);
            if (buffer == NULL) {
                ESP_LOGE(TAG, "Failed to allocate record buffer");
                ctx->state = STATE_ERROR;
                snprintf(ctx->error_message, sizeof(ctx->error_message), "Out of memory");
                ws_close(ctx->ws_fd);
                ctx->ws_fd = -1;
                ctx->ws_connected = false;
                update_ui(ctx);
                continue;
            }
            size_t total_sent_bytes = 0;
            
            // Stream audio loop while PTT is held
            while (ctx->is_pressed && !ctx->stop_session && !ctx->cancel_session && !ctx->ws_done && total_sent_bytes < 320000) {
                size_t bytes_read = 0;
                error_t r = i2s_controller_read(ctx->i2s_dev, buffer, 1024, &bytes_read, pdMS_TO_TICKS(100));
                if (r == ERROR_NONE && bytes_read > 0) {
                    if (ws_send(ctx->ws_fd, buffer, bytes_read, true) < 0) {
                        ESP_LOGE(TAG, "Audio stream send failed");
                        break;
                    }
                    total_sent_bytes += bytes_read;
                }
            }
            
            free(buffer);
            
            if (ctx->cancel_session || total_sent_bytes < 3200) {
                ESP_LOGI(TAG, "Cancelling audio session");
                const char* cancel_json = "{\"event\":\"cancel\"}";
                ws_send(ctx->ws_fd, (const uint8_t*)cancel_json, strlen(cancel_json), false);
                ctx->state = STATE_IDLE;
            } else {
                const char* stop_json = "{\"event\":\"stop\"}";
                ws_send(ctx->ws_fd, (const uint8_t*)stop_json, strlen(stop_json), false);
                ctx->state = STATE_THINKING;
                update_ui(ctx);
                
                // Wait for response to finish (increase to 90s to match socket timeout)
                int wait_timeout_ms = 90000;
                while (!ctx->ws_done && ctx->state != STATE_ERROR && wait_timeout_ms > 0) {
                    vTaskDelay(pdMS_TO_TICKS(100));
                    wait_timeout_ms -= 100;
                    if (ctx->ui_update_pending) {
                        ctx->ui_update_pending = false;
                        update_ui(ctx);
                    }
                }
                
                if (!ctx->ws_done && ctx->state != STATE_ERROR) {
                    ctx->state = STATE_ERROR;
                    snprintf(ctx->error_message, sizeof(ctx->error_message), "Response timeout");
                }
            }
            
            // Clean up socket
            int fd_to_close = ctx->ws_fd;
            ctx->ws_fd = -1;
            ctx->ws_connected = false;
            ws_close(fd_to_close);
            
            // Reset I2S controller to release DMA and stop white noise
            if (ctx->i2s_dev != NULL) {
                device_lock(ctx->i2s_dev);
                i2s_controller_reset(ctx->i2s_dev);
                device_unlock(ctx->i2s_dev);
            }
            
            // Wait for receive task to exit
            int rx_timeout = 100;
            while (ctx->rx_task != NULL && rx_timeout > 0) {
                vTaskDelay(pdMS_TO_TICKS(10));
                rx_timeout--;
            }
            
            if (ctx->state != STATE_ERROR) {
                ctx->state = STATE_IDLE;
            }
            update_ui(ctx);
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
        if (ctx->ui_update_pending) {
            ctx->ui_update_pending = false;
            update_ui(ctx);
        }
    }
    
    ctx->worker_task = NULL;
    vTaskDelete(NULL);
}

/* ─── App Lifecycle ─── */

static void* create_data(void) {
    ReynaBotCtx* ctx = calloc(1, sizeof(ReynaBotCtx));
    if (ctx != NULL) {
        ctx->state = STATE_IDLE;
        ctx->ws_fd = -1;
    }
    return ctx;
}

static void destroy_data(void* data) {
    free(data);
}

static void on_create(AppHandle app, void* data) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)data;
    ctx->app = app;
}

static void on_destroy(AppHandle app, void* data) {
    // No-op
}

static void on_show(AppHandle app, void* data, lv_obj_t* parent) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)data;
    ctx->visible = true;
    
    load_config(ctx);
    
    // Find I2S controller
    ctx->i2s_dev = device_find_by_name("i2s0");
    if (ctx->i2s_dev == NULL) {
        ESP_LOGE(TAG, "I2S controller 'i2s0' not found!");
    }
    
    // Style the parent screen
    lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(parent, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_all(parent, 10, LV_PART_MAIN);
    lv_obj_set_style_pad_row(parent, 12, LV_PART_MAIN);
    lv_obj_set_style_bg_color(parent, lv_color_hex(0x0C0B12), LV_PART_MAIN); // Rich dark background
    
    // Add App Toolbar
    lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app);
    lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
    
    // Status Label inside toolbar (replaces the static "ReynaBot Voice" label)
    ctx->lbl_status = lv_label_create(toolbar);
    lv_label_set_text(ctx->lbl_status, "Ready");
    lv_obj_add_flag(ctx->lbl_status, LV_OBJ_FLAG_FLOATING);
    lv_obj_align(ctx->lbl_status, LV_ALIGN_RIGHT_MID, -10, 0);
    lv_obj_set_style_text_color(ctx->lbl_status, lv_color_hex(0x9E9E9E), LV_PART_MAIN);

    // Initial state
    ctx->speaker_selected = false;

    // Default to Grace if device_id is not already configured to elias-esp32
    if (strcmp(ctx->device_id, "elias-esp32") != 0) {
        strcpy(ctx->device_id, "grace-esp32");
    }

    // 1. Speaker Selection Container (Startup Screen)
    ctx->speaker_select_cont = lv_obj_create(parent);
    lv_obj_set_width(ctx->speaker_select_cont, lv_pct(95));
    lv_obj_set_flex_grow(ctx->speaker_select_cont, 1);
    lv_obj_set_flex_flow(ctx->speaker_select_cont, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(ctx->speaker_select_cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_all(ctx->speaker_select_cont, 0, LV_PART_MAIN);
    lv_obj_set_style_bg_opa(ctx->speaker_select_cont, 0, LV_PART_MAIN);
    lv_obj_set_style_border_width(ctx->speaker_select_cont, 0, LV_PART_MAIN);
    lv_obj_set_style_pad_row(ctx->speaker_select_cont, 16, LV_PART_MAIN);

    // Title Prompt
    lv_obj_t* prompt_lbl = lv_label_create(ctx->speaker_select_cont);
    lv_label_set_text(prompt_lbl, "Who is speaking?");
    lv_obj_set_style_text_color(prompt_lbl, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
    
    // Row of two selection buttons
    lv_obj_t* speaker_row = lv_obj_create(ctx->speaker_select_cont);
    lv_obj_set_size(speaker_row, lv_pct(100), 140);
    lv_obj_set_flex_flow(speaker_row, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(speaker_row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_all(speaker_row, 0, LV_PART_MAIN);
    lv_obj_set_style_bg_opa(speaker_row, 0, LV_PART_MAIN);
    lv_obj_set_style_border_width(speaker_row, 0, LV_PART_MAIN);

    // Grace Button (Girl)
    ctx->btn_grace = lv_btn_create(speaker_row);
    lv_obj_set_size(ctx->btn_grace, lv_pct(46), 130);
    lv_obj_set_flex_flow(ctx->btn_grace, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(ctx->btn_grace, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_all(ctx->btn_grace, 8, LV_PART_MAIN);
    lv_obj_set_style_pad_row(ctx->btn_grace, 8, LV_PART_MAIN);
    lv_obj_set_style_radius(ctx->btn_grace, 12, LV_PART_MAIN);
    lv_obj_set_style_bg_color(ctx->btn_grace, lv_color_hex(0x1C1B22), LV_PART_MAIN);
    lv_obj_set_style_border_width(ctx->btn_grace, 1, LV_PART_MAIN);
    lv_obj_set_style_border_color(ctx->btn_grace, lv_color_hex(0x2D2B36), LV_PART_MAIN);
    lv_obj_add_event_cb(ctx->btn_grace, speaker_event_cb, LV_EVENT_CLICKED, ctx);

    lv_obj_t* icon_grace = lv_image_create(ctx->btn_grace);
    lv_obj_set_size(icon_grace, 80, 80);
    char path_grace[256] = "A:";
    size_t sz_grace = sizeof(path_grace) - 2;
    tt_app_get_assets_child_path(ctx->app, "girl.png", path_grace + 2, &sz_grace);
    lv_image_set_src(icon_grace, path_grace);
    
    lv_obj_t* lbl_grace = lv_label_create(ctx->btn_grace);
    lv_label_set_text(lbl_grace, "Grace");
    lv_obj_set_style_text_color(lbl_grace, lv_color_hex(0xFFFFFF), LV_PART_MAIN);

    // Elias Button (Boy)
    ctx->btn_elias = lv_btn_create(speaker_row);
    lv_obj_set_size(ctx->btn_elias, lv_pct(46), 130);
    lv_obj_set_flex_flow(ctx->btn_elias, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(ctx->btn_elias, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_all(ctx->btn_elias, 8, LV_PART_MAIN);
    lv_obj_set_style_pad_row(ctx->btn_elias, 8, LV_PART_MAIN);
    lv_obj_set_style_radius(ctx->btn_elias, 12, LV_PART_MAIN);
    lv_obj_set_style_bg_color(ctx->btn_elias, lv_color_hex(0x1C1B22), LV_PART_MAIN);
    lv_obj_set_style_border_width(ctx->btn_elias, 1, LV_PART_MAIN);
    lv_obj_set_style_border_color(ctx->btn_elias, lv_color_hex(0x2D2B36), LV_PART_MAIN);
    lv_obj_add_event_cb(ctx->btn_elias, speaker_event_cb, LV_EVENT_CLICKED, ctx);

    lv_obj_t* icon_elias = lv_image_create(ctx->btn_elias);
    lv_obj_set_size(icon_elias, 80, 80);
    char path_elias[256] = "A:";
    size_t sz_elias = sizeof(path_elias) - 2;
    tt_app_get_assets_child_path(ctx->app, "boy.png", path_elias + 2, &sz_elias);
    lv_image_set_src(icon_elias, path_elias);
    
    lv_obj_t* lbl_elias = lv_label_create(ctx->btn_elias);
    lv_label_set_text(lbl_elias, "Elias");
    lv_obj_set_style_text_color(lbl_elias, lv_color_hex(0xFFFFFF), LV_PART_MAIN);

    // 2. Main App Container (Usual App Screen)
    ctx->app_main_cont = lv_obj_create(parent);
    lv_obj_set_width(ctx->app_main_cont, lv_pct(95));
    lv_obj_set_flex_grow(ctx->app_main_cont, 1);
    lv_obj_set_flex_flow(ctx->app_main_cont, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(ctx->app_main_cont, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_all(ctx->app_main_cont, 0, LV_PART_MAIN);
    lv_obj_set_style_pad_row(ctx->app_main_cont, 12, LV_PART_MAIN);
    lv_obj_set_style_bg_opa(ctx->app_main_cont, 0, LV_PART_MAIN);
    lv_obj_set_style_border_width(ctx->app_main_cont, 0, LV_PART_MAIN);
    lv_obj_add_flag(ctx->app_main_cont, LV_OBJ_FLAG_HIDDEN); // Hidden by default

    // Conversation bubble container
    lv_obj_t* conv_card = lv_obj_create(ctx->app_main_cont);
    lv_obj_set_width(conv_card, lv_pct(100));
    lv_obj_set_flex_grow(conv_card, 1);
    lv_obj_set_flex_flow(conv_card, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(conv_card, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
    lv_obj_set_style_pad_all(conv_card, 12, LV_PART_MAIN);
    lv_obj_set_style_pad_row(conv_card, 10, LV_PART_MAIN);
    lv_obj_set_style_bg_color(conv_card, lv_color_hex(0x13121A), LV_PART_MAIN);
    lv_obj_set_style_border_color(conv_card, lv_color_hex(0x23212C), LV_PART_MAIN);
    lv_obj_set_style_border_width(conv_card, 1, LV_PART_MAIN);
    lv_obj_set_style_radius(conv_card, 12, LV_PART_MAIN);
    
    // User bubble
    lv_obj_t* user_label_title = lv_label_create(conv_card);
    lv_label_set_text(user_label_title, "👤 You:");
    lv_obj_set_style_text_color(user_label_title, lv_color_hex(0x00E676), LV_PART_MAIN);
    
    ctx->lbl_transcript = lv_label_create(conv_card);
    lv_label_set_text(ctx->lbl_transcript, "(Your question will appear here)");
    lv_label_set_long_mode(ctx->lbl_transcript, LV_LABEL_LONG_WRAP);
    lv_obj_set_width(ctx->lbl_transcript, lv_pct(100));
    lv_obj_set_style_text_color(ctx->lbl_transcript, lv_color_hex(0xE0E0E0), LV_PART_MAIN);
    
    // Separator line
    lv_obj_t* sep = lv_line_create(conv_card);
    static lv_point_precise_t line_points[] = { {0, 0}, {300, 0} };
    lv_line_set_points(sep, line_points, 2);
    lv_obj_set_style_line_color(sep, lv_color_hex(0x2D2B36), LV_PART_MAIN);
    lv_obj_set_style_line_width(sep, 1, LV_PART_MAIN);
    lv_obj_set_width(sep, lv_pct(100));
    
    // Assistant bubble
    lv_obj_t* bot_label_title = lv_label_create(conv_card);
    lv_label_set_text(bot_label_title, "🤖 ReynaBot:");
    lv_obj_set_style_text_color(bot_label_title, lv_color_hex(0xAA00FF), LV_PART_MAIN);
    
    ctx->lbl_response = lv_label_create(conv_card);
    lv_label_set_text(ctx->lbl_response, "(Answer will appear here)");
    lv_label_set_long_mode(ctx->lbl_response, LV_LABEL_LONG_WRAP);
    lv_obj_set_width(ctx->lbl_response, lv_pct(100));
    lv_obj_set_style_text_color(ctx->lbl_response, lv_color_hex(0xE0E0E0), LV_PART_MAIN);
    
    // Push-to-Talk Button
    ctx->btn_ptt = lv_btn_create(ctx->app_main_cont);
    lv_obj_set_size(ctx->btn_ptt, 180, 50);
    lv_obj_set_style_radius(ctx->btn_ptt, 25, LV_PART_MAIN);
    lv_obj_set_style_bg_color(ctx->btn_ptt, lv_color_hex(0x6200EE), LV_PART_MAIN);
    
    ctx->btn_ptt_label = lv_label_create(ctx->btn_ptt);
    lv_label_set_text(ctx->btn_ptt_label, LV_SYMBOL_AUDIO " Hold to Talk");
    lv_obj_center(ctx->btn_ptt_label);
    
    lv_obj_add_event_cb(ctx->btn_ptt, ptt_event_cb, LV_EVENT_ALL, ctx);
    
    // Launch main worker task
    xTaskCreate(reynabot_task, "reynabot_worker", 6144, ctx, 5, &ctx->worker_task);
}

static void on_hide(AppHandle app, void* data) {
    ReynaBotCtx* ctx = (ReynaBotCtx*)data;
    if (ctx == NULL) return;
    
    ESP_LOGI(TAG, "on_hide: cleaning up");
    ctx->visible = false;
    
    if (ctx->ws_fd >= 0) {
        int fd_to_close = ctx->ws_fd;
        ctx->ws_fd = -1;
        ws_close(fd_to_close);
    }
    
    // Reset I2S controller to stop DMA and looping noise
    if (ctx->i2s_dev != NULL) {
        device_lock(ctx->i2s_dev);
        i2s_controller_reset(ctx->i2s_dev);
        device_unlock(ctx->i2s_dev);
    }
    
    // Wait briefly for tasks to exit
    int timeout = 100;
    while ((ctx->worker_task != NULL || ctx->rx_task != NULL) && timeout > 0) {
        vTaskDelay(pdMS_TO_TICKS(10));
        timeout--;
    }
}

int main(int argc, char* argv[]) {
    (void)argc;
    (void)argv;
    tt_app_register((AppRegistration) {
        .createData = create_data,
        .destroyData = destroy_data,
        .onCreate = on_create,
        .onDestroy = on_destroy,
        .onShow = on_show,
        .onHide = on_hide
    });
    return 0;
}
