#include <stdio.h>
#include <string.h>
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#include "nuklear.h"
#include "widget.h"
#include "input.h"
#include "theme.h"
#include "../interface/katconfig.h"
#include "../interface/sdl_if.h"
#include "../interface/media_if.h"

//int row_height = 22;
extern struct theme g_theme;

static int widget_has_rom_source(struct option_widget *wgt);

static float widget_text_width(struct nk_context *ctx, const char *text, int len)
{
    if (!ctx || !ctx->style.font || !text || len <= 0)
        return 0.0f;

    return ctx->style.font->width(ctx->style.font->userdata,
                                  ctx->style.font->height,
                                  text, len);
}

/* Build the longest UTF-8 prefix that fits in value_w pixels with a trailing
   ellipsis. Returns str unchanged if it already fits. */
static const char *widget_fit_leading(struct nk_context *ctx, const char *str,
                                      float value_w, char *buf, int buf_size)
{
    static const char ellipsis[] = "\xE2\x80\xA6";
    int len;
    int ellipsis_len;
    float ellipsis_w;
    int best_len = 0;

    if (!str)
        str = "";
    if (!buf || buf_size <= 0)
        return str;

    buf[0] = '\0';
    len = (int)strlen(str);
    if (len == 0 || widget_text_width(ctx, str, len) <= value_w)
        return str;

    ellipsis_len = (int)strlen(ellipsis);
    ellipsis_w = widget_text_width(ctx, ellipsis, ellipsis_len);
    if (ellipsis_w > value_w || buf_size <= ellipsis_len)
        return buf;

    for (int offset = 0; offset < len; ) {
        nk_rune rune;
        int glyph_len = nk_utf_decode(str + offset, &rune, len - offset);
        int next_offset;
        int fitted_len;

        if (glyph_len <= 0)
            glyph_len = 1;

        next_offset = offset + glyph_len;
        fitted_len = next_offset + ellipsis_len;
        if (fitted_len >= buf_size)
            break;

        memcpy(buf, str, next_offset);
        memcpy(buf + next_offset, ellipsis, ellipsis_len + 1);
        if (widget_text_width(ctx, buf, fitted_len) <= value_w)
            best_len = next_offset;
        else
            break;

        offset = next_offset;
    }

    if (best_len > 0) {
        memcpy(buf, str, best_len);
        memcpy(buf + best_len, ellipsis, ellipsis_len + 1);
        return buf;
    }

    memcpy(buf, ellipsis, ellipsis_len + 1);
    return buf;
}

static const char *widget_label_display_string(struct option_widget *wgt,
                                               char *buf, int buf_size)
{
    if (!wgt)
        return "";

    if (widget_has_rom_source(wgt)) {
        snprintf(buf, buf_size, "%s [*]", wgt->label);
        return buf;
    }

    return wgt->label;
}

void widget_draw_selection(struct nk_context *ctx, struct nk_rect bounds)
{
    struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
    nk_fill_rect(canvas, bounds, 0, g_theme.selection_bg);
    struct nk_rect bar = nk_rect(bounds.x - 6, bounds.y + 2, 4, bounds.h - 4);
    nk_fill_rect(canvas, bar, 2.0f, g_theme.selection);
}

static void widget_draw_text(struct nk_context *ctx, const char *label,
                             int ellipsize)
{
    const struct nk_user_font *font = ctx->style.font;
    struct nk_rect b = nk_widget_bounds(ctx);
    const char *draw_label;
    char fitted[256];

    if (!label)
        label = "";

    nk_widget(&b, ctx);
    draw_label = label;
    if (ellipsize && widget_text_width(ctx, label, (int)strlen(label)) > b.w)
        draw_label = widget_fit_leading(ctx, label, b.w, fitted, sizeof(fitted));

    nk_draw_text(nk_window_get_canvas(ctx),
         nk_rect(b.x, b.y, b.w, font->height),
         draw_label, strlen(draw_label), font,
         ctx->style.window.background, ctx->style.text.color);
}

void widget_custom_label(struct nk_context *ctx, const char *label)
{
    widget_draw_text(ctx, label, 0);
}

static int widget_has_rom_source(struct option_widget *wgt)
{
    return wgt->p_source_mask && wgt->mask_value &&
           ((*wgt->p_source_mask & wgt->mask_value) != 0);
}

static void widget_custom_label_with_rom_marker(struct nk_context *ctx,
                                                struct option_widget *wgt)
{
    char label[sizeof(wgt->label) + sizeof(" [*]")];
    widget_draw_text(ctx,
                     widget_label_display_string(wgt, label, sizeof(label)), 1);
}

float widget_label_pixel_width(struct nk_context *ctx, struct option_widget *wgt)
{
    char label[sizeof(wgt->label) + sizeof(" [*]")];
    const char *display_label = widget_label_display_string(wgt, label,
                                                            sizeof(label));
    return widget_text_width(ctx, display_label, (int)strlen(display_label));
}

static const char *widget_string_display_value(struct option_widget *wgt,
                                               char *buf, int buf_size)
{
    if (!wgt)
        return "N/A";

    if (buf && buf_size > 0)
        buf[0] = '\0';

    switch (wgt->widget_type) {
        case WIDGET_TYPE_STRING:
        {
            char *str_data = (char *)wgt->data;
            if (wgt->get_string && buf && buf_size > 0) {
                wgt->get_string(wgt, buf);
                return buf;
            }
            if (str_data && strlen(str_data))
                return str_data;
            return "N/A";
        }
        case WIDGET_TYPE_STRING_INPUT:
        {
            t_input_common *input = (t_input_common *)wgt->data;
            if (wgt->input_empty_row)
                return "Not mapped";
            if (input && buf && buf_size > 0) {
                input_common_to_string(input, buf, buf_size);
                return buf;
            }
            return "N/A";
        }
        case WIDGET_TYPE_STRING_SCAN:
        {
            t_media_scan *scan = (t_media_scan *)wgt->data;
            if (scan && scan->progress != 0 && buf && buf_size > 0) {
                const char *plabel = (scan->progress_label && scan->progress_label[0])
                                     ? scan->progress_label : "Files Scanned";
                snprintf(buf, buf_size, "%s: %d", plabel, scan->progress);
                return buf;
            }
            if (scan && scan->directory && strlen(scan->directory))
                return scan->directory;
            return "N/A";
        }
        default:
            return "N/A";
    }
}

float widget_string_pixel_width(struct nk_context *ctx, struct option_widget *wgt)
{
    char value[WIDGET_VALUE_STR_SIZE];
    const char *display_value;
    float value_w;
    float pad_w;

    if (!ctx || !wgt)
        return 0.0f;

    display_value = widget_string_display_value(wgt, value, sizeof(value));
    value_w = widget_text_width(ctx, display_value, (int)strlen(display_value));
    pad_w = 2.0f * ctx->style.window.group_padding.x
            + ctx->style.window.spacing.x + 8.0f;

    return (float)g_theme.menu_label_width_active + value_w + pad_w;
}

void widget_push_select_button_style(struct nk_context *ctx)
{
    //ctx->style.button.border = 0.0f;
    //ctx->style.button.normal = nk_style_item_color(nk_rgba(0, 0, 0, 0));  // Transparent background
    //ctx->style.button.hover = nk_style_item_color(nk_rgba(0, 0, 0, 0));   // Transparent background
    //ctx->style.button.active = nk_style_item_color(nk_rgba(0, 0, 0, 0));   // Transparent background

    nk_style_push_float(ctx, &(ctx->style.button.border), 0.0f);
    nk_style_push_style_item(ctx, &(ctx->style.button.normal), nk_style_item_color(nk_rgba(0, 0, 0, 0)));
    nk_style_push_style_item(ctx, &(ctx->style.button.hover), nk_style_item_color(nk_rgba(0, 0, 0, 0)));
    nk_style_push_style_item(ctx, &(ctx->style.button.active), nk_style_item_color(nk_rgba(0, 0, 0, 0)));
}

void widget_pop_select_button_style(struct nk_context *ctx)
{
    nk_style_pop_float(ctx);
    nk_style_pop_style_item(ctx);
    nk_style_pop_style_item(ctx);
    nk_style_pop_style_item(ctx);
}

/* Returns a pointer into str at the leftmost byte where the trailing suffix
   fits within value_w pixels at the current font.  If the whole string fits,
   returns str unchanged. */
static const char *widget_fit_trailing(struct nk_context *ctx, const char *str, float value_w)
{
    int len = (int)strlen(str);
    if (len == 0) return str;

    /* Check full string first (common case for short strings) */
    float full_w = ctx->style.font->width(ctx->style.font->userdata,
                                           ctx->style.font->height, str, len);
    if (full_w <= value_w) return str;

    /* Binary search: find smallest start where the suffix [start..end) fits */
    int lo = 0, hi = len;
    while (lo < hi) {
        int mid = (lo + hi) / 2;
        float w = ctx->style.font->width(ctx->style.font->userdata,
                                          ctx->style.font->height,
                                          str + mid, len - mid);
        if (w <= value_w)
            hi = mid;
        else
            lo = mid + 1;
    }
    return str + lo;
}

// Custom widget with left arrow, value display, and right arrow
int widget_toggle(struct nk_context *ctx, struct option_widget *wgt, int selected)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);
    int value = 0;

    if (wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, bounds)) {
        changed = 1;
    }

    // Save original button style
    //struct nk_style_toggle original_style = ctx->style.checkbox;
    //ctx->style.checkbox.cursor_normal.data.color = g_theme.foreground;
    /*
    ctx->style.checkbox.cursor_hover.data.color = g_theme.foreground;
    ctx->style.checkbox.active.data.color = g_theme.foreground;
    */

    // Disable hover color change: make hover match normal for box and cursor
    nk_style_push_style_item(ctx, &ctx->style.checkbox.hover, ctx->style.checkbox.normal);
    nk_style_push_color(ctx, &ctx->style.checkbox.cursor_hover.data.color,
                        ctx->style.checkbox.cursor_normal.data.color);

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.checkbox.border_color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.checkbox.cursor_hover.data.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.checkbox.cursor_normal.data.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.checkbox.active.data.color, g_theme.selection_active);
    }

    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);

    // Start a local group for proper layout
    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        nk_layout_row_template_begin(ctx, g_theme.row_height);
        nk_layout_row_template_push_static(ctx, g_theme.menu_label_width_active);  // Label
        nk_layout_row_template_push_dynamic(ctx);  // Toggle takes remaining space
        nk_layout_row_template_end(ctx);

        widget_custom_label_with_rom_marker(ctx, wgt);
        //nk_label(ctx, wgt->label, NK_TEXT_LEFT);

        if (wgt->data_type == WIDGET_DATA_TYPE_INT)
            value = *((int *)wgt->data);

        // Toggle switch
        int old_value = value;
        value = nk_check_label(ctx, "", value);
        if (value != old_value && selected) {
            changed = 1;
        }

        if (wgt->accept_input && selected) {
            // Allow using enter/space to toggle
            if (input_check_select_item(ctx) || input_check_toggle_item(ctx)) {
                value = !value;
                changed = 1;
            }
        }

        if (wgt->data_type == WIDGET_DATA_TYPE_INT)
            *((int *)wgt->data) = value;

        nk_group_end(ctx);
    }

    nk_style_pop_float(ctx);

    // Draw a border if this widget is selected
    if (selected) {
        widget_draw_selection(ctx, bounds);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
    }

    // Pop unconditional hover-suppression pushes
    nk_style_pop_color(ctx);
    nk_style_pop_style_item(ctx);

    if (changed && wgt->p_mask)
        *(wgt->p_mask) |= wgt->mask_value;

    return changed;
}

int widget_string(struct nk_context *ctx, struct option_widget *wgt, int selected)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);

    // Resolve the display string and compute click bounds before entering the group
    char get_str_buf[128];
    char *str_data = (char *)(wgt->data);
    const char *display_str = "N/A";

    float grp_pad_x = ctx->style.window.group_padding.x;
    float value_w = bounds.w - 2*grp_pad_x - g_theme.menu_label_width_active
                    - ctx->style.window.spacing.x;

    if (wgt->get_string) {
        wgt->get_string(wgt, get_str_buf);
        display_str = widget_fit_trailing(ctx, get_str_buf, value_w);
    } else if (str_data && strlen(str_data)) {
        display_str = widget_fit_trailing(ctx, str_data, value_w);
    }

    float str_w = widget_text_width(ctx, display_str, (int)strlen(display_str));
    struct nk_rect click_bounds = bounds;
    click_bounds.w = NK_MIN(g_theme.menu_label_width_active + str_w + 8, bounds.w);

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
    }

    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);

    // Start a local group for proper layout
    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        nk_layout_row_template_begin(ctx, g_theme.row_height);
        nk_layout_row_template_push_static(ctx, g_theme.menu_label_width_active);  // Label
        nk_layout_row_template_push_dynamic(ctx);  // Value takes remaining space
        nk_layout_row_template_end(ctx);

        widget_custom_label_with_rom_marker(ctx, wgt);
        widget_custom_label(ctx, display_str);

        if (wgt->accept_input && selected) {
            // Allow using enter/space to toggle
            if (input_check_select_item(ctx) || input_check_toggle_item(ctx)) {
                changed = 1;
            }
        }

        nk_group_end(ctx);
    }
    nk_style_pop_float(ctx);

    if (wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, click_bounds)) {
        changed = 1;
    }

    // Draw a border if this widget is selected
    if (selected) {
        widget_draw_selection(ctx, bounds);

        nk_style_pop_color(ctx);
    }

    return changed;
}

int widget_slider(struct nk_context *ctx, struct option_widget *wgt, int selected)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);
    int value = 0;

    if (wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, bounds)) {
        changed = 1;
    }

    /*
    ctx->style.slider.bar_filled = g_theme.foreground;
    ctx->style.slider.cursor_normal.data.color = g_theme.foreground;
    ctx->style.slider.cursor_hover.data.color = g_theme.foreground;
    ctx->style.slider.cursor_active.data.color = theme_get_active_color(g_theme.foreground);
    */

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.slider.bar_filled, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.slider.bar_normal, g_theme.selection_shifted);
        nk_style_push_color(ctx, &ctx->style.slider.bar_active, g_theme.selection_shifted);
        nk_style_push_color(ctx, &ctx->style.slider.bar_hover, g_theme.selection_shifted);
        nk_style_push_color(ctx, &ctx->style.slider.cursor_normal.data.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.slider.cursor_hover.data.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.slider.cursor_active.data.color, g_theme.selection_active);
    }

    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);

    // Start a local group for proper layout
    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        nk_layout_row_template_begin(ctx, g_theme.row_height);
        nk_layout_row_template_push_static(ctx, g_theme.menu_label_width_active);  // Label
        nk_layout_row_template_push_dynamic(ctx);  // Slider takes remaining space
        nk_layout_row_template_push_static(ctx, 50);  // Value display
        nk_layout_row_template_end(ctx);

        widget_custom_label_with_rom_marker(ctx, wgt);
        //nk_label(ctx, wgt->label, NK_TEXT_LEFT);

        if (wgt->data_type == WIDGET_DATA_TYPE_INT)
            value = *((int *)wgt->data);

        // Slider
        int old_value = value;
        value = nk_slide_int(ctx, wgt->min, value, wgt->max, 1);
        if (value != old_value && selected) {
            changed = 1;
        }

        if (wgt->accept_input && selected) {
            changed += input_check_value_navigation(ctx, &value, wgt->min, wgt->max);
        }

        // Value display as string
        char value_str[WIDGET_VALUE_STR_SIZE];
        if (wgt->get_string) {
            wgt->get_string(wgt, value_str);
            nk_label(ctx, value_str, NK_TEXT_CENTERED);
        } else {
            snprintf(value_str, sizeof(value_str), "%d", value);
            nk_label(ctx, value_str, NK_TEXT_CENTERED);
        }

        if (wgt->data_type == WIDGET_DATA_TYPE_INT)
            *((int *)wgt->data) = value;

        nk_group_end(ctx);
    }

    nk_style_pop_float(ctx);

    // Draw a border if this widget is selected
    if (selected) {
        widget_draw_selection(ctx, bounds);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
    }

    if (changed && wgt->p_mask)
        *(wgt->p_mask) |= wgt->mask_value;

    return changed;
}

int widget_select(struct nk_context *ctx, struct option_widget *wgt, int selected)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);
    int value = 0;

    if (wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, bounds)) {
        changed = 1;
    }

    // Save original button style
    struct nk_style_button original_style = ctx->style.button;

    // Modify button style for transparent background and no borders
    /*
    ctx->style.button.border = 0.0f;
    ctx->style.button.normal = nk_style_item_color(nk_rgba(0, 0, 0, 0));  // Transparent background
    ctx->style.button.hover = nk_style_item_color(nk_rgba(0, 0, 0, 0));   // Transparent background
    ctx->style.button.active = nk_style_item_color(nk_rgba(0, 0, 0, 0));   // Transparent background
    */

    //ctx->style.button.text_normal = g_theme.foreground;
    //ctx->style.button.text_hover = g_theme.foreground;
    //ctx->style.button.text_active = theme_get_active_color(g_theme.foreground);

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.button.text_normal, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.button.text_hover, g_theme.selection);
        nk_style_push_color(ctx, &ctx->style.button.text_active, g_theme.selection_active);
    }
    nk_style_push_float(ctx, &ctx->style.text.padding.y, 0);
    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);
    nk_style_push_float(ctx, &ctx->style.button.padding.y, 0);
    nk_style_push_float(ctx, &ctx->style.button.padding.x, 0);

    // Start a local group for proper layout
    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        nk_layout_row_template_begin(ctx, g_theme.row_height);
        nk_layout_row_template_push_static(ctx, g_theme.menu_label_width_active);  // Label
        nk_layout_row_template_push_static(ctx, g_theme.row_height); // Left button width
        //nk_layout_row_template_push_dynamic(ctx);     // Middle label takes remaining space
        nk_layout_row_template_push_static(ctx, g_theme.select_label_width_active);  // Middle Label
        nk_layout_row_template_push_static(ctx, g_theme.row_height);  // Right button width
        nk_layout_row_template_end(ctx);

        widget_custom_label_with_rom_marker(ctx, wgt);
        //nk_label(ctx, wgt->label, NK_TEXT_LEFT|NK_TEXT_ALIGN_TOP);

        if (wgt->data_type == WIDGET_DATA_TYPE_INT)
            value = *((int *)wgt->data);

        // Left arrow button (decreases value)
        if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_LEFT) && selected) {
            if (value > wgt->min) {
                value--;
            }
            else {
                value = wgt->max;
            }
            changed = 1;
        }

        // Value display as string
        char value_str[WIDGET_VALUE_STR_SIZE];
        if (wgt->get_string) {
            wgt->get_string(wgt, value_str);
            nk_label(ctx, value_str, NK_TEXT_CENTERED);
        } else {
            snprintf(value_str, sizeof(value_str), "%d", value);
            nk_label(ctx, value_str, NK_TEXT_CENTERED);
        }

        // Right arrow button (increases value)
        if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT) && selected) {
            if (value < wgt->max) {
                value++;
            }
            else {
                value = wgt->min;
            }
            changed = 1;
        }

        if (wgt->accept_input && selected) {
            changed += input_check_value_navigation (ctx, &value, wgt->min, wgt->max);
        }

        if (wgt->data_type == WIDGET_DATA_TYPE_INT)
            *((int *)wgt->data) = value;

        nk_group_end(ctx);
    }

    // Restore original button style
    ctx->style.button = original_style;

    // Draw a yellow border if this widget is selected
    if (selected) {
        widget_draw_selection(ctx, bounds);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
        nk_style_pop_color(ctx);
    }
    nk_style_pop_float(ctx);
    nk_style_pop_float(ctx);
    nk_style_pop_float(ctx);
    nk_style_pop_float(ctx);

    if (changed && wgt->p_mask)
        *(wgt->p_mask) |= wgt->mask_value;

    return changed;
}

/* A plain label whose only job is to open another menu: it has a next-menu
   target and no action task.  "Do work then return" rows carry a run_task, so
   they're excluded automatically. */
static int widget_is_child_menu(const struct option_widget *wgt)
{
    return wgt->widget_type == WIDGET_TYPE_LABEL
        && wgt->menu_next != 0
        && wgt->run_task == NULL;
}

int widget_label(struct nk_context *ctx, struct option_widget *wgt, int selected,
                 int allow_child_caret)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);
    int child_menu = allow_child_caret && widget_is_child_menu(wgt);

    if (wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, bounds)) {
        changed = 1;
    }

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
    }

    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);

    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        if (child_menu) {
            /* Label fills the left; a caret-right marker pins to the widget
               area's right edge so it lines up with the select widgets'
               right arrows (same row_height cell at the same right edge). */
            nk_layout_row_template_begin(ctx, g_theme.row_height);
            nk_layout_row_template_push_dynamic(ctx);                    /* Label  */
            nk_layout_row_template_push_static(ctx, g_theme.row_height); /* Marker */
            nk_layout_row_template_end(ctx);

            widget_custom_label_with_rom_marker(ctx, wgt);
            widget_custom_label(ctx, "\xef\x83\x9a");  /* FA caret-right (U+F0DA) */
        } else {
            nk_layout_row_dynamic(ctx, g_theme.row_height, 1);
            widget_custom_label_with_rom_marker(ctx, wgt);
        }

        if (wgt->accept_input && selected && (input_check_select_item(ctx) || input_check_toggle_item(ctx))) {
            changed = 1;
        }

        nk_group_end(ctx);
    }

    nk_style_pop_float(ctx);

    if (selected) {
        widget_draw_selection(ctx, bounds);
        nk_style_pop_color(ctx);
    }

    return changed;
}

/* Helper function to convert t_input_common to human-readable string */
void input_common_to_string(t_input_common *input, char *buf, int buf_size)
{
    t_input_common display_input;
    char tmp[64];

    if (!input || !buf || buf_size <= 0) {
        if (buf && buf_size > 0)
            buf[0] = '\0';
        return;
    }

    if (!input->enable) {
        strncpy(buf, "Disabled", buf_size - 1);
        buf[buf_size - 1] = '\0';
        return;
    }

    buf[0] = '\0';

    /* Combos are stored as action, then modifier. Display the held modifier
       first so labels read like the actual chord: Back + Start. */
    if (input->combo) {
        display_input = *input;
        display_input.device[0] = input->device[1];
        display_input.device[1] = input->device[0];
        display_input.device_num[0] = input->device_num[1];
        display_input.device_num[1] = input->device_num[0];
        display_input.part_type[0] = input->part_type[1];
        display_input.part_type[1] = input->part_type[0];
        display_input.part_num[0] = input->part_num[1];
        display_input.part_num[1] = input->part_num[0];
        display_input.direction[0] = input->direction[1];
        display_input.direction[1] = input->direction[0];
        input = &display_input;
    }

    /* Process up to 2 inputs (regular and combo display side) */
    for (int j = 0; j < 2; ++j) {
        if (j == 1 && !input->combo)
            break;

        if (j == 1)
            strncat(buf, " + ", buf_size - strlen(buf) - 1);

        /* Skip device prefix if combo uses the same device */
        int same_device = (j == 1 && input->device[0] == input->device[1]
                           && input->device_num[0] == input->device_num[1]);

        /* Device type and number */
        switch (input->device[j]) {
            case DEV_KEYBOARD:
                if (!same_device)
                    strncat(buf, "Key | ", buf_size - strlen(buf) - 1);
                /* Check for modifier key */
                pc_interpret_mod_key_value(input->device_num[j], tmp);
                if (strcmp(tmp, "0") != 0) {
                    /* Capitalize first letter of modifier */
                    if (tmp[0] >= 'a' && tmp[0] <= 'z')
                        tmp[0] = tmp[0] - 'a' + 'A';
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                    strncat(buf, "-", buf_size - strlen(buf) - 1);
                }
                /* Get key name and capitalize first letter */
                pc_interpret_key_value(input->part_num[j], tmp);
                if (tmp[0] >= 'a' && tmp[0] <= 'z')
                    tmp[0] = tmp[0] - 'a' + 'A';
                strncat(buf, tmp, buf_size - strlen(buf) - 1);
                break;

            case DEV_JOYSTICK:
                if (!same_device) {
                    snprintf(tmp, sizeof(tmp), "Joystick:%d | ", input->device_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                }

                /* Part type */
                if (input->part_type[j] == PART_TYPE_AXIS) {
                    snprintf(tmp, sizeof(tmp), "Axis:%d", input->part_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                    /* Direction for axis */
                    switch (input->direction[j]) {
                        case DIR_LEFT:  strncat(buf, ":Left", buf_size - strlen(buf) - 1); break;
                        case DIR_RIGHT: strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                        case DIR_UP:    strncat(buf, ":Up", buf_size - strlen(buf) - 1); break;
                        case DIR_DOWN:  strncat(buf, ":Down", buf_size - strlen(buf) - 1); break;
                        case DIR_MINUS: strncat(buf, ":-", buf_size - strlen(buf) - 1); break;
                        case DIR_PLUS:  strncat(buf, ":+", buf_size - strlen(buf) - 1); break;
                        default: break;
                    }
                } else if (input->part_type[j] == PART_TYPE_BUTTON) {
                    snprintf(tmp, sizeof(tmp), "Button:%d", input->part_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                } else if (input->part_type[j] == PART_TYPE_HAT) {
                    snprintf(tmp, sizeof(tmp), "Hat:%d", input->part_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                    switch (input->direction[j]) {
                        case DIR_LEFT:  strncat(buf, ":Left", buf_size - strlen(buf) - 1); break;
                        case DIR_RIGHT: strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                        case DIR_UP:    strncat(buf, ":Up", buf_size - strlen(buf) - 1); break;
                        case DIR_DOWN:  strncat(buf, ":Down", buf_size - strlen(buf) - 1); break;
                        default: break;
                    }
                } else if (input->part_type[j] == PART_TYPE_BALL) {
                    snprintf(tmp, sizeof(tmp), "Ball:%d", input->part_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                    switch (input->direction[j]) {
                        case DIR_LEFT:  strncat(buf, ":Left",  buf_size - strlen(buf) - 1); break;
                        case DIR_RIGHT: strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                        case DIR_UP:    strncat(buf, ":Up",    buf_size - strlen(buf) - 1); break;
                        case DIR_DOWN:  strncat(buf, ":Down",  buf_size - strlen(buf) - 1); break;
                        default: break;
                    }
                }
                break;

            case DEV_MOUSE:
                if (!same_device) {
                    snprintf(tmp, sizeof(tmp), "Mouse:%d | ", input->device_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                }

                if (input->part_type[j] == PART_TYPE_AXIS) {
                    snprintf(tmp, sizeof(tmp), "Axis:%d", input->part_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                    switch (input->direction[j]) {
                        case DIR_LEFT:  strncat(buf, ":Left", buf_size - strlen(buf) - 1); break;
                        case DIR_RIGHT: strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                        case DIR_UP:    strncat(buf, ":Up", buf_size - strlen(buf) - 1); break;
                        case DIR_DOWN:  strncat(buf, ":Down", buf_size - strlen(buf) - 1); break;
                        case DIR_MINUS: strncat(buf, ":-", buf_size - strlen(buf) - 1); break;
                        case DIR_PLUS:  strncat(buf, ":+", buf_size - strlen(buf) - 1); break;
                        default: break;
                    }
                } else if (input->part_type[j] == PART_TYPE_BUTTON) {
                    snprintf(tmp, sizeof(tmp), "Button:%d", input->part_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                } else if (input->part_type[j] == PART_TYPE_BALL) {
                    strncat(buf, "Motion", buf_size - strlen(buf) - 1);
                    switch (input->direction[j]) {
                        case DIR_LEFT:  strncat(buf, ":Left",  buf_size - strlen(buf) - 1); break;
                        case DIR_RIGHT: strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                        case DIR_UP:    strncat(buf, ":Up",    buf_size - strlen(buf) - 1); break;
                        case DIR_DOWN:  strncat(buf, ":Down",  buf_size - strlen(buf) - 1); break;
                        default: break;
                    }
                }
                break;

            case DEV_CONTROLLER:
                if (!same_device) {
                    snprintf(tmp, sizeof(tmp), "GamePad:%d | ", input->device_num[j]);
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                }

                if (input->part_type[j] == PART_TYPE_AXIS) {
                    pc_interpret_controller_axis_value(input->part_num[j], tmp);
                    /* Stick axes: leftx/lefty/rightx/righty -> Left/Right:Stick:direction */
                    if (strcmp(tmp, "leftx") == 0) {
                        strncat(buf, "Left:Stick", buf_size - strlen(buf) - 1);
                        switch (input->direction[j]) {
                            case DIR_MINUS: strncat(buf, ":Left", buf_size - strlen(buf) - 1); break;
                            case DIR_PLUS:  strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                            default: break;
                        }
                    } else if (strcmp(tmp, "lefty") == 0) {
                        strncat(buf, "Left:Stick", buf_size - strlen(buf) - 1);
                        switch (input->direction[j]) {
                            case DIR_MINUS: strncat(buf, ":Up", buf_size - strlen(buf) - 1); break;
                            case DIR_PLUS:  strncat(buf, ":Down", buf_size - strlen(buf) - 1); break;
                            default: break;
                        }
                    } else if (strcmp(tmp, "rightx") == 0) {
                        strncat(buf, "Right:Stick", buf_size - strlen(buf) - 1);
                        switch (input->direction[j]) {
                            case DIR_MINUS: strncat(buf, ":Left", buf_size - strlen(buf) - 1); break;
                            case DIR_PLUS:  strncat(buf, ":Right", buf_size - strlen(buf) - 1); break;
                            default: break;
                        }
                    } else if (strcmp(tmp, "righty") == 0) {
                        strncat(buf, "Right:Stick", buf_size - strlen(buf) - 1);
                        switch (input->direction[j]) {
                            case DIR_MINUS: strncat(buf, ":Up", buf_size - strlen(buf) - 1); break;
                            case DIR_PLUS:  strncat(buf, ":Down", buf_size - strlen(buf) - 1); break;
                            default: break;
                        }
                    } else {
                        /* Triggers and other axes: just capitalize */
                        if (tmp[0] >= 'a' && tmp[0] <= 'z')
                            tmp[0] = tmp[0] - 'a' + 'A';
                        strncat(buf, tmp, buf_size - strlen(buf) - 1);
                        switch (input->direction[j]) {
                            case DIR_MINUS: strncat(buf, ":-", buf_size - strlen(buf) - 1); break;
                            case DIR_PLUS:  strncat(buf, ":+", buf_size - strlen(buf) - 1); break;
                            default: break;
                        }
                    }
                } else if (input->part_type[j] == PART_TYPE_BUTTON) {
                    pc_interpret_controller_button_value(input->part_num[j], tmp);
                    /* Transform dpad names: dpad_up -> D-Pad:Up */
                    if (strncmp(tmp, "dpad_", 5) == 0) {
                        char dir[32];
                        strncpy(dir, tmp + 5, sizeof(dir) - 1);
                        dir[sizeof(dir) - 1] = '\0';
                        if (dir[0] >= 'a' && dir[0] <= 'z')
                            dir[0] = dir[0] - 'a' + 'A';
                        snprintf(tmp, sizeof(tmp), "D-Pad:%s", dir);
                    } else {
                        if (tmp[0] >= 'a' && tmp[0] <= 'z')
                            tmp[0] = tmp[0] - 'a' + 'A';
                    }
                    strncat(buf, tmp, buf_size - strlen(buf) - 1);
                }
                break;

            default:
                strncat(buf, "Unknown", buf_size - strlen(buf) - 1);
                break;
        }
    }

    buf[buf_size - 1] = '\0';
}

int widget_string_input(struct nk_context *ctx, struct option_widget *wgt, int selected)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);
    char display_str[128];
    const char *show_str = "N/A";
    t_input_common *input = (t_input_common *)wgt->data;
    float grp_pad_x = ctx->style.window.group_padding.x;
    float value_w = bounds.w - 2*grp_pad_x - g_theme.menu_label_width_active
                    - ctx->style.window.spacing.x;

    if (wgt->input_empty_row) {
        show_str = "Not mapped";
    } else if (input) {
        input_common_to_string(input, display_str, sizeof(display_str));
        show_str = widget_fit_trailing(ctx, display_str, value_w);
    }

    float str_w = widget_text_width(ctx, show_str, (int)strlen(show_str));
    float extra_w = widget_text_width(ctx, "XXXXXXXXXX", 10);
    struct nk_rect click_bounds = bounds;
    click_bounds.w = NK_MIN(g_theme.menu_label_width_active + str_w + extra_w + 8,
                            bounds.w);

    if (wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, click_bounds)) {
        changed = 1;
    }

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
    } else if (wgt->show_as_disabled) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.menu_unselected);
    }

    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);

    /* Start a local group for proper layout */
    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        nk_layout_row_template_begin(ctx, g_theme.row_height);
        nk_layout_row_template_push_static(ctx, g_theme.menu_label_width_active);  /* Label */
        nk_layout_row_template_push_dynamic(ctx);  /* Value takes remaining space */
        nk_layout_row_template_end(ctx);

        widget_custom_label_with_rom_marker(ctx, wgt);

        widget_custom_label(ctx, show_str);

        if (wgt->accept_input && selected) {
            /* Allow using enter/space to activate input editing */
            if (input_check_select_item(ctx) || input_check_toggle_item(ctx)) {
                changed = 1;
            }
        }

        nk_group_end(ctx);
    }
    nk_style_pop_float(ctx);

    /* Draw a border if this widget is selected */
    if (selected) {
        widget_draw_selection(ctx, bounds);
        nk_style_pop_color(ctx);
    } else if (wgt->show_as_disabled) {
        nk_style_pop_color(ctx);
    }

    return changed;
}

int widget_string_scan(struct nk_context *ctx, struct option_widget *wgt, int selected)
{
    int changed = 0;
    struct nk_rect bounds = nk_widget_bounds(ctx);
    t_media_scan *scan = (t_media_scan *)wgt->data;
    int scanning = (scan && scan->progress != 0);
    char value_buf[WIDGET_VALUE_STR_SIZE];

    struct nk_rect click_bounds = bounds;
    if (!scanning) {
        float grp_pad_x = ctx->style.window.group_padding.x;
        float value_w = bounds.w - 2*grp_pad_x - g_theme.menu_label_width_active
                        - ctx->style.window.spacing.x;
        char *str_data = scan ? scan->directory : NULL;
        const char *display_str = (str_data && strlen(str_data))
                                  ? widget_fit_trailing(ctx, str_data, value_w)
                                  : "N/A";
        float str_w = widget_text_width(ctx, display_str, (int)strlen(display_str));
        click_bounds.w = NK_MIN(g_theme.menu_label_width_active + str_w + 8,
                                bounds.w);
    }

    if (selected) {
        nk_style_push_color(ctx, &ctx->style.text.color, g_theme.selection);
    }

    nk_style_push_float(ctx, &ctx->style.window.spacing.y, 0);

    /* Start a local group for proper layout */
    if (nk_group_begin(ctx, wgt->label, NK_WINDOW_NO_SCROLLBAR)) {
        nk_layout_row_template_begin(ctx, g_theme.row_height);
        nk_layout_row_template_push_static(ctx, g_theme.menu_label_width_active);  /* Label */
        nk_layout_row_template_push_dynamic(ctx);  /* Value takes remaining space */
        nk_layout_row_template_end(ctx);

        widget_custom_label_with_rom_marker(ctx, wgt);

        /* Show progress count if scanning, otherwise show directory */
        if (scanning) {
            const char *plabel = (scan->progress_label && scan->progress_label[0])
                                 ? scan->progress_label : "Files Scanned";
            snprintf(value_buf, sizeof(value_buf), "%s: %d", plabel, scan->progress);
            widget_custom_label(ctx, value_buf);
        } else {
            float value_w = nk_window_get_content_region_size(ctx).x
                            - g_theme.menu_label_width_active
                            - ctx->style.window.spacing.x;
            char *str_data = scan ? scan->directory : NULL;
            if (str_data && strlen(str_data))
                widget_custom_label(ctx, widget_fit_trailing(ctx, str_data, value_w));
            else
                widget_custom_label(ctx, "N/A");
        }

        /* Only check for keyboard selection if not scanning */
        if (!scanning && wgt->accept_input && selected) {
            if (input_check_select_item(ctx) || input_check_toggle_item(ctx)) {
                changed = 1;
            }
        }

        nk_group_end(ctx);
    }
    nk_style_pop_float(ctx);

    /* Only allow input if not scanning */
    if (!scanning && wgt->accept_input && nk_input_mouse_clicked(&ctx->input, NK_BUTTON_LEFT, click_bounds)) {
        changed = 1;
    }

    /* Draw a border if this widget is selected */
    if (selected) {
        widget_draw_selection(ctx, bounds);
        nk_style_pop_color(ctx);
    }

    return changed;
}
