// plip_ui — implementation. See plip_ui.h for the design.
//
// LVGL 9 widget tree, built once under bsp_display_lock(). The plip_virtual
// state callback can fire from any task, so the callback re-takes the lock
// before touching widgets. Touch events (keypad, bottom button) already run
// in the LVGL task context, so they call plip_virtual_* directly.

#include "plip_ui.h"

#include <string.h>

#include "bsp/esp-bsp.h"
#include "esp_log.h"
#include "lvgl.h"

#include "plip_virtual.h"

#define TAG "plip_ui"
#define LOCK_MS 1000

static bool        s_built = false;
static lv_obj_t   *s_phone_view;   // idle / ringing illustration
static lv_obj_t   *s_keypad_view;  // off-hook dial pad
static lv_obj_t   *s_status_lbl;   // line under the phone glyph
static lv_obj_t   *s_number_lbl;   // dialed digits
static lv_obj_t   *s_hook_btn;     // fixed bottom button
static lv_obj_t   *s_hook_lbl;     // its label
static char        s_number[21] = "";

static const char *KEYS[] = {
    "1", "2", "3",
    "4", "5", "6",
    "7", "8", "9",
    "*", "0", "#",
};

// ---------- touch event handlers (LVGL task context) ----------

static void key_event_cb(lv_event_t *e)
{
    const char *key = (const char *) lv_event_get_user_data(e);
    size_t len = strlen(s_number);
    if (len < sizeof(s_number) - 1) {
        s_number[len] = key[0];
        s_number[len + 1] = '\0';
        lv_label_set_text(s_number_lbl, s_number);
        // Report the running number to the master; "#" finalises (drop it
        // from the buffer first). The master decides what a valid number is.
        if (key[0] == '#' && len > 0) {
            s_number[len] = '\0';
            lv_label_set_text(s_number_lbl, s_number);
        }
        plip_virtual_dial(s_number);
    }
}

static void hook_btn_event_cb(lv_event_t *e)
{
    (void) e;
    switch (plip_virtual_state()) {
    case PLIP_HOOK_RINGING: plip_virtual_pickup(); break;
    case PLIP_HOOK_OFF:     plip_virtual_hangup(); break;
    default: break;  // idle: button is disabled, nothing to do
    }
}

// ---------- view switching (must hold the display lock) ----------

static void apply_state_locked(plip_hook_state_t st)
{
    switch (st) {
    case PLIP_HOOK_OFF:
        s_number[0] = '\0';
        lv_label_set_text(s_number_lbl, "");
        lv_obj_add_flag(s_phone_view, LV_OBJ_FLAG_HIDDEN);
        lv_obj_clear_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
        lv_label_set_text(s_hook_lbl, "Raccrocher");
        lv_obj_set_style_bg_color(s_hook_btn, lv_palette_main(LV_PALETTE_RED), 0);
        lv_obj_clear_state(s_hook_btn, LV_STATE_DISABLED);
        break;
    case PLIP_HOOK_RINGING:
        lv_obj_clear_flag(s_phone_view, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
        lv_label_set_text(s_status_lbl, "Appel entrant…");
        lv_label_set_text(s_hook_lbl, "Décrocher");
        lv_obj_set_style_bg_color(s_hook_btn, lv_palette_main(LV_PALETTE_GREEN), 0);
        lv_obj_clear_state(s_hook_btn, LV_STATE_DISABLED);
        break;
    case PLIP_HOOK_ON:
    default:
        lv_obj_clear_flag(s_phone_view, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
        lv_label_set_text(s_status_lbl, "PLIP — en attente");
        lv_label_set_text(s_hook_lbl, "—");
        lv_obj_set_style_bg_color(s_hook_btn, lv_palette_darken(LV_PALETTE_GREY, 2), 0);
        lv_obj_add_state(s_hook_btn, LV_STATE_DISABLED);
        break;
    }
}

// plip_virtual callback — arbitrary task context, so take the lock here.
static void on_hook_state(plip_hook_state_t st)
{
    if (bsp_display_lock(LOCK_MS)) {
        apply_state_locked(st);
        bsp_display_unlock();
    }
}

// ---------- widget tree ----------

static void build_phone_view(lv_obj_t *parent)
{
    s_phone_view = lv_obj_create(parent);
    lv_obj_remove_style_all(s_phone_view);
    lv_obj_set_size(s_phone_view, LV_PCT(100), LV_PCT(100));
    lv_obj_set_flex_flow(s_phone_view, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(s_phone_view, LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);

    lv_obj_t *glyph = lv_label_create(s_phone_view);
    lv_label_set_text(glyph, LV_SYMBOL_CALL);
    lv_obj_set_style_text_font(glyph, &lv_font_montserrat_48, 0);
    lv_obj_set_style_text_color(glyph, lv_palette_main(LV_PALETTE_BLUE), 0);

    s_status_lbl = lv_label_create(s_phone_view);
    lv_label_set_text(s_status_lbl, "PLIP — en attente");
    lv_obj_set_style_pad_top(s_status_lbl, 12, 0);
}

static void build_keypad_view(lv_obj_t *parent)
{
    s_keypad_view = lv_obj_create(parent);
    lv_obj_remove_style_all(s_keypad_view);
    lv_obj_set_size(s_keypad_view, LV_PCT(100), LV_PCT(100));
    lv_obj_set_flex_flow(s_keypad_view, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(s_keypad_view, LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_add_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);

    s_number_lbl = lv_label_create(s_keypad_view);
    lv_label_set_text(s_number_lbl, "");
    lv_obj_set_style_text_font(s_number_lbl, &lv_font_montserrat_24, 0);
    lv_obj_set_style_pad_bottom(s_number_lbl, 6, 0);

    lv_obj_t *grid = lv_obj_create(s_keypad_view);
    lv_obj_remove_style_all(grid);
    lv_obj_set_size(grid, 240, 150);
    lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
    lv_obj_set_flex_align(grid, LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);

    for (size_t i = 0; i < sizeof(KEYS) / sizeof(KEYS[0]); i++) {
        lv_obj_t *btn = lv_button_create(grid);
        lv_obj_set_size(btn, 64, 40);
        lv_obj_add_event_cb(btn, key_event_cb, LV_EVENT_CLICKED,
                            (void *) KEYS[i]);
        lv_obj_t *lbl = lv_label_create(btn);
        lv_label_set_text(lbl, KEYS[i]);
        lv_obj_center(lbl);
    }
}

static void build_hook_button(lv_obj_t *parent)
{
    s_hook_btn = lv_button_create(parent);
    lv_obj_set_size(s_hook_btn, LV_PCT(96), 44);
    lv_obj_align(s_hook_btn, LV_ALIGN_BOTTOM_MID, 0, -4);
    lv_obj_add_event_cb(s_hook_btn, hook_btn_event_cb, LV_EVENT_CLICKED, NULL);

    s_hook_lbl = lv_label_create(s_hook_btn);
    lv_label_set_text(s_hook_lbl, "—");
    lv_obj_center(s_hook_lbl);
}

esp_err_t plip_ui_init(void)
{
    if (s_built) return ESP_OK;
    if (!bsp_display_lock(LOCK_MS)) {
        ESP_LOGE(TAG, "display lock failed");
        return ESP_FAIL;
    }

    // The voice app starts the display but never lights the backlight — turn
    // it on (and to full brightness) or the whole UI is invisible.
    bsp_display_brightness_init();
    bsp_display_brightness_set(100);

    lv_obj_t *scr = lv_screen_active();
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x101020), 0);
    lv_obj_set_style_text_color(scr, lv_color_white(), 0);

    // Content area above the fixed bottom button.
    lv_obj_t *content = lv_obj_create(scr);
    lv_obj_remove_style_all(content);
    lv_obj_set_size(content, LV_PCT(100), 188);
    lv_obj_align(content, LV_ALIGN_TOP_MID, 0, 0);

    build_phone_view(content);
    build_keypad_view(content);
    build_hook_button(scr);

    apply_state_locked(plip_virtual_state());
    bsp_display_unlock();

    plip_virtual_register_state_cb(on_hook_state);
    s_built = true;
    ESP_LOGI(TAG, "phone UI ready");
    return ESP_OK;
}
