#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    char step_id[64];        // current scenario step ("" = none)
    char armed[8];           // "qr" | "sound" | "none"
    char code[16];           // assembled puzzle code
    uint8_t solved_count;
    char ip[16];             // STA IP ("" = none)
    bool wake_active;        // wake-word detector up

    // Scene metadata (from the step's optional `scene` IR object; empty
    // strings = fall back to step_id / armed / code). Sizes mirror
    // puzzle_binding.h SB_MAX_*.
    char scene_title[48];
    char scene_subtitle[64];
    char scene_symbol[16];
    uint8_t scene_effect;    // scene_effect_t value (0=pulse 1=glitch 2=gyro 3=none)
} display_status_t;

/**
 * @brief Initialise the ST7796 panel via LovyanGFX and show a splash screen.
 *
 * Must be called once from app_main after NVS init but before the idle loop.
 * Non-fatal: a failure is logged and the rest of the firmware continues.
 * The internal refresh task is spawned here; it polls for status changes
 * every 250 ms.
 *
 * @return ESP_OK on success, or a driver error code.
 */
esp_err_t display_ui_init(void);

/**
 * @brief Copy a new status snapshot and request an async redraw.
 *
 * Thread-safe. The copy is protected by an internal mutex; the actual
 * rendering happens on the display task (LovyanGFX is not thread-safe
 * across tasks).
 *
 * @param s  Pointer to the caller's status struct.  May be NULL (no-op).
 */
void display_ui_set_status(const display_status_t *s);

/**
 * @brief Set the backlight brightness (LEDC PWM on LEDC_TIMER_1/CHANNEL_1).
 *
 * @param pct  0..100 (values above 100 are clamped). Default after init: 100.
 */
void display_ui_set_brightness(uint8_t pct);

/**
 * @brief Push a camera frame for the scene-view viewfinder (QR aiming aid).
 *
 * Thread-safe; intended as the qr_puzzle preview callback (wired in main.c).
 * Copies the grayscale QVGA buffer; the display task converts it to RGB565
 * and refreshes the canvas at ~5 fps. Frames are shown only while the scene
 * view is active with the QR puzzle armed. Non-QVGA frames are ignored.
 */
void display_ui_camera_frame(const uint8_t *gray, int width, int height);

/**
 * @brief Handle a 5-way key press (called from the buttons scan task).
 *
 * Original key numbering: 1=SELECT 2=DOWN 3=MENU 4=LEFT/RIGHT 5=UP.
 * Shell closed: SELECT / LEFT-RIGHT toggle scene↔status; MENU opens the
 * Workbench shell; UP/DOWN step the backlight brightness.
 * Shell open: grid navigation per the original ui_amiga_shell semantics
 * (SELECT launches the tile — Statut / Scene / Auto / Lumiere; MENU closes).
 * Thread-safe (atomic request flags consumed by the display task; the
 * brightness path drives LEDC directly, which has its own locking).
 */
void display_ui_handle_key(uint8_t key);

/**
 * @brief Install an optional key interceptor.
 *
 * If a hook is set and returns true for a given key, the key is consumed and
 * the normal shell/scene handling is skipped. Lets a takeover mode (e.g. the
 * gamebook) own the 5-way pad without display_ui having to depend on it.
 * Pass NULL to remove the hook. Thread-safe (single pointer write).
 */
typedef bool (*display_ui_key_hook_t)(uint8_t key);
void display_ui_set_key_hook(display_ui_key_hook_t hook);

/**
 * @brief Show a full-screen gamebook page (takes over the display).
 *
 * Dedicated "livre dont vous êtes le héros" view: a centred title, the full
 * passage text wrapped over multiple lines, and a choice menu at the bottom.
 * Forces this view until display_ui_gamebook_hide(). Thread-safe (buffers
 * copied under the mutex; the actual LVGL render runs on the display task).
 * Strings should be ASCII (the embedded fonts have no accents).
 *
 * @param title  short page title (top)
 * @param body   full passage text (wrapped); may be long
 * @param menu   choice lines (bottom), e.g. "[OK] ...\n[<>] ..."
 */
void display_ui_gamebook_show(const char *title, const char *body,
                              const char *menu, bool reset_scroll);

/**
 * @brief Scroll the gamebook reading area (Up/Down buttons).
 * @param dir  >0 = read further down, <0 = back up. Half-page per call.
 */
void display_ui_gamebook_scroll(int dir);

/** @brief Leave the gamebook view and return to the normal scene/status flow. */
void display_ui_gamebook_hide(void);

/**
 * @brief Show the gamebook library as a grid of up to 6 tiles.
 *
 * Each tile shows a story title; the tile at index `sel` is highlighted. The
 * gamebook owns the pad and calls this on every cursor move. Forces the
 * library view until a story is loaded or display_ui_library_hide(). Titles
 * should be ASCII.
 *
 * @param titles  array of `count` story titles
 * @param count   number of stories (clamped to 6)
 * @param sel     highlighted tile index
 */
void display_ui_library_show(const char *const *titles, int count, int sel);

/** @brief Leave the library view. */
void display_ui_library_hide(void);

/**
 * @brief Pop the pending shell-app launch request, if any.
 *
 * Dynamic shell tiles (from /littlefs/apps/<id>/) set a pending launch on
 * SELECT; the main loop polls this and performs the app action (e.g. read
 * the app's step.txt and call game_endpoint_apply_step). One-shot: returns
 * true at most once per launch.
 */
bool display_ui_take_pending_launch(char *id_out, size_t cap);

#ifdef __cplusplus
}
#endif
