#include "ui.h"

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

#include "display.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ili9341.h"
#include "lvgl.h"
#include "mnemonic.h"
#include "secure_mem.h"
#include "touch.h"

#define UI_TICK_MS 2
#define UI_DRAW_LINES 20
#define UI_MNEMONIC_WORDS 12
#define UI_BIP39_WORD_MAX 9
#define UI_SUGGESTION_STORE_MAX 64
#define UI_SUGGESTION_VISIBLE_MAX 32

static const char *TAG = "ui_lvgl";

static bool s_lvgl_inited = false;
static lv_disp_draw_buf_t s_draw_buf;
static lv_color_t s_lv_draw_buf[DISPLAY_WIDTH * UI_DRAW_LINES];
static uint8_t s_flush_bytes[DISPLAY_WIDTH * UI_DRAW_LINES * 2];
static esp_timer_handle_t s_lv_tick_timer;

static bool s_done = false;
static bool s_have_mnemonic = false;
static char s_selected_mnemonic[256];
static lv_obj_t *s_status_label = NULL;

static char s_committed_words[UI_MNEMONIC_WORDS][UI_BIP39_WORD_MAX];
static size_t s_word_count = 0;
static char s_prefix[UI_BIP39_WORD_MAX];
static size_t s_prefix_len = 0;
static bool s_checksum_override = false;

static lv_obj_t *s_entry_words_label = NULL;
static lv_obj_t *s_entry_prefix_label = NULL;
static lv_obj_t *s_suggestion_row = NULL;
static lv_obj_t *s_key_matrix = NULL;
static lv_obj_t *s_checksum_panel = NULL;

static uint16_t s_match_indices[UI_SUGGESTION_STORE_MAX];
static size_t s_match_total = 0;

static ui_approval_decision_t s_approval_decision = UI_APPROVAL_TIMEOUT;
static lv_obj_t *s_approval_countdown = NULL;
static int64_t s_approval_deadline_us = 0;

#define UI_EVENT_LOG_MAX 8
#define UI_EVENT_LINE_MAX 96
static char s_event_lines[UI_EVENT_LOG_MAX][UI_EVENT_LINE_MAX];
static int s_event_count = 0;

static void reset_screen_widget_refs(void)
{
    s_status_label = NULL;
    s_entry_words_label = NULL;
    s_entry_prefix_label = NULL;
    s_suggestion_row = NULL;
    s_key_matrix = NULL;
    s_checksum_panel = NULL;
    s_approval_countdown = NULL;
    s_match_total = 0;
}

static void style_screen(lv_obj_t *scr)
{
    lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x000000), 0);
}

static void style_button(lv_obj_t *btn)
{
    lv_obj_set_style_radius(btn, 6, 0);
    lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
    lv_obj_set_style_bg_color(btn, lv_color_hex(0x000000), 0);
    lv_obj_set_style_border_width(btn, 2, 0);
    lv_obj_set_style_border_color(btn, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_color(btn, lv_color_hex(0xFFFFFF), 0);

    lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED);
    lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED);
    lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
    lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
}

static void style_key_matrix(lv_obj_t *km)
{
    lv_obj_set_style_bg_opa(km, LV_OPA_COVER, 0);
    lv_obj_set_style_bg_color(km, lv_color_hex(0x000000), 0);
    lv_obj_set_style_border_width(km, 1, 0);
    lv_obj_set_style_border_color(km, lv_color_hex(0xFFFFFF), 0);

    lv_obj_set_style_bg_opa(km, LV_OPA_COVER, LV_PART_ITEMS);
    lv_obj_set_style_bg_color(km, lv_color_hex(0x000000), LV_PART_ITEMS);
    lv_obj_set_style_border_color(km, lv_color_hex(0xFFFFFF), LV_PART_ITEMS);
    lv_obj_set_style_border_width(km, 1, LV_PART_ITEMS);
    lv_obj_set_style_text_color(km, lv_color_hex(0xFFFFFF), LV_PART_ITEMS);

    lv_obj_set_style_border_color(km, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED);
    lv_obj_set_style_text_color(km, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED);

    lv_obj_set_style_text_color(km, lv_color_hex(0x505050), LV_PART_ITEMS | LV_STATE_DISABLED);
    lv_obj_set_style_border_color(km, lv_color_hex(0x303030), LV_PART_ITEMS | LV_STATE_DISABLED);
}

static void lvgl_tick_cb(void *arg)
{
    (void)arg;
    lv_tick_inc(UI_TICK_MS);
}

static void lvgl_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    const uint16_t w = (uint16_t)(area->x2 - area->x1 + 1);
    const uint16_t h = (uint16_t)(area->y2 - area->y1 + 1);
    const uint32_t px_count = (uint32_t)w * h;

    if (px_count > (uint32_t)(DISPLAY_WIDTH * UI_DRAW_LINES)) {
        lv_disp_flush_ready(disp_drv);
        return;
    }

    for (uint32_t i = 0; i < px_count; ++i) {
        uint16_t c = color_p[i].full;
        s_flush_bytes[i * 2 + 0] = (uint8_t)(c >> 8);
        s_flush_bytes[i * 2 + 1] = (uint8_t)(c & 0xFF);
    }

    (void)ili9341_blit_rgb565((uint16_t)area->x1,
                              (uint16_t)area->y1,
                              w,
                              h,
                              s_flush_bytes);

    lv_disp_flush_ready(disp_drv);
}

static void lvgl_touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
    (void)drv;

    int sx = 0;
    int sy = 0;
    uint16_t pressure = 0;

    if (!touch_read_screen(&sx, &sy, &pressure)) {
        data->state = LV_INDEV_STATE_RELEASED;
        return;
    }

    data->state = LV_INDEV_STATE_PRESSED;
    data->point.x = sx;
    data->point.y = sy;
}

static esp_err_t ui_lvgl_init_once(void)
{
    if (s_lvgl_inited) {
        return ESP_OK;
    }

    ESP_ERROR_CHECK(touch_init());

    lv_init();

    lv_disp_draw_buf_init(&s_draw_buf, s_lv_draw_buf, NULL, DISPLAY_WIDTH * UI_DRAW_LINES);

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = DISPLAY_WIDTH;
    disp_drv.ver_res = DISPLAY_HEIGHT;
    disp_drv.flush_cb = lvgl_flush_cb;
    disp_drv.draw_buf = &s_draw_buf;
    lv_disp_drv_register(&disp_drv);

    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = lvgl_touch_read_cb;
    lv_indev_drv_register(&indev_drv);

    const esp_timer_create_args_t lv_tick_timer_args = {
        .callback = lvgl_tick_cb,
        .name = "lv_tick",
    };
    ESP_ERROR_CHECK(esp_timer_create(&lv_tick_timer_args, &s_lv_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(s_lv_tick_timer, UI_TICK_MS * 1000));

    s_lvgl_inited = true;
    return ESP_OK;
}

static void set_status(const char *text)
{
    if (s_status_label != NULL) {
        lv_label_set_text(s_status_label, text);
    }
}

static void on_generate(lv_event_t *e)
{
    (void)e;

    memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
    if (generate_mnemonic_12(s_selected_mnemonic, sizeof(s_selected_mnemonic)) != 0) {
        set_status("Failed to generate mnemonic");
        return;
    }

    s_have_mnemonic = true;
    s_done = true;
}

static void build_main_menu(void);
static void refresh_entry_ui(void);
static bool verify_or_prompt_checksum(void);

static const char *s_key_map[] = {
    "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
    "a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
    "z", "x", "c", "v", "b", "n", "m", "<", "",
};

static void clear_entry_state(void)
{
    secure_memzero(s_committed_words, sizeof(s_committed_words));
    secure_memzero(s_prefix, sizeof(s_prefix));
    s_word_count = 0;
    s_prefix_len = 0;
    s_checksum_override = false;
    s_match_total = 0;
}

static bool join_committed_words(char *out, size_t out_len)
{
    size_t cursor = 0;

    if (out == NULL || out_len == 0) {
        return false;
    }

    out[0] = '\0';

    for (size_t i = 0; i < s_word_count; ++i) {
        size_t wl = strlen(s_committed_words[i]);

        if (cursor != 0) {
            if (cursor + 1 >= out_len) {
                out[0] = '\0';
                return false;
            }
            out[cursor++] = ' ';
        }

        if (cursor + wl >= out_len) {
            out[0] = '\0';
            return false;
        }

        memcpy(out + cursor, s_committed_words[i], wl);
        cursor += wl;
        out[cursor] = '\0';
    }

    return true;
}

static void commit_word(const char *word)
{
    if (word == NULL || s_word_count >= UI_MNEMONIC_WORDS) {
        return;
    }

    memset(s_committed_words[s_word_count], 0, UI_BIP39_WORD_MAX);
    strncpy(s_committed_words[s_word_count], word, UI_BIP39_WORD_MAX - 1);
    s_word_count++;

    secure_memzero(s_prefix, sizeof(s_prefix));
    s_prefix_len = 0;
    s_checksum_override = false;
}

static void on_checksum_use_anyway(lv_event_t *e)
{
    (void)e;

    char joined[256];
    memset(joined, 0, sizeof(joined));
    if (!join_committed_words(joined, sizeof(joined))) {
        set_status("Mnemonic buffer error");
        secure_memzero(joined, sizeof(joined));
        return;
    }

    memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
    strncpy(s_selected_mnemonic, joined, sizeof(s_selected_mnemonic) - 1);
    s_have_mnemonic = true;
    s_done = true;
    s_checksum_override = true;
    secure_memzero(joined, sizeof(joined));
}

static void on_checksum_edit_words(lv_event_t *e)
{
    (void)e;

    if (s_checksum_panel != NULL) {
        lv_obj_del(s_checksum_panel);
        s_checksum_panel = NULL;
    }
    set_status("Checksum mismatch: edit or use anyway");
    refresh_entry_ui();
}

static bool verify_or_prompt_checksum(void)
{
    char joined[256];

    if (s_key_matrix == NULL || s_entry_words_label == NULL || s_entry_prefix_label == NULL) {
        return false;
    }

    if (s_word_count != UI_MNEMONIC_WORDS || s_prefix_len != 0) {
        set_status("Need exactly 12 complete words");
        return false;
    }

    memset(joined, 0, sizeof(joined));
    if (!join_committed_words(joined, sizeof(joined))) {
        set_status("Mnemonic buffer error");
        secure_memzero(joined, sizeof(joined));
        return false;
    }

    if (mnemonic_validate(joined) == 0) {
        if (s_checksum_panel != NULL) {
            lv_obj_del(s_checksum_panel);
            s_checksum_panel = NULL;
        }
        set_status("Checksum valid");
        memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
        strncpy(s_selected_mnemonic, joined, sizeof(s_selected_mnemonic) - 1);
        s_have_mnemonic = true;
        s_done = true;
        secure_memzero(joined, sizeof(joined));
        return true;
    }

    if (s_checksum_panel != NULL) {
        lv_obj_del(s_checksum_panel);
        s_checksum_panel = NULL;
    }

    s_checksum_panel = lv_obj_create(lv_scr_act());
    lv_obj_set_size(s_checksum_panel, DISPLAY_WIDTH - 20, 82);
    lv_obj_align(s_checksum_panel, LV_ALIGN_TOP_MID, 0, 78);
    lv_obj_set_style_bg_color(s_checksum_panel, lv_color_hex(0x000000), 0);
    lv_obj_set_style_border_color(s_checksum_panel, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_border_width(s_checksum_panel, 2, 0);
    lv_obj_set_style_radius(s_checksum_panel, 6, 0);
    lv_obj_set_style_pad_all(s_checksum_panel, 6, 0);

    lv_obj_t *txt = lv_label_create(s_checksum_panel);
    lv_label_set_text(txt, "Checksum invalid. Use anyway?");
    lv_obj_set_style_text_color(txt, lv_color_hex(0xFF0000), 0);
    lv_obj_align(txt, LV_ALIGN_TOP_MID, 0, 0);

    lv_obj_t *use_btn = lv_btn_create(s_checksum_panel);
    lv_obj_set_size(use_btn, 128, 30);
    lv_obj_align(use_btn, LV_ALIGN_BOTTOM_LEFT, 4, -2);
    style_button(use_btn);
    lv_obj_set_style_border_color(use_btn, lv_color_hex(0xFF0000), 0);
    lv_obj_add_event_cb(use_btn, on_checksum_use_anyway, LV_EVENT_RELEASED, NULL);
    lv_obj_t *use_lbl = lv_label_create(use_btn);
    lv_label_set_text(use_lbl, "Use Anyway");
    lv_obj_center(use_lbl);

    lv_obj_t *edit_btn = lv_btn_create(s_checksum_panel);
    lv_obj_set_size(edit_btn, 128, 30);
    lv_obj_align(edit_btn, LV_ALIGN_BOTTOM_RIGHT, -4, -2);
    style_button(edit_btn);
    lv_obj_add_event_cb(edit_btn, on_checksum_edit_words, LV_EVENT_RELEASED, NULL);
    lv_obj_t *edit_lbl = lv_label_create(edit_btn);
    lv_label_set_text(edit_lbl, "Edit Words");
    lv_obj_center(edit_lbl);

    set_status("Checksum invalid");
    secure_memzero(joined, sizeof(joined));
    return false;
}

static void on_back_to_menu(lv_event_t *e)
{
    (void)e;
    if (s_checksum_panel != NULL) {
        lv_obj_del(s_checksum_panel);
        s_checksum_panel = NULL;
    }
    clear_entry_state();
    build_main_menu();
}

static void on_suggestion_tap(lv_event_t *e)
{
    const char *word = (const char *)lv_event_get_user_data(e);

    if (s_suggestion_row == NULL || word == NULL || s_word_count >= UI_MNEMONIC_WORDS) {
        return;
    }

    commit_word(word);
    refresh_entry_ui();

    if (s_word_count == UI_MNEMONIC_WORDS) {
        (void)verify_or_prompt_checksum();
    }
}

static void entry_backspace_step(void)
{
    if (s_prefix_len > 0) {
        s_prefix_len--;
        s_prefix[s_prefix_len] = '\0';
    } else if (s_word_count > 0) {
        s_word_count--;
        memset(s_prefix, 0, sizeof(s_prefix));
        strncpy(s_prefix, s_committed_words[s_word_count], sizeof(s_prefix) - 1);
        s_prefix_len = strlen(s_prefix);
        secure_memzero(s_committed_words[s_word_count], sizeof(s_committed_words[s_word_count]));
    }

    s_checksum_override = false;
    if (s_checksum_panel != NULL) {
        lv_obj_del(s_checksum_panel);
        s_checksum_panel = NULL;
    }
    refresh_entry_ui();
}

static void on_key_matrix(lv_event_t *e)
{
    lv_obj_t *obj = lv_event_get_target(e);
    if (s_key_matrix == NULL || obj != s_key_matrix) {
        return;
    }

    uint16_t idx = lv_btnmatrix_get_selected_btn(obj);
    const char *txt = lv_btnmatrix_get_btn_text(obj, idx);

    if (txt == NULL || txt[0] == '\0' || txt[1] != '\0') {
        return;
    }

    if (txt[0] == '<') {
        entry_backspace_step();
        return;
    }

    if (s_word_count >= UI_MNEMONIC_WORDS) {
        set_status("Already have 12 words");
        return;
    }

    if (s_prefix_len + 1 >= sizeof(s_prefix)) {
        set_status("Word too long");
        return;
    }

    s_prefix[s_prefix_len++] = txt[0];
    s_prefix[s_prefix_len] = '\0';
    s_checksum_override = false;

    refresh_entry_ui();

    if (s_match_total == 1) {
        const char *word = mnemonic_word_at(s_match_indices[0]);
        if (word != NULL) {
            commit_word(word);
            refresh_entry_ui();
            if (s_word_count == UI_MNEMONIC_WORDS) {
                (void)verify_or_prompt_checksum();
            }
        }
    }
}

static void refresh_entry_ui(void)
{
    size_t shown = 0;
    char words_line[256];
    char prefix_line[64];

    if (s_checksum_panel != NULL) {
        lv_obj_del(s_checksum_panel);
        s_checksum_panel = NULL;
    }

    memset(words_line, 0, sizeof(words_line));
    (void)join_committed_words(words_line, sizeof(words_line));

    if (s_entry_words_label != NULL) {
        if (s_word_count == 0) {
            lv_label_set_text(s_entry_words_label, "(no words selected)");
        } else {
            lv_label_set_text(s_entry_words_label, words_line);
        }
    }

    snprintf(prefix_line, sizeof(prefix_line), "Word %u/12 prefix: %s",
             (unsigned)(s_word_count + 1),
             (s_prefix_len > 0) ? s_prefix : "-");
    if (s_entry_prefix_label != NULL) {
        lv_label_set_text(s_entry_prefix_label, prefix_line);
    }

    if (s_suggestion_row != NULL) {
        lv_obj_clean(s_suggestion_row);

        if (s_prefix_len > 0 && s_word_count < UI_MNEMONIC_WORDS) {
            s_match_total = mnemonic_words_with_prefix(s_prefix, s_match_indices, UI_SUGGESTION_STORE_MAX);
            shown = (s_match_total > UI_SUGGESTION_VISIBLE_MAX) ? UI_SUGGESTION_VISIBLE_MAX : s_match_total;

            for (size_t i = 0; i < shown; ++i) {
                const char *word = mnemonic_word_at(s_match_indices[i]);
                lv_obj_t *btn;
                lv_obj_t *lbl;
                if (word == NULL) {
                    continue;
                }
                btn = lv_btn_create(s_suggestion_row);
                lv_obj_set_size(btn, 84, 26);
                style_button(btn);
                lv_obj_add_event_cb(btn, on_suggestion_tap, LV_EVENT_RELEASED, (void *)word);
                lbl = lv_label_create(btn);
                lv_label_set_text(lbl, word);
                lv_obj_center(lbl);
            }

            if (s_match_total > shown) {
                char more[24];
                lv_obj_t *m = lv_label_create(s_suggestion_row);
                snprintf(more, sizeof(more), "+%u more", (unsigned)(s_match_total - shown));
                lv_label_set_text(m, more);
                lv_obj_set_style_text_color(m, lv_color_hex(0xFFFFFF), 0);
            }

            if (s_match_total == 0) {
                set_status("No matches");
            } else {
                set_status("");
            }
        } else {
            s_match_total = 0;
        }
    }

    if (s_key_matrix != NULL) {
        const uint16_t matrix_btn_count = 27;
        for (uint16_t i = 0; i < matrix_btn_count; ++i) {
            const char *txt = lv_btnmatrix_get_btn_text(s_key_matrix, i);
            bool enabled = false;
            if (txt == NULL || txt[0] == '\0' || txt[1] != '\0') {
                continue;
            }

            if (txt[0] == '<') {
                enabled = (s_prefix_len > 0 || s_word_count > 0);
            } else if (s_word_count >= UI_MNEMONIC_WORDS) {
                enabled = false;
            } else if (s_prefix_len == 0) {
                size_t start = 0;
                enabled = (mnemonic_words_for_letter(txt[0], &start) > 0);
            } else {
                enabled = mnemonic_letter_extends_prefix(s_prefix, s_prefix_len, txt[0]);
            }

            if (enabled) {
                lv_btnmatrix_clear_btn_ctrl(s_key_matrix, i, LV_BTNMATRIX_CTRL_DISABLED);
            } else {
                lv_btnmatrix_set_btn_ctrl(s_key_matrix, i, LV_BTNMATRIX_CTRL_DISABLED);
            }
        }
    }
}

static void build_entry_screen(void)
{
    lv_obj_t *scr = lv_scr_act();
    lv_obj_t *title;
    lv_obj_t *close_btn;
    lv_obj_t *close_lbl;

    reset_screen_widget_refs();
    lv_obj_clean(scr);
    style_screen(scr);
    clear_entry_state();

    title = lv_label_create(scr);
    lv_label_set_text(title, "Enter Mnemonic");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 4);

    s_entry_words_label = lv_label_create(scr);
    lv_obj_set_width(s_entry_words_label, DISPLAY_WIDTH - 16);
    lv_label_set_long_mode(s_entry_words_label, LV_LABEL_LONG_WRAP);
    lv_label_set_text(s_entry_words_label, "(no words selected)");
    lv_obj_set_style_text_color(s_entry_words_label, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(s_entry_words_label, LV_ALIGN_TOP_LEFT, 8, 26);

    s_entry_prefix_label = lv_label_create(scr);
    lv_label_set_text(s_entry_prefix_label, "Word 1/12 prefix: -");
    lv_obj_set_style_text_color(s_entry_prefix_label, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(s_entry_prefix_label, LV_ALIGN_TOP_LEFT, 8, 66);

    s_status_label = lv_label_create(scr);
    lv_label_set_text(s_status_label, "");
    lv_obj_set_style_text_color(s_status_label, lv_color_hex(0xFF0000), 0);
    lv_obj_align(s_status_label, LV_ALIGN_TOP_LEFT, 8, 82);

    s_suggestion_row = lv_obj_create(scr);
    lv_obj_set_size(s_suggestion_row, DISPLAY_WIDTH - 12, 30);
    lv_obj_align(s_suggestion_row, LV_ALIGN_TOP_MID, 0, 96);
    lv_obj_set_style_bg_color(s_suggestion_row, lv_color_hex(0x000000), 0);
    lv_obj_set_style_border_color(s_suggestion_row, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_border_width(s_suggestion_row, 1, 0);
    lv_obj_set_style_pad_all(s_suggestion_row, 2, 0);
    lv_obj_set_scroll_dir(s_suggestion_row, LV_DIR_HOR);
    lv_obj_set_flex_flow(s_suggestion_row, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(s_suggestion_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);

    close_btn = lv_btn_create(scr);
    lv_obj_set_size(close_btn, 42, 30);
    lv_obj_align(close_btn, LV_ALIGN_TOP_RIGHT, -4, 2);
    style_button(close_btn);
    lv_obj_add_event_cb(close_btn, on_back_to_menu, LV_EVENT_CLICKED, NULL);
    close_lbl = lv_label_create(close_btn);
    lv_label_set_text(close_lbl, "X");
    lv_obj_center(close_lbl);

    s_key_matrix = lv_btnmatrix_create(scr);
    lv_obj_set_size(s_key_matrix, DISPLAY_WIDTH - 16, 100);
    lv_obj_align(s_key_matrix, LV_ALIGN_TOP_MID, 0, 132);
    lv_btnmatrix_set_map(s_key_matrix, s_key_map);
    lv_obj_add_event_cb(s_key_matrix, on_key_matrix, LV_EVENT_VALUE_CHANGED, NULL);
    style_key_matrix(s_key_matrix);
    lv_obj_set_style_pad_all(s_key_matrix, 0, 0);
    lv_obj_set_style_pad_gap(s_key_matrix, 0, LV_PART_ITEMS);
    lv_obj_set_style_border_width(s_key_matrix, 1, LV_PART_ITEMS);

    refresh_entry_ui();
}

static void on_enter(lv_event_t *e)
{
    (void)e;
    build_entry_screen();
}

static void on_approve_deny(lv_event_t *e)
{
    (void)e;
    s_approval_decision = UI_APPROVAL_DENY;
}

static void on_approve_once(lv_event_t *e)
{
    (void)e;
    s_approval_decision = UI_APPROVAL_ONCE;
}

static void on_approve_always(lv_event_t *e)
{
    (void)e;
    s_approval_decision = UI_APPROVAL_ALWAYS;
}

static void on_use_viewed(lv_event_t *e)
{
    (void)e;
    if (!s_have_mnemonic) {
        set_status("No generated mnemonic yet");
        return;
    }
    s_done = true;
}

static void build_view_screen(void)
{
    lv_obj_t *scr = lv_scr_act();
    reset_screen_widget_refs();
    lv_obj_clean(scr);
    style_screen(scr);

    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "Mnemonic Preview");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 6);

    lv_obj_t *mn = lv_label_create(scr);
    lv_obj_set_width(mn, DISPLAY_WIDTH - 20);
    lv_label_set_long_mode(mn, LV_LABEL_LONG_WRAP);
    lv_label_set_text(mn, s_have_mnemonic ? s_selected_mnemonic : "No mnemonic generated yet");
    lv_obj_align(mn, LV_ALIGN_TOP_LEFT, 10, 40);
    lv_obj_set_style_text_color(mn, lv_color_hex(0xFFFFFF), 0);

    lv_obj_t *use_btn = lv_btn_create(scr);
    lv_obj_set_size(use_btn, 150, 36);
    lv_obj_align(use_btn, LV_ALIGN_BOTTOM_LEFT, 8, -8);
    lv_obj_add_event_cb(use_btn, on_use_viewed, LV_EVENT_RELEASED, NULL);
    style_button(use_btn);
    lv_obj_t *use_lbl = lv_label_create(use_btn);
    lv_label_set_text(use_lbl, "Use This");
    lv_obj_center(use_lbl);

    lv_obj_t *back_btn = lv_btn_create(scr);
    lv_obj_set_size(back_btn, 150, 36);
    lv_obj_align(back_btn, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
    lv_obj_add_event_cb(back_btn, on_back_to_menu, LV_EVENT_RELEASED, NULL);
    style_button(back_btn);
    lv_obj_t *back_lbl = lv_label_create(back_btn);
    lv_label_set_text(back_lbl, "Back");
    lv_obj_center(back_lbl);

    s_status_label = NULL;
}

static void on_view(lv_event_t *e)
{
    (void)e;

    if (!s_have_mnemonic) {
        memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
        if (generate_mnemonic_12(s_selected_mnemonic, sizeof(s_selected_mnemonic)) == 0) {
            s_have_mnemonic = true;
        }
    }

    build_view_screen();
}

static void build_main_menu(void)
{
    lv_obj_t *scr = lv_scr_act();
    reset_screen_widget_refs();
    lv_obj_clean(scr);
    style_screen(scr);

    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "n_signer CYD");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);

    lv_obj_t *subtitle = lv_label_create(scr);
    lv_label_set_text(subtitle, "Select startup mode");
    lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 36);
    lv_obj_set_style_text_color(subtitle, lv_color_hex(0xFFFFFF), 0);

    lv_obj_t *btn_gen = lv_btn_create(scr);
    lv_obj_set_size(btn_gen, DISPLAY_WIDTH - 40, 42);
    lv_obj_align(btn_gen, LV_ALIGN_TOP_MID, 0, 64);
    lv_obj_add_event_cb(btn_gen, on_generate, LV_EVENT_RELEASED, NULL);
    style_button(btn_gen);
    lv_obj_t *lbl_gen = lv_label_create(btn_gen);
    lv_label_set_text(lbl_gen, "Generate New Mnemonic");
    lv_obj_center(lbl_gen);

    lv_obj_t *btn_enter = lv_btn_create(scr);
    lv_obj_set_size(btn_enter, DISPLAY_WIDTH - 40, 42);
    lv_obj_align(btn_enter, LV_ALIGN_TOP_MID, 0, 112);
    lv_obj_add_event_cb(btn_enter, on_enter, LV_EVENT_RELEASED, NULL);
    style_button(btn_enter);
    lv_obj_t *lbl_enter = lv_label_create(btn_enter);
    lv_label_set_text(lbl_enter, "Enter Existing Mnemonic");
    lv_obj_center(lbl_enter);

    lv_obj_t *btn_view = lv_btn_create(scr);
    lv_obj_set_size(btn_view, DISPLAY_WIDTH - 40, 42);
    lv_obj_align(btn_view, LV_ALIGN_TOP_MID, 0, 160);
    lv_obj_add_event_cb(btn_view, on_view, LV_EVENT_RELEASED, NULL);
    style_button(btn_view);
    lv_obj_t *lbl_view = lv_label_create(btn_view);
    lv_label_set_text(lbl_view, "View Current Mnemonic");
    lv_obj_center(lbl_view);

    s_status_label = lv_label_create(scr);
    lv_label_set_text(s_status_label, "");
    lv_obj_set_style_text_color(s_status_label, lv_color_hex(0xFF0000), 0);
    lv_obj_align(s_status_label, LV_ALIGN_BOTTOM_MID, 0, -10);
}

void ui_show_approval_prompt(const char *caller_pubkey_hex, int kind, const char *content, uint32_t timeout_ms)
{
    if (!s_lvgl_inited) {
        return;
    }

    lv_obj_t *scr = lv_scr_act();
    reset_screen_widget_refs();
    lv_obj_clean(scr);
    style_screen(scr);

    char caller_line[64];
    char kind_line[32];
    char content_line[96];

    if (caller_pubkey_hex != NULL && strlen(caller_pubkey_hex) >= 12) {
        snprintf(caller_line, sizeof(caller_line), "from %.6s..%.4s",
                 caller_pubkey_hex, caller_pubkey_hex + strlen(caller_pubkey_hex) - 4);
    } else {
        snprintf(caller_line, sizeof(caller_line), "from %s",
                 caller_pubkey_hex ? caller_pubkey_hex : "anon");
    }

    snprintf(kind_line, sizeof(kind_line), "kind: %d", kind);
    snprintf(content_line, sizeof(content_line), "%.80s", content ? content : "");

    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "Approve sign_event?");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFF0000), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);

    lv_obj_t *caller_lbl = lv_label_create(scr);
    lv_label_set_text(caller_lbl, caller_line);
    lv_obj_set_style_text_color(caller_lbl, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(caller_lbl, LV_ALIGN_TOP_LEFT, 8, 34);

    lv_obj_t *kind_lbl = lv_label_create(scr);
    lv_label_set_text(kind_lbl, kind_line);
    lv_obj_set_style_text_color(kind_lbl, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(kind_lbl, LV_ALIGN_TOP_LEFT, 8, 52);

    lv_obj_t *content_lbl = lv_label_create(scr);
    lv_obj_set_width(content_lbl, DISPLAY_WIDTH - 16);
    lv_label_set_long_mode(content_lbl, LV_LABEL_LONG_WRAP);
    lv_label_set_text(content_lbl, content_line);
    lv_obj_set_style_text_color(content_lbl, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(content_lbl, LV_ALIGN_TOP_LEFT, 8, 72);

    lv_obj_t *deny_btn = lv_btn_create(scr);
    lv_obj_set_size(deny_btn, 98, 34);
    lv_obj_align(deny_btn, LV_ALIGN_BOTTOM_LEFT, 8, -8);
    lv_obj_add_event_cb(deny_btn, on_approve_deny, LV_EVENT_RELEASED, NULL);
    style_button(deny_btn);
    lv_obj_t *deny_lbl = lv_label_create(deny_btn);
    lv_label_set_text(deny_lbl, "Deny");
    lv_obj_center(deny_lbl);

    lv_obj_t *once_btn = lv_btn_create(scr);
    lv_obj_set_size(once_btn, 98, 34);
    lv_obj_align(once_btn, LV_ALIGN_BOTTOM_MID, 0, -8);
    lv_obj_add_event_cb(once_btn, on_approve_once, LV_EVENT_RELEASED, NULL);
    style_button(once_btn);
    lv_obj_t *once_lbl = lv_label_create(once_btn);
    lv_label_set_text(once_lbl, "Approve");
    lv_obj_center(once_lbl);

    lv_obj_t *always_btn = lv_btn_create(scr);
    lv_obj_set_size(always_btn, 98, 34);
    lv_obj_align(always_btn, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
    lv_obj_add_event_cb(always_btn, on_approve_always, LV_EVENT_RELEASED, NULL);
    style_button(always_btn);
    lv_obj_t *always_lbl = lv_label_create(always_btn);
    lv_label_set_text(always_lbl, "Always");
    lv_obj_center(always_lbl);

    s_approval_countdown = lv_label_create(scr);
    lv_obj_set_style_text_color(s_approval_countdown, lv_color_hex(0xFF0000), 0);
    lv_obj_align(s_approval_countdown, LV_ALIGN_BOTTOM_MID, 0, -46);

    s_approval_decision = UI_APPROVAL_TIMEOUT;
    s_approval_deadline_us = esp_timer_get_time() + ((int64_t)timeout_ms * 1000);
}

ui_approval_decision_t ui_wait_for_approval(uint32_t timeout_ms)
{
    if (!s_lvgl_inited) {
        return UI_APPROVAL_TIMEOUT;
    }

    int64_t deadline_us = esp_timer_get_time() + ((int64_t)timeout_ms * 1000);

    while (1) {
        int64_t now = esp_timer_get_time();
        if (s_approval_decision != UI_APPROVAL_TIMEOUT) {
            return s_approval_decision;
        }

        if (now >= deadline_us) {
            return UI_APPROVAL_TIMEOUT;
        }

        if (s_approval_countdown != NULL) {
            int remain_s = (int)((deadline_us - now + 999999) / 1000000);
            if (remain_s < 0) {
                remain_s = 0;
            }
            char buf[32];
            snprintf(buf, sizeof(buf), "timeout: %ds", remain_s);
            lv_label_set_text(s_approval_countdown, buf);
        }

        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

static void show_event_log_screen(void)
{
    lv_obj_t *scr = lv_scr_act();
    reset_screen_widget_refs();
    lv_obj_clean(scr);
    style_screen(scr);

    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "recent events");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFF0000), 0);
    lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 6);

    lv_obj_t *sub = lv_label_create(scr);
    lv_label_set_text(sub, "newest first");
    lv_obj_set_style_text_color(sub, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(sub, LV_ALIGN_TOP_RIGHT, -8, 8);

    int y = 30;
    for (int i = 0; i < s_event_count; ++i) {
        lv_obj_t *line = lv_label_create(scr);
        lv_obj_set_width(line, DISPLAY_WIDTH - 16);
        lv_label_set_long_mode(line, LV_LABEL_LONG_DOT);
        lv_label_set_text(line, s_event_lines[i]);
        lv_obj_set_style_text_color(line, lv_color_hex(0xFFFFFF), 0);
        lv_obj_align(line, LV_ALIGN_TOP_LEFT, 8, y);
        y += 24;
    }

    if (s_event_count == 0) {
        lv_obj_t *empty = lv_label_create(scr);
        lv_label_set_text(empty, "No events yet");
        lv_obj_set_style_text_color(empty, lv_color_hex(0xFFFFFF), 0);
        lv_obj_align(empty, LV_ALIGN_TOP_LEFT, 8, 36);
    }

    lv_obj_t *ready = lv_label_create(scr);
    lv_label_set_text(ready, "ready");
    lv_obj_set_style_text_color(ready, lv_color_hex(0xFF0000), 0);
    lv_obj_align(ready, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
}

void ui_show_idle(const char *npub_full, const char *mnemonic_full, const char *version_text)
{
    if (!s_lvgl_inited) {
        return;
    }

    lv_obj_t *scr = lv_scr_act();
    reset_screen_widget_refs();
    lv_obj_clean(scr);
    style_screen(scr);

    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "nostr npub");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 6);

    lv_obj_t *npub = lv_label_create(scr);
    lv_obj_set_width(npub, DISPLAY_WIDTH - 16);
    lv_label_set_long_mode(npub, LV_LABEL_LONG_WRAP);
    lv_label_set_text(npub, npub_full ? npub_full : "");
    lv_obj_set_style_text_color(npub, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(npub, LV_ALIGN_TOP_LEFT, 8, 24);

    lv_obj_t *mn_title = lv_label_create(scr);
    lv_label_set_text(mn_title, "mnemonic");
    lv_obj_set_style_text_color(mn_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align_to(mn_title, npub, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 8);

    lv_obj_t *mn = lv_label_create(scr);
    lv_obj_set_width(mn, DISPLAY_WIDTH - 16);
    lv_label_set_long_mode(mn, LV_LABEL_LONG_WRAP);
    lv_label_set_text(mn, mnemonic_full ? mnemonic_full : "");
    lv_obj_set_style_text_color(mn, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align_to(mn, mn_title, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 4);

    lv_obj_t *ver = lv_label_create(scr);
    lv_label_set_text(ver, version_text ? version_text : "");
    lv_obj_set_style_text_color(ver, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(ver, LV_ALIGN_BOTTOM_LEFT, 8, -8);

    lv_obj_t *ready = lv_label_create(scr);
    lv_label_set_text(ready, "ready");
    lv_obj_set_style_text_color(ready, lv_color_hex(0xFF0000), 0);
    lv_obj_align(ready, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
}

void ui_add_event(const char *action, const char *caller_pubkey_hex, const char *extra)
{
    char caller_short[20] = "anon";
    char line[UI_EVENT_LINE_MAX];

    if (!s_lvgl_inited) {
        return;
    }

    if (caller_pubkey_hex != NULL && strlen(caller_pubkey_hex) >= 12) {
        snprintf(caller_short, sizeof(caller_short), "%.6s..%.4s",
                 caller_pubkey_hex, caller_pubkey_hex + strlen(caller_pubkey_hex) - 4);
    } else if (caller_pubkey_hex != NULL && caller_pubkey_hex[0] != '\0') {
        snprintf(caller_short, sizeof(caller_short), "%.16s", caller_pubkey_hex);
    }

    snprintf(line, sizeof(line), "%s %s %s",
             action ? action : "event",
             caller_short,
             extra ? extra : "");

    for (int i = (s_event_count < UI_EVENT_LOG_MAX ? s_event_count : UI_EVENT_LOG_MAX - 1); i > 0; --i) {
        strncpy(s_event_lines[i], s_event_lines[i - 1], UI_EVENT_LINE_MAX - 1);
        s_event_lines[i][UI_EVENT_LINE_MAX - 1] = '\0';
    }

    strncpy(s_event_lines[0], line, UI_EVENT_LINE_MAX - 1);
    s_event_lines[0][UI_EVENT_LINE_MAX - 1] = '\0';

    if (s_event_count < UI_EVENT_LOG_MAX) {
        s_event_count++;
    }

    show_event_log_screen();
}

void ui_pump(void)
{
    if (!s_lvgl_inited) {
        return;
    }
    lv_timer_handler();
}

int ui_init_runtime(void)
{
    return (ui_lvgl_init_once() == ESP_OK) ? 0 : -1;
}

int ui_run_until_working(char out_mnemonic[256])
{
    if (out_mnemonic == NULL) {
        return -1;
    }

    if (ui_lvgl_init_once() != ESP_OK) {
        return -1;
    }

    s_done = false;
    s_have_mnemonic = false;
    memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));

    build_main_menu();

    while (!s_done) {
        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(5));
    }

    memset(out_mnemonic, 0, 256);
    strncpy(out_mnemonic, s_selected_mnemonic, 255);

    ESP_LOGI(TAG, "Mnemonic selected via LVGL UI");
    return 0;
}
