#include <sys/types.h>
#include <sys/stat.h>
#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 "message.h"
#include "theme.h"
#include "input.h"

#define ICON_WARNING "\xef\x81\xb1"  
#define ICON_ERROR   "\xef\x85\xb1"
#define ICON_CRASH   "\xef\x9c\x94"  

extern struct theme g_theme;
static struct modal_config *p_modal_cfg = 0;

static int g_focused_button = 2;  

static int message_next_wrapped_line(const struct nk_user_font *font,
                                     const char **cursor,
                                     const char *end,
                                     float max_w,
                                     const char **line_start,
                                     int *line_len)
{
    const char *p = *cursor;
    int segment_len = 0;
    int fitting;
    int len = 0;
    int last_break = 0;

    /* Do NOT strip leading spaces/tabs here. */
    if (p >= end) {
        *cursor = p;
        *line_start = p;
        *line_len = 0;
        return 0;
    }

    if (*p == '\n') {
        *cursor = p + 1;
        *line_start = p;
        *line_len = 0;
        return 1;
    }

    while (p + segment_len < end && p[segment_len] != '\n')
        segment_len++;

    /* Accumulate per-glyph advances so each step is O(1) instead of
     * re-measuring the whole growing prefix (which made this O(N^2)). */
    float acc_w = 0.0f;
    while (len < segment_len) {
        nk_rune unicode = 0;
        int glyph_len = nk_utf_decode(p + len, &unicode, segment_len - len);
        float gw;

        if (glyph_len <= 0)
            glyph_len = 1;

        gw = font->width(font->userdata, font->height, p + len, glyph_len);
        if (acc_w + gw > max_w) {
            if (len == 0)
                len += glyph_len;
            else if (last_break > 0)
                len = last_break;
            break;
        }

        acc_w += gw;
        len += glyph_len;
        if (unicode == ' ' || unicode == '\t')
            last_break = len;
    }
    fitting = len > 0 ? len : segment_len;

    *line_start = p;
    *line_len = fitting;
    while (*line_len > 0 &&
           ((*line_start)[*line_len - 1] == ' ' ||
            (*line_start)[*line_len - 1] == '\t'))
        (*line_len)--;

    p += fitting;
    while (p < end && (*p == ' ' || *p == '\t'))
        p++;
    if (p < end && *p == '\n')
        p++;

    *cursor = p;
    return 1;
}

static int message_count_wrapped_lines(const struct nk_user_font *font,
                                       const char *msg,
                                       float max_w)
{
    const char *cursor = msg;
    const char *end = msg + strlen(msg);
    const char *line_start;
    int line_len;
    int lines = 0;

    while (message_next_wrapped_line(font, &cursor, end, max_w,
                                     &line_start, &line_len))
        lines++;

    return lines ? lines : 1;
}

static void message_draw_wrapped_text(struct nk_command_buffer *canvas,
                                      const struct nk_user_font *font,
                                      struct nk_rect bounds,
                                      const char *msg,
                                      struct nk_color color)
{
    const char *cursor = msg;
    const char *end = msg + strlen(msg);
    const char *line_start;
    int line_len;
    float line_h = font->height + 4.0f;
    float y = bounds.y;
    struct nk_color transparent = nk_rgba(0, 0, 0, 0);

    while (y + font->height <= bounds.y + bounds.h &&
           message_next_wrapped_line(font, &cursor, end, bounds.w,
                                     &line_start, &line_len)) {
        if (line_len > 0) {
            nk_draw_text(canvas,
                         nk_rect(bounds.x, y, bounds.w, font->height),
                         line_start, line_len, font, transparent, color);
        }
        y += line_h;
    }
}

void message_init(struct modal_config *p_cfg)
{
    p_modal_cfg = p_cfg;
    g_focused_button = 1;  /* default focus to accept/OK button */
}

void message_init_modal(struct modal_config *p_cfg, int msg_type,
        const char *title, const char *msg)
{
    if (!p_cfg)
        return;

    memset(p_cfg, 0, sizeof(struct modal_config));
    p_cfg->modal_type = MODAL_MESSAGE;
    p_cfg->modal_active = 1;
    p_cfg->msg_type = msg_type;
    snprintf(p_cfg->title, sizeof(p_cfg->title), "%s", title ? title : "");
    strcpy(p_cfg->cancel_btn_label, "OK");
    snprintf(p_cfg->msg, sizeof(p_cfg->msg), "%s", msg ? msg : "");
    message_init(p_cfg);
}

void message_show(struct nk_context *ctx,
                          int screen_w, int screen_h)
{
    int is_text_input = (p_modal_cfg->modal_type == MODAL_TEXT_INPUT);
    int is_wait = (p_modal_cfg->modal_type == MODAL_WAIT);
    char *button_labels[3] = { p_modal_cfg->cancel_btn_label, p_modal_cfg->accept_btn_label};

    ctx->style.font = &(g_theme.font_normal->handle);
    theme_update_dimensions(ctx);

    /* ---------------------------------------------------------
     * 1) Full-screen input blocker + dim background
     * --------------------------------------------------------- */
    {
        struct nk_style_window *s = &ctx->style.window;
        struct nk_color old_bg = s->background;

        s->background = g_theme.modal_overlay;

        nk_begin(ctx, "__modal_bg",
            nk_rect(0, 0, screen_w, screen_h),
            NK_WINDOW_NO_INPUT |
            NK_WINDOW_NO_SCROLLBAR |
            NK_WINDOW_BACKGROUND);

        nk_end(ctx);

        s->background = old_bg;
    }

    /* ---------------------------------------------------------
     * 2) Modal window — height adapts to message length
     * --------------------------------------------------------- */
    /* Measure message text to determine how many wrapped lines it needs. */
    int msg_chars = (int)strlen(p_modal_cfg->msg);
    int modal_w = msg_chars > 140 ? 560 : 420;
    if (modal_w > screen_w - 40)
        modal_w = screen_w - 40;
    if (modal_w < 320)
        modal_w = 320;

    float font_h = ctx->style.font->height;
    /* Left buffer column (~5 chars) so the message text isn't flush against
     * the window border.  Subtract it from the wrap-width estimate as well so
     * the line count used for the modal height matches the narrower draw area. */
    const struct nk_user_font *msg_font = ctx->style.font;
    float left_pad = msg_font->width(msg_font->userdata, msg_font->height,
                                     "     ", 5);
    float approx_label_w = (float)modal_w - 105.0f - left_pad;

    /* Wrap measurement only changes when the message, wrap width, or font
     * changes — cache it instead of re-walking the string every frame. */
    static const char *cached_msg = NULL;
    static float cached_label_w = 0.0f;
    static float cached_font_h = 0.0f;
    static int cached_num_lines = 1;
    if (p_modal_cfg->msg != cached_msg ||
        approx_label_w != cached_label_w ||
        font_h != cached_font_h) {
        cached_msg = p_modal_cfg->msg;
        cached_label_w = approx_label_w;
        cached_font_h = font_h;
        cached_num_lines = message_count_wrapped_lines(ctx->style.font,
                                                       p_modal_cfg->msg,
                                                       approx_label_w);
    }
    int num_lines = cached_num_lines;
    if (num_lines < 1) num_lines = 1;
    float msg_row_h = (float)num_lines * (font_h + 4.0f) + 10.0f;
    if (msg_row_h < (float)g_theme.row_height_double)
        msg_row_h = (float)g_theme.row_height_double;

    int modal_h = is_text_input ? 290 : 120 + (int)msg_row_h;
    int max_modal_h = screen_h - 40;
    if (max_modal_h < 160)
        max_modal_h = screen_h;
    if (modal_h > max_modal_h) {
        modal_h = max_modal_h;
        msg_row_h = (float)(modal_h - 120);
        if (msg_row_h < (float)g_theme.row_height_double)
            msg_row_h = (float)g_theme.row_height_double;
    }

    struct nk_rect modal =
        nk_rect((screen_w - modal_w) / 2,
                (screen_h - modal_h) / 2,
                modal_w, modal_h);

    if (nk_begin(ctx, p_modal_cfg->title,
        modal,
        NK_WINDOW_BORDER |
        NK_WINDOW_TITLE |
        NK_WINDOW_MOVABLE |
        NK_WINDOW_NO_SCROLLBAR))
    {
        /* Modal: keep it out of NK_WINDOW_ROM so the mouse works even when the
           modal was opened by a mouse click.  See filebrowser_show() for the
           full explanation of the stuck-ROM interaction. */
        ctx->current->flags &= ~(nk_flags)NK_WINDOW_ROM;
        ctx->current->layout->flags &= ~(nk_flags)NK_WINDOW_ROM;

        struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
        /* Only use enter-to-confirm when not in text-edit mode (avoids swallowing
           the Enter key that the user may intend to confirm the edit field). */
        int enter_hit = is_text_input ? 0 : input_check_select_item(ctx);

        nk_window_set_focus(ctx, p_modal_cfg->title);

        /* NOTE: use nk_spacer() (not nk_spacing(ctx,1)) to fill a single-column
         * row. nk_spacing(ctx,1) on a 1-column row computes rows=(0+1)/1=1 and
         * allocates an *extra* row, double-advancing the layout cursor by the
         * row height — which pushed the buttons off the bottom of the window. */
        nk_layout_row_dynamic(ctx, 3, 1);
        nk_spacer(ctx);

        /* Message / prompt label */
        struct nk_vec2 content_size = nk_window_get_content_region_size(ctx);
        /* Rows below the message: top spacer(3), spacer(8), button row(32),
         * plus a bottom margin. Nuklear inserts window.spacing.y *between*
         * every row, and those gaps must be reserved too or the button row
         * spills off the bottom of the window. */
        float row_spacing = ctx->style.window.spacing.y;
        float reserved_h = 3.0f + 8.0f + 32.0f + 12.0f;
        reserved_h += row_spacing * 4.0f;  /* gaps around the 4 rows */
        if (is_text_input)
            reserved_h += (float)g_theme.row_height_double + 8.0f + row_spacing;
        if (content_size.y > reserved_h &&
            msg_row_h > content_size.y - reserved_h)
            msg_row_h = content_size.y - reserved_h;
        if (msg_row_h < (float)g_theme.row_height_double)
            msg_row_h = (float)g_theme.row_height_double;

        const char *icon = NULL;
        struct nk_color icon_color = ctx->style.text.color;
        switch (p_modal_cfg->msg_type) {
            case KAT_MSG_TYPE_WARN:  icon = ICON_WARNING; icon_color = g_theme.msg_warn_icon;  break;
            case KAT_MSG_TYPE_ERROR: icon = ICON_ERROR;   icon_color = g_theme.msg_error_icon; break;
            case KAT_MSG_TYPE_CRASH: icon = ICON_CRASH;                                        break;
            default: break;
        }

        nk_layout_row_dynamic(ctx, msg_row_h, 1);
        struct nk_rect msg_bounds = nk_widget_bounds(ctx);
        nk_spacer(ctx);

        msg_bounds.x += 4.0f + left_pad;
        msg_bounds.y += 4.0f;
        msg_bounds.w -= 8.0f + left_pad;
        msg_bounds.h -= 8.0f;

        if (icon) {
            struct nk_rect icon_bounds = nk_rect(msg_bounds.x, msg_bounds.y, 30.0f, font_h);
            nk_draw_text(canvas, icon_bounds, icon, (int)strlen(icon),
                         ctx->style.font, nk_rgba(0, 0, 0, 0), icon_color);
            msg_bounds.x += 40.0f;
            msg_bounds.w -= 40.0f;
        }

        message_draw_wrapped_text(canvas, ctx->style.font, msg_bounds,
                                  p_modal_cfg->msg, ctx->style.text.color);

        /* Text edit field for MODAL_TEXT_INPUT */
        if (is_text_input) {
            nk_layout_row_dynamic(ctx, g_theme.row_height_double, 1);
            nk_edit_string(ctx, NK_EDIT_FIELD | NK_EDIT_AUTO_SELECT,
                           p_modal_cfg->edit_buf, &p_modal_cfg->edit_len,
                           (int)sizeof(p_modal_cfg->edit_buf) - 1,
                           nk_filter_default);
        }

        /* MODAL_WAIT is a buttonless splash (e.g. first-run "please wait");
         * it is raised and dismissed in code, so skip all button handling. */
        if (is_wait) {
            nk_end(ctx);
            return;
        }

        int num_buttons = strlen(button_labels[1]) ? 2 : 1;
        if (g_focused_button >= num_buttons)
            g_focused_button = num_buttons - 1;

        if (!is_text_input)
            input_check_horizontal_navigation(ctx, &g_focused_button, num_buttons);

        nk_layout_row_dynamic(ctx, 8, 1);
        nk_spacer(ctx);

        /* Buttons — real nk widgets in a centered flow row. The message
         * height is clamped above so this row always stays inside the
         * window instead of flowing off the bottom. */
        nk_layout_row_dynamic(ctx, 32, num_buttons + 2);
        nk_spacing(ctx, 1);

        for (int i = 0; i < num_buttons; ++i) {

            struct nk_rect bounds = nk_widget_bounds(ctx);

            if (g_focused_button == i)
                theme_push_selected_button_color(ctx);

            if (nk_button_label(ctx, button_labels[i]) ||
                (g_focused_button == i && enter_hit)) {
                switch(i) {
                    case 0: // Cancel
                        p_modal_cfg->modal_active = 0;
                        p_modal_cfg->result = 0;
                        break;

                    case 1: // OK
                        if (is_text_input)
                            p_modal_cfg->edit_buf[p_modal_cfg->edit_len] = '\0';
                        p_modal_cfg->result = 1;
                        p_modal_cfg->modal_active = 0;
                        break;
                }
            }

            if (g_focused_button == i) {
                nk_stroke_rect(canvas, bounds, 4.0f, 2.0f, g_theme.selection);
                theme_pop_selected_button_color(ctx);
            }

        } // end for buttons

        nk_spacing(ctx, 1);
    }
    nk_end(ctx);
}
