/*
    * widgets.hpp
    * Montauk Toolkit immediate-mode widgets
    * Copyright (c) 2026 Daniel Hammer
*/

#pragma once

#include <Api/Syscall.hpp>

#include "gui/canvas.hpp"
#include "gui/clipboard.hpp"
#include "gui/font.hpp"
#include "gui/mtk/theme.hpp"

namespace gui::mtk {

enum ButtonVariant : uint8_t {
    BUTTON_PRIMARY = 0,
    BUTTON_SECONDARY,
    BUTTON_DANGER,
    BUTTON_GHOST,
    BUTTON_TONAL,
};

struct WidgetState {
    bool active;
    bool hovered;
    bool enabled;
};

struct ContextMenuState {
    bool open;
    int x;
    int y;
    int hover;
};

struct ContextMenuItem {
    const char* label;
    int id;
    bool enabled;
};

enum ContextMenuResult : int {
    CONTEXT_MENU_NONE      = -1,
    CONTEXT_MENU_CONSUMED  = -2,
    CONTEXT_MENU_DISMISSED = -3,
};

static constexpr int CONTEXT_MENU_DEFAULT_W = 140;
static constexpr int CONTEXT_MENU_ITEM_H = 24;
static constexpr int CONTEXT_MENU_PAD_Y = 4;

struct TextInputState {
    int cursor;
    int selection_anchor;
    bool dragging;
    ContextMenuState context;
};

using TextInputCharFilter = bool (*)(char ch, void* userdata);

enum TextInputResult : uint8_t {
    TEXT_INPUT_NONE     = 0,
    TEXT_INPUT_CONSUMED = 1,
    TEXT_INPUT_CHANGED  = 2,
};

enum TextInputContextItem : int {
    TEXT_INPUT_CTX_CUT = 0,
    TEXT_INPUT_CTX_COPY,
    TEXT_INPUT_CTX_PASTE,
    TEXT_INPUT_CTX_DELETE,
    TEXT_INPUT_CTX_COUNT,
};

inline WidgetState widget_state(bool active = false, bool hovered = false, bool enabled = true) {
    return {active, hovered, enabled};
}

struct ButtonColors {
    Color bg;
    Color border;
    Color fg;
};

inline void context_menu_reset(ContextMenuState& state) {
    state.open = false;
    state.x = 0;
    state.y = 0;
    state.hover = -1;
}

inline void context_menu_open(ContextMenuState& state, int x, int y) {
    state.open = true;
    state.x = x;
    state.y = y;
    state.hover = -1;
}

inline void context_menu_close(ContextMenuState& state) {
    state.open = false;
    state.hover = -1;
}

inline int context_menu_height(int item_count,
                               int item_h = CONTEXT_MENU_ITEM_H) {
    return gui_max(item_count, 0) * item_h + CONTEXT_MENU_PAD_Y * 2;
}

inline Rect context_menu_rect(const ContextMenuState& state,
                              int item_count,
                              int view_w,
                              int view_h,
                              int menu_w = CONTEXT_MENU_DEFAULT_W,
                              int item_h = CONTEXT_MENU_ITEM_H) {
    int menu_h = context_menu_height(item_count, item_h);
    int x = state.x;
    int y = state.y;
    if (x + menu_w > view_w) x = view_w - menu_w;
    if (y + menu_h > view_h) y = view_h - menu_h;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    return {x, y, menu_w, menu_h};
}

inline int context_menu_hit_item(const ContextMenuState& state,
                                 int item_count,
                                 int mx,
                                 int my,
                                 int view_w,
                                 int view_h,
                                 int menu_w = CONTEXT_MENU_DEFAULT_W,
                                 int item_h = CONTEXT_MENU_ITEM_H) {
    if (!state.open || item_count <= 0) return -1;
    Rect menu = context_menu_rect(state, item_count, view_w, view_h,
                                  menu_w, item_h);
    Rect items = {menu.x + 2, menu.y + CONTEXT_MENU_PAD_Y,
                  menu.w - 4, item_count * item_h};
    if (!items.contains(mx, my)) return -1;
    return (my - items.y) / item_h;
}

inline int context_menu_handle_mouse(ContextMenuState& state,
                                     const ContextMenuItem* items,
                                     int item_count,
                                     int mx,
                                     int my,
                                     uint8_t buttons,
                                     uint8_t prev_buttons,
                                     int view_w,
                                     int view_h,
                                     int menu_w = CONTEXT_MENU_DEFAULT_W,
                                     int item_h = CONTEXT_MENU_ITEM_H) {
    if (!state.open) return CONTEXT_MENU_NONE;

    bool left_pressed = (buttons & 1) && !(prev_buttons & 1);
    bool right_pressed = (buttons & 2) && !(prev_buttons & 2);
    state.hover = context_menu_hit_item(state, item_count, mx, my,
                                        view_w, view_h, menu_w, item_h);

    if (left_pressed) {
        int hover = state.hover;
        context_menu_close(state);
        if (hover >= 0 && hover < item_count && items && items[hover].enabled)
            return items[hover].id;
        return CONTEXT_MENU_DISMISSED;
    }

    if (right_pressed) {
        context_menu_close(state);
        return CONTEXT_MENU_DISMISSED;
    }

    return CONTEXT_MENU_CONSUMED;
}

inline int context_menu_label_height(TrueTypeFont* font = nullptr,
                                     int font_size = 0) {
    if (font && font->valid && font_size > 0)
        return font->get_line_height(font_size);
    return system_font_height();
}

inline void context_menu_draw_label(Canvas& c,
                                    int x,
                                    int y,
                                    const char* label,
                                    Color color,
                                    TrueTypeFont* font = nullptr,
                                    int font_size = 0) {
    if (font && font->valid && font_size > 0) {
        font->draw_to_buffer(c.pixels, c.w, c.h, x, y, label, color, font_size);
        return;
    }
    c.text(x, y, label, color);
}

inline void draw_context_menu(Canvas& c,
                              const ContextMenuState& state,
                              const ContextMenuItem* items,
                              int item_count,
                              const Theme& theme,
                              int menu_w = CONTEXT_MENU_DEFAULT_W,
                              int item_h = CONTEXT_MENU_ITEM_H,
                              TrueTypeFont* font = nullptr,
                              int font_size = 0) {
    if (!state.open || !items || item_count <= 0) return;

    Rect menu = context_menu_rect(state, item_count, c.w, c.h,
                                  menu_w, item_h);
    c.fill_rect(menu.x + 2, menu.y + 2, menu.w, menu.h,
                Color::from_rgb(0x80, 0x80, 0x80));
    c.fill_rounded_rect(menu.x, menu.y, menu.w, menu.h, 4,
                        colors::MENU_BG);
    c.rect(menu.x, menu.y, menu.w, menu.h, theme.border);

    int fh = context_menu_label_height(font, font_size);
    for (int i = 0; i < item_count; i++) {
        Rect item = {menu.x + 2, menu.y + CONTEXT_MENU_PAD_Y + i * item_h,
                     menu.w - 4, item_h};
        if (state.hover == i)
            c.fill_rect(item.x, item.y, item.w, item.h, colors::MENU_HOVER);

        Color label_color = items[i].enabled ? theme.text : theme.text_muted;
        context_menu_draw_label(c, menu.x + 12, item.y + (item.h - fh) / 2,
                                items[i].label, label_color, font, font_size);
    }
}

inline int text_len(const char* text) {
    int len = 0;
    while (text && text[len]) len++;
    return len;
}

inline int text_input_len(const char* text, int cap = 0) {
    int len = 0;
    int limit = cap > 0 ? cap - 1 : 0x7FFFFFFF;
    while (text && text[len] && len < limit) len++;
    return len;
}

inline int text_input_clamp_cursor(int cursor, int len) {
    return gui_clamp(cursor, 0, gui_max(len, 0));
}

inline void text_input_reset(TextInputState& state, int cursor = 0) {
    state.cursor = gui_max(cursor, 0);
    state.selection_anchor = state.cursor;
    state.dragging = false;
    context_menu_reset(state.context);
}

inline void text_input_set_cursor(TextInputState& state, int len, int cursor, bool selecting = false) {
    state.cursor = text_input_clamp_cursor(cursor, len);
    state.selection_anchor = selecting
        ? text_input_clamp_cursor(state.selection_anchor, len)
        : state.cursor;
}

inline bool text_input_has_selection(const TextInputState& state) {
    return state.cursor != state.selection_anchor;
}

inline int text_input_selection_start(const TextInputState& state, int len) {
    int cursor = text_input_clamp_cursor(state.cursor, len);
    int anchor = text_input_clamp_cursor(state.selection_anchor, len);
    return cursor < anchor ? cursor : anchor;
}

inline int text_input_selection_end(const TextInputState& state, int len) {
    int cursor = text_input_clamp_cursor(state.cursor, len);
    int anchor = text_input_clamp_cursor(state.selection_anchor, len);
    return cursor > anchor ? cursor : anchor;
}

inline bool text_input_has_selection(const TextInputState& state, const char* text, int cap = 0) {
    int len = text_input_len(text, cap);
    return text_input_selection_start(state, len) != text_input_selection_end(state, len);
}

inline int text_input_prefix_width(const char* text, int count, bool masked) {
    char prefix[512];
    int len = 0;
    while (text && text[len] && len < count && len < (int)sizeof(prefix) - 1) {
        prefix[len] = masked ? '*' : text[len];
        len++;
    }
    prefix[len] = '\0';
    return text_width(prefix);
}

inline int text_input_index_from_x(const Rect& bounds, const char* value,
                                   int mx, bool masked = false) {
    int rel_x = mx - (bounds.x + 8);
    if (rel_x <= 0) return 0;

    int len = text_input_len(value);
    int prev_w = 0;
    for (int i = 0; i < len; i++) {
        int cur_w = text_input_prefix_width(value, i + 1, masked);
        int midpoint = prev_w + (cur_w - prev_w) / 2;
        if (rel_x < midpoint) return i;
        prev_w = cur_w;
    }
    return len;
}

inline int text_input_mouse(TextInputState& state,
                            const Rect& bounds,
                            const char* value,
                            int mx, int my,
                            uint8_t buttons,
                            uint8_t prev_buttons,
                            bool focused,
                            bool masked = false) {
    bool left_down = (buttons & 1) != 0;
    bool left_pressed = (buttons & 1) && !(prev_buttons & 1);
    bool left_released = !(buttons & 1) && (prev_buttons & 1);

    if (left_pressed && bounds.contains(mx, my)) {
        int cursor = text_input_index_from_x(bounds, value, mx, masked);
        state.cursor = cursor;
        state.selection_anchor = cursor;
        state.dragging = true;
        return TEXT_INPUT_CONSUMED;
    }

    if (focused && state.dragging && left_down) {
        int cursor = text_input_index_from_x(bounds, value, mx, masked);
        if (state.cursor != cursor) {
            state.cursor = cursor;
            return TEXT_INPUT_CONSUMED;
        }
        return TEXT_INPUT_CONSUMED;
    }

    if (state.dragging && left_released) {
        state.dragging = false;
        return TEXT_INPUT_CONSUMED;
    }

    return TEXT_INPUT_NONE;
}

inline bool text_input_word_char(char ch) {
    return (ch >= 'a' && ch <= 'z') ||
           (ch >= 'A' && ch <= 'Z') ||
           (ch >= '0' && ch <= '9') ||
           ch == '_';
}

inline int text_input_prev_word_boundary(const char* text, int cursor) {
    int pos = cursor;
    if (pos <= 0) return 0;
    pos--;
    while (pos > 0 && !text_input_word_char(text[pos])) pos--;
    while (pos > 0 && text_input_word_char(text[pos - 1])) pos--;
    return pos;
}

inline int text_input_next_word_boundary(const char* text, int cursor) {
    int len = text_input_len(text);
    int pos = text_input_clamp_cursor(cursor, len);
    if (pos >= len) return len;
    while (pos < len && text_input_word_char(text[pos])) pos++;
    while (pos < len && !text_input_word_char(text[pos])) pos++;
    return pos;
}

inline void text_input_delete_range(char* text, int* len, int start, int end) {
    if (!text || !len || start >= end) return;
    for (int i = start; i <= *len - (end - start); i++)
        text[i] = text[i + (end - start)];
    *len -= (end - start);
}

inline bool text_input_char_allowed(char ch, TextInputCharFilter filter = nullptr,
                                    void* userdata = nullptr) {
    unsigned char u = (unsigned char)ch;
    if (u < 0x20 || u >= 0x7F) return false;
    return filter == nullptr || filter(ch, userdata);
}

inline bool text_input_delete_selection(TextInputState& state, char* text, int cap) {
    int len = text_input_len(text, cap);
    int sel_start = text_input_selection_start(state, len);
    int sel_end = text_input_selection_end(state, len);
    if (sel_start == sel_end) return false;

    text_input_delete_range(text, &len, sel_start, sel_end);
    state.cursor = sel_start;
    state.selection_anchor = sel_start;
    return true;
}

inline bool text_input_copy_selection(const TextInputState& state, const char* text,
                                      int cap, bool masked = false) {
    if (masked) return false;
    int len = text_input_len(text, cap);
    int sel_start = text_input_selection_start(state, len);
    int sel_end = text_input_selection_end(state, len);
    if (sel_start == sel_end) return false;
    return gui::clipboard_set_text(text + sel_start, sel_end - sel_start);
}

inline bool text_input_replace_selection_with_text(TextInputState& state,
                                                   char* text,
                                                   int cap,
                                                   const char* insert,
                                                   int insert_len,
                                                   TextInputCharFilter filter = nullptr,
                                                   void* userdata = nullptr) {
    if (!text || !insert || cap <= 0 || insert_len < 0) return false;

    for (int i = 0; i < insert_len; i++) {
        if (!text_input_char_allowed(insert[i], filter, userdata))
            return false;
    }

    int len = text_input_len(text, cap);
    state.cursor = text_input_clamp_cursor(state.cursor, len);
    state.selection_anchor = text_input_clamp_cursor(state.selection_anchor, len);

    int sel_start = text_input_selection_start(state, len);
    int sel_end = text_input_selection_end(state, len);
    int replaced = sel_end - sel_start;
    if (len - replaced + insert_len >= cap) return false;

    if (replaced > 0)
        text_input_delete_range(text, &len, sel_start, sel_end);

    for (int i = len; i >= sel_start; i--)
        text[i + insert_len] = text[i];
    for (int i = 0; i < insert_len; i++)
        text[sel_start + i] = insert[i];

    state.cursor = sel_start + insert_len;
    state.selection_anchor = state.cursor;
    return replaced > 0 || insert_len > 0;
}

inline bool text_input_paste_clipboard(TextInputState& state,
                                       char* text,
                                       int cap,
                                       TextInputCharFilter filter = nullptr,
                                       void* userdata = nullptr) {
    int paste_len = 0;
    char* paste = gui::clipboard_get_text_alloc(&paste_len);
    if (paste == nullptr || paste_len <= 0) {
        if (paste) montauk::mfree(paste);
        return false;
    }

    bool changed = text_input_replace_selection_with_text(state, text, cap,
                                                         paste, paste_len,
                                                         filter, userdata);
    montauk::mfree(paste);
    return changed;
}

inline bool text_input_cut_selection(TextInputState& state, char* text,
                                     int cap, bool masked = false) {
    if (!text_input_copy_selection(state, text, cap, masked))
        return false;
    return text_input_delete_selection(state, text, cap);
}

inline Rect text_input_context_menu_rect(const TextInputState& state, int view_w, int view_h) {
    return context_menu_rect(state.context, TEXT_INPUT_CTX_COUNT, view_w, view_h);
}

inline void text_input_context_items(ContextMenuItem items[TEXT_INPUT_CTX_COUNT],
                                     bool has_selection,
                                     bool masked) {
    bool paste_enabled = gui::clipboard_has_text();
    items[TEXT_INPUT_CTX_CUT] = {"Cut", TEXT_INPUT_CTX_CUT,
                                 has_selection && !masked};
    items[TEXT_INPUT_CTX_COPY] = {"Copy", TEXT_INPUT_CTX_COPY,
                                  has_selection && !masked};
    items[TEXT_INPUT_CTX_PASTE] = {"Paste", TEXT_INPUT_CTX_PASTE,
                                   paste_enabled};
    items[TEXT_INPUT_CTX_DELETE] = {"Delete", TEXT_INPUT_CTX_DELETE,
                                    has_selection};
}

inline int text_input_handle_mouse(TextInputState& state,
                                   const Rect& bounds,
                                   char* text,
                                   int cap,
                                   int mx,
                                   int my,
                                   uint8_t buttons,
                                   uint8_t prev_buttons,
                                   int view_w,
                                   int view_h,
                                   bool focused,
                                   bool masked = false,
                                   TextInputCharFilter filter = nullptr,
                                   void* userdata = nullptr) {
    bool right_pressed = (buttons & 2) && !(prev_buttons & 2);

    if (state.context.open) {
        bool has_selection = text_input_has_selection(state, text, cap);
        ContextMenuItem items[TEXT_INPUT_CTX_COUNT];
        text_input_context_items(items, has_selection, masked);

        int action = context_menu_handle_mouse(state.context, items,
                                               TEXT_INPUT_CTX_COUNT,
                                               mx, my, buttons, prev_buttons,
                                               view_w, view_h);
        if (action == CONTEXT_MENU_NONE) return TEXT_INPUT_NONE;
        int result = TEXT_INPUT_CONSUMED;
        switch (action) {
        case TEXT_INPUT_CTX_CUT:
            if (text_input_cut_selection(state, text, cap, masked))
                result |= TEXT_INPUT_CHANGED;
            break;
        case TEXT_INPUT_CTX_COPY:
            text_input_copy_selection(state, text, cap, masked);
            break;
        case TEXT_INPUT_CTX_PASTE:
            if (text_input_paste_clipboard(state, text, cap, filter, userdata))
                result |= TEXT_INPUT_CHANGED;
            break;
        case TEXT_INPUT_CTX_DELETE:
            if (text_input_delete_selection(state, text, cap))
                result |= TEXT_INPUT_CHANGED;
            break;
        }
        return result;
    }

    if (right_pressed && bounds.contains(mx, my)) {
        int len = text_input_len(text, cap);
        int cursor = text_input_index_from_x(bounds, text, mx, masked);
        int sel_start = text_input_selection_start(state, len);
        int sel_end = text_input_selection_end(state, len);
        if (sel_start == sel_end || cursor < sel_start || cursor > sel_end)
            text_input_set_cursor(state, len, cursor, false);
        state.dragging = false;
        context_menu_open(state.context, mx, my);
        return TEXT_INPUT_CONSUMED;
    }

    return text_input_mouse(state, bounds, text, mx, my, buttons, prev_buttons,
                            focused, masked);
}

inline int text_input_key(TextInputState& state,
                          char* text,
                          int cap,
                          const Montauk::KeyEvent& key,
                          TextInputCharFilter filter = nullptr,
                          void* userdata = nullptr,
                          bool masked = false) {
    if (!key.pressed || !text || cap <= 0) return TEXT_INPUT_NONE;

    int len = text_input_len(text, cap);
    state.cursor = text_input_clamp_cursor(state.cursor, len);
    state.selection_anchor = text_input_clamp_cursor(state.selection_anchor, len);

    if (state.context.open) {
        context_menu_close(state.context);
        if (key.scancode == 0x01) return TEXT_INPUT_CONSUMED;
    }

    if (key.ctrl && (key.scancode == 0x1E || key.ascii == 'a' || key.ascii == 'A')) {
        state.selection_anchor = 0;
        state.cursor = len;
        return TEXT_INPUT_CONSUMED;
    }

    int sel_start = text_input_selection_start(state, len);
    int sel_end = text_input_selection_end(state, len);

    if (key.ctrl && !key.alt && (key.ascii == 'c' || key.ascii == 'C')) {
        text_input_copy_selection(state, text, cap, masked);
        return TEXT_INPUT_CONSUMED;
    }

    if (key.ctrl && !key.alt && (key.ascii == 'x' || key.ascii == 'X')) {
        bool changed = text_input_cut_selection(state, text, cap, masked);
        return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
    }

    if (key.ctrl && !key.alt && (key.ascii == 'v' || key.ascii == 'V')) {
        bool changed = text_input_paste_clipboard(state, text, cap, filter, userdata);
        return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
    }

    if (key.scancode == 0x4B || key.scancode == 0x4D ||
        key.scancode == 0x47 || key.scancode == 0x4F) {
        int next = state.cursor;
        if (!key.shift && sel_start != sel_end && key.scancode == 0x4B)
            next = sel_start;
        else if (!key.shift && sel_start != sel_end && key.scancode == 0x4D)
            next = sel_end;
        else if (key.scancode == 0x4B)
            next = key.ctrl ? text_input_prev_word_boundary(text, state.cursor) : state.cursor - 1;
        else if (key.scancode == 0x4D)
            next = key.ctrl ? text_input_next_word_boundary(text, state.cursor) : state.cursor + 1;
        else if (key.scancode == 0x47)
            next = 0;
        else if (key.scancode == 0x4F)
            next = len;

        text_input_set_cursor(state, len, next, key.shift);
        return TEXT_INPUT_CONSUMED;
    }

    bool changed = false;

    if (key.ascii == '\b' || key.scancode == 0x0E) {
        if (sel_start != sel_end) {
            text_input_delete_range(text, &len, sel_start, sel_end);
            state.cursor = sel_start;
            changed = true;
        } else if (key.ctrl) {
            int start = text_input_prev_word_boundary(text, state.cursor);
            if (start != state.cursor) {
                text_input_delete_range(text, &len, start, state.cursor);
                state.cursor = start;
                changed = true;
            }
        } else if (state.cursor > 0) {
            text_input_delete_range(text, &len, state.cursor - 1, state.cursor);
            state.cursor--;
            changed = true;
        }
        state.selection_anchor = state.cursor;
        return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
    }

    if (key.scancode == 0x53) {
        if (sel_start != sel_end) {
            text_input_delete_range(text, &len, sel_start, sel_end);
            state.cursor = sel_start;
            changed = true;
        } else if (key.ctrl) {
            int end = text_input_next_word_boundary(text, state.cursor);
            if (end != state.cursor) {
                text_input_delete_range(text, &len, state.cursor, end);
                changed = true;
            }
        } else if (state.cursor < len) {
            text_input_delete_range(text, &len, state.cursor, state.cursor + 1);
            changed = true;
        }
        state.selection_anchor = state.cursor;
        return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
    }

    if (key.ctrl || key.alt || !text_input_char_allowed(key.ascii, filter, userdata))
        return TEXT_INPUT_NONE;

    if (sel_start != sel_end) {
        text_input_delete_range(text, &len, sel_start, sel_end);
        state.cursor = sel_start;
        changed = true;
    }

    if (len < cap - 1) {
        for (int i = len; i >= state.cursor; i--)
            text[i + 1] = text[i];
        text[state.cursor++] = key.ascii;
        len++;
        changed = true;
    }

    state.selection_anchor = state.cursor;
    return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
}

inline bool same_color(Color a, Color b) {
    return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
}

inline void draw_rounded_frame(Canvas& c,
                               const Rect& bounds,
                               int radius,
                               Color fill,
                               Color border,
                               int border_w = 1) {
    if (bounds.empty()) return;
    if (border_w <= 0 || same_color(fill, border)) {
        c.fill_rounded_rect(bounds.x, bounds.y, bounds.w, bounds.h, gui_max(radius, 0), fill);
        return;
    }

    c.fill_rounded_rect(bounds.x, bounds.y, bounds.w, bounds.h, gui_max(radius, 0), border);
    int inner_w = bounds.w - border_w * 2;
    int inner_h = bounds.h - border_w * 2;
    if (inner_w <= 0 || inner_h <= 0) return;
    c.fill_rounded_rect(bounds.x + border_w, bounds.y + border_w,
                        inner_w, inner_h, gui_max(radius - border_w, 0), fill);
}

inline ButtonColors resolve_button_colors(ButtonVariant variant,
                                          const WidgetState& state,
                                          const Theme& theme) {
    if (!state.enabled) {
        return {theme.disabled_bg, theme.disabled_bg, theme.disabled_fg};
    }

    switch (variant) {
    case BUTTON_PRIMARY:
        return {
            state.hovered ? theme.accent_hover : theme.accent,
            state.hovered ? theme.accent_hover : theme.accent,
            theme.accent_fg
        };
    case BUTTON_DANGER:
        return {
            state.hovered ? theme.danger_hover : theme.danger,
            state.hovered ? theme.danger_hover : theme.danger,
            theme.danger_fg
        };
    case BUTTON_GHOST:
        if (state.active) {
            return {theme.accent_soft, theme.accent_soft, theme.accent};
        }
        return {
            state.hovered ? theme.surface_hover : theme.window_bg,
            theme.border,
            theme.text
        };
    case BUTTON_TONAL:
        {
            Color fill = state.hovered ? mix(theme.surface, theme.accent, 40)
                                       : mix(theme.surface, theme.accent, 24);
            return {fill, fill, theme.text};
        }
    case BUTTON_SECONDARY:
    default:
        if (state.active) {
            return {
                state.hovered ? theme.accent_hover : theme.accent,
                state.hovered ? theme.accent_hover : theme.accent,
                theme.accent_fg
            };
        }
        return {
            state.hovered ? theme.surface_hover : theme.window_bg,
            theme.border,
            theme.text
        };
    }
}

inline void draw_button(Canvas& c,
                        const Rect& bounds,
                        const char* label,
                        ButtonVariant variant,
                        const WidgetState& state,
                        const Theme& theme) {
    if (bounds.empty()) return;

    ButtonColors colors = resolve_button_colors(variant, state, theme);
    draw_rounded_frame(c, bounds, theme.radius_md, colors.bg, colors.border);

    if (label && label[0]) {
        int tw = text_width(label);
        int fh = system_font_height();
        int tx = bounds.x + (bounds.w - tw) / 2;
        int ty = bounds.y + (bounds.h - fh) / 2;
        c.text(tx, ty, label, colors.fg);
    }
}

inline void draw_separator(Canvas& c, int x, int y, int w, const Theme& theme) {
    c.hline(x, y, w, theme.border);
}

inline Rect tab_rect(const Rect& bar, int count, int index) {
    if (count <= 0) return {bar.x, bar.y, 0, bar.h};
    int slot_w = bar.w / count;
    int rx = bar.x + slot_w * index;
    int rw = (index == count - 1) ? (bar.w - slot_w * index) : slot_w;
    return {rx, bar.y, rw, bar.h};
}

inline void draw_tab_bar(Canvas& c,
                         const Rect& bar,
                         const char* const* labels,
                         int count,
                         int active,
                         const Theme& theme) {
    c.fill_rect(bar.x, bar.y, bar.w, bar.h, theme.surface);
    c.hline(bar.x, bar.y + bar.h - 1, bar.w, theme.border);

    int fh = system_font_height();
    for (int i = 0; i < count; i++) {
        Rect item = tab_rect(bar, count, i);
        bool is_active = (i == active);
        if (is_active) {
            c.fill_rect(item.x, item.y, item.w, item.h, theme.window_bg);
            if (item.w > 8) {
                c.fill_rect(item.x + 4, item.y + item.h - 3, item.w - 8, 3, theme.selection);
            }
        }

        Color tc = is_active ? theme.accent : theme.text_subtle;
        int tw = text_width(labels[i]);
        c.text(item.x + (item.w - tw) / 2,
               item.y + (item.h - fh) / 2,
               labels[i], tc);
    }
}

inline int hit_tab_bar(const Rect& bar, int count, int mx, int my) {
    if (!bar.contains(mx, my) || count <= 0) return -1;
    for (int i = 0; i < count; i++) {
        if (tab_rect(bar, count, i).contains(mx, my)) return i;
    }
    return -1;
}

inline Rect radio_indicator_rect(const Rect& option) {
    int size = 14;
    return {option.x, option.y + (option.h - size) / 2, size, size};
}

inline void draw_radio(Canvas& c,
                       const Rect& option,
                       const char* label,
                       bool selected,
                       const Theme& theme) {
    Rect bubble = radio_indicator_rect(option);
    int radius = bubble.w / 2;
    c.fill_rounded_rect(bubble.x, bubble.y, bubble.w, bubble.h, radius, theme.border);
    c.fill_rounded_rect(bubble.x + 1, bubble.y + 1, bubble.w - 2, bubble.h - 2,
                        gui_max(radius - 1, 1), colors::WHITE);
    if (selected) {
        int inner = gui_max(bubble.w - 8, 2);
        c.fill_rounded_rect(bubble.x + 4, bubble.y + 4, inner, inner,
                            gui_max(inner / 2, 1), theme.accent);
    }

    int fh = system_font_height();
    c.text(bubble.x + bubble.w + theme.gap_sm,
           option.y + (option.h - fh) / 2,
           label, theme.text);
}

inline Rect swatch_rect(int row_x, int row_y, int index, int size = 24, int gap = 6) {
    return {row_x + index * (size + gap), row_y, size, size};
}

inline int hit_swatch_row(int mx, int my,
                          int row_x, int row_y,
                          int count, int size = 24, int gap = 6) {
    for (int i = 0; i < count; i++) {
        if (swatch_rect(row_x, row_y, i, size, gap).contains(mx, my)) return i;
    }
    return -1;
}

inline void draw_swatch_row(Canvas& c,
                            int row_x, int row_y,
                            const Color* palette,
                            int count,
                            int selected_index,
                            const Theme& theme,
                            int size = 24,
                            int gap = 6) {
    for (int i = 0; i < count; i++) {
        Rect swatch = swatch_rect(row_x, row_y, i, size, gap);
        if (i == selected_index) {
            c.fill_rounded_rect(swatch.x - 2, swatch.y - 2,
                                swatch.w + 4, swatch.h + 4, 4, theme.accent);
        }
        c.fill_rounded_rect(swatch.x, swatch.y, swatch.w, swatch.h, 3, palette[i]);
        c.rect(swatch.x, swatch.y, swatch.w, swatch.h, Color::from_rgb(0xCC, 0xCC, 0xCC));
    }
}

inline void draw_list_row(Canvas& c,
                          const Rect& row,
                          bool selected,
                          bool alternate,
                          const Theme& theme) {
    if (selected) {
        c.fill_rounded_rect(row.x, row.y, row.w, row.h, theme.radius_md, theme.selection);
    } else if (alternate) {
        c.fill_rounded_rect(row.x, row.y, row.w, row.h, theme.radius_md, theme.surface_alt);
    }
}

inline int badge_width(const char* label, const Theme& theme) {
    return text_width(label) + theme.gap_sm * 2;
}

inline int badge_height() {
    return system_font_height() + 4;
}

inline void draw_badge(Canvas& c,
                       int x, int y,
                       const char* label,
                       Color bg, Color fg,
                       const Theme& theme) {
    int bw = badge_width(label, theme);
    int bh = badge_height();
    c.fill_rounded_rect(x, y, bw, bh, bh / 2, bg);
    c.text(x + theme.gap_sm, y + 2, label, fg);
}

inline const char* mask_text(const char* value, char* buffer, int cap) {
    int len = 0;
    while (value && value[len] && len < cap - 1) {
        buffer[len] = '*';
        len++;
    }
    buffer[len] = '\0';
    return buffer;
}

inline void copy_prefix(char* buffer, int cap, const char* value, int count, bool masked) {
    if (cap <= 0) return;
    int len = 0;
    while (value && value[len] && len < count && len < cap - 1) {
        buffer[len] = masked ? '*' : value[len];
        len++;
    }
    buffer[len] = '\0';
}

inline void draw_text_field(Canvas& c,
                            const Rect& bounds,
                            const char* value,
                            int cursor,
                            bool focused,
                            bool masked,
                            const Theme& theme,
                            int selection_anchor = -1) {
    const char* text = value ? value : "";
    char masked_text[65];
    const char* shown = masked ? mask_text(text, masked_text, (int)sizeof(masked_text)) : text;
    int len = text_len(text);

    draw_rounded_frame(c, bounds, theme.radius_sm, theme.surface,
                       focused ? theme.accent : theme.border);

    int fh = system_font_height();
    int tx = bounds.x + 8;
    int ty = bounds.y + (bounds.h - fh) / 2;

    if (focused && selection_anchor >= 0) {
        int cur = text_input_clamp_cursor(cursor, len);
        int anchor = text_input_clamp_cursor(selection_anchor, len);
        if (cur != anchor) {
            int start = cur < anchor ? cur : anchor;
            int end = cur > anchor ? cur : anchor;
            int sx = tx + text_input_prefix_width(text, start, masked);
            int ex = tx + text_input_prefix_width(text, end, masked);
            int clip_x = gui_max(sx, bounds.x + 3);
            int clip_r = gui_min(ex, bounds.x + bounds.w - 3);
            if (clip_r > clip_x)
                c.fill_rect(clip_x, bounds.y + 4, clip_r - clip_x,
                            gui_max(bounds.h - 8, 1), theme.text_selection);
        }
    }

    c.text(tx, ty, shown, theme.text);

    if (focused) {
        int clamped = gui_clamp(cursor, 0, len);
        int cx = tx + text_input_prefix_width(text, clamped, masked);
        c.fill_rect(cx, bounds.y + 6, 2, gui_max(bounds.h - 12, 1), theme.accent);
    }
}

inline void draw_text_input_context_menu(Canvas& c,
                                         const TextInputState& state,
                                         const Theme& theme,
                                         bool has_selection = true,
                                         bool masked = false) {
    ContextMenuItem items[TEXT_INPUT_CTX_COUNT];
    text_input_context_items(items, has_selection, masked);
    draw_context_menu(c, state.context, items, TEXT_INPUT_CTX_COUNT, theme);
}

inline Rect labeled_text_input_rect(int x, int y, int w, const Theme& theme, int field_h = 0) {
    if (field_h <= 0) field_h = theme.control_h;
    return {x, y + system_font_height() + theme.gap_xs, w, field_h};
}

inline int labeled_text_height(const Theme& theme, int field_h = 0) {
    if (field_h <= 0) field_h = theme.control_h;
    return system_font_height() + theme.gap_xs + field_h;
}

inline void draw_labeled_text_field(Canvas& c,
                                    int x, int y, int w,
                                    const char* label,
                                    const char* value,
                                    int cursor,
                                    bool focused,
                                    bool masked,
                                    const Theme& theme,
                                    int field_h = 0,
                                    int selection_anchor = -1) {
    if (field_h <= 0) field_h = theme.control_h;
    c.text(x, y, label, theme.text_muted);
    draw_text_field(c, labeled_text_input_rect(x, y, w, theme, field_h),
                    value, cursor, focused, masked, theme, selection_anchor);
}

inline void draw_modal_actions(Canvas& c,
                               const Rect& primary,
                               const char* primary_label,
                               ButtonVariant primary_variant,
                               const WidgetState& primary_state,
                               const Rect& secondary,
                               const char* secondary_label,
                               const WidgetState& secondary_state,
                               const Theme& theme) {
    draw_button(c, primary, primary_label, primary_variant, primary_state, theme);
    draw_button(c, secondary, secondary_label, BUTTON_SECONDARY, secondary_state, theme);
}

} // namespace gui::mtk
