// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

#define DVX_WIDGET_IMPL
// widgetComboBox.c  -- ComboBox widget (editable text + dropdown list)
//
// Combines a single-line text input with a dropdown list. The text area
// supports full editing (cursor movement, selection, undo, clipboard) via
// the shared widgetTextEditOnKey helper, while the dropdown button opens
// a popup list overlay.
//
// This is a "combo" box in the Windows sense: the user can either type a
// value or select from the list. When an item is selected from the list,
// its text is copied into the edit buffer. The edit buffer is independently
// allocated (malloc'd) so the user can modify the text after selecting.
//
// The popup list is painted as an overlay (widgetComboBoxPaintPopup) that
// renders on top of all other widgets. Popup visibility is coordinated
// through the sOpenPopup global  -- only one popup can be open at a time.
// The sClosedPopup mechanism prevents click-to-close from immediately
// reopening the popup when the close click lands on the dropdown button.
//
// Text selection supports single-click (cursor placement + drag start),
// double-click (word select), and triple-click (select all). Drag-select
// is tracked via the sDragWidget global.

#include "dvxWgtP.h"
#include "../../../libs/kpunch/texthelp/textHelp.h"
#include "../../../libs/kpunch/listhelp/listHelp.h"
#include "stb_ds_wrap.h"

static int32_t sTypeId = -1;

typedef struct {
    // Fields are ordered by alignment to minimize struct padding.
    char       *buf;
    int32_t     bufSize;
    int32_t     len;
    int32_t     cursorPos;
    int32_t     scrollOff;
    int32_t     selStart;
    int32_t     selEnd;
    char       *undoBuf;
    int32_t     undoLen;
    int32_t     undoCursor;
    const char **items;
    int32_t     itemCount;
    int32_t     selectedIdx;
    int32_t     hoverIdx;
    int32_t     listScrollPos;
    int32_t     maxItemLen;
    // Owned item storage for AddItem/RemoveItem/Clear
    char       **ownedItems;    // stb_ds dynamic array of strdup'd strings
    bool         open;
    bool         ownsItems;     // true if items[] points to ownedItems
} ComboBoxDataT;


// ============================================================
// Prototypes
// ============================================================

static void comboBoxSyncOwned(WidgetT *w);
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen);
void wgtComboBoxAddItem(WidgetT *w, const char *text);
void wgtComboBoxClear(WidgetT *w);
const char *wgtComboBoxGetItem(const WidgetT *w, int32_t idx);
int32_t wgtComboBoxGetItemCount(const WidgetT *w);
int32_t wgtComboBoxGetSelected(const WidgetT *w);
void wgtComboBoxRemoveItem(WidgetT *w, int32_t idx);
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count);
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx);
void wgtRegister(void);
void widgetComboBoxAccelActivate(WidgetT *w, WidgetT *root);
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
static bool widgetComboBoxClearSelection(WidgetT *w);
void widgetComboBoxClosePopup(WidgetT *w);
void widgetComboBoxDestroy(WidgetT *w);
static void widgetComboBoxGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
const char *widgetComboBoxGetText(const WidgetT *w);
static void widgetComboBoxOnDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetComboBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetComboBoxSetText(WidgetT *w, const char *text);


static void comboBoxSyncOwned(WidgetT *w) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    d->items     = (const char **)d->ownedItems;
    d->itemCount = (int32_t)arrlen(d->ownedItems);
    d->maxItemLen = widgetMaxItemLen(d->items, d->itemCount);
    d->ownsItems = true;
    wgtInvalidate(w);
}


WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
    WidgetT *w = widgetAlloc(parent, sTypeId);

    if (w) {
        ComboBoxDataT *d = (ComboBoxDataT *)calloc(1, sizeof(ComboBoxDataT));

        if (!d) {
            return w;
        }

        w->data = d;
        int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
        d->buf     = (char *)malloc(bufSize);
        d->undoBuf = (char *)malloc(bufSize);
        d->bufSize = bufSize;

        if (!d->buf || !d->undoBuf) {
            free(d->buf);
            free(d->undoBuf);
            d->buf     = NULL;
            d->undoBuf = NULL;
        } else {
            d->buf[0] = '\0';
        }

        d->selStart    = -1;
        d->selEnd      = -1;
        d->selectedIdx = -1;
        w->weight = WGT_WEIGHT_FILL;
    }

    return w;
}


void wgtComboBoxAddItem(WidgetT *w, const char *text) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    arrput(d->ownedItems, strdup(text ? text : ""));
    comboBoxSyncOwned(w);
}


void wgtComboBoxClear(WidgetT *w) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    for (int32_t i = 0; i < (int32_t)arrlen(d->ownedItems); i++) {
        free(d->ownedItems[i]);
    }
    arrsetlen(d->ownedItems, 0);
    comboBoxSyncOwned(w);
    d->selectedIdx = -1;
    d->listScrollPos = 0;
}


const char *wgtComboBoxGetItem(const WidgetT *w, int32_t idx) {
    if (!w || w->type != sTypeId) { return ""; }
    const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
    if (idx < 0 || idx >= d->itemCount) { return ""; }
    return d->items[idx] ? d->items[idx] : "";
}


int32_t wgtComboBoxGetItemCount(const WidgetT *w) {
    if (!w || w->type != sTypeId) { return 0; }
    const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
    return d->itemCount;
}


int32_t wgtComboBoxGetSelected(const WidgetT *w) {
    VALIDATE_WIDGET(w, sTypeId, -1);
    const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;

    return d->selectedIdx;
}


void wgtComboBoxRemoveItem(WidgetT *w, int32_t idx) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    if (idx < 0 || idx >= (int32_t)arrlen(d->ownedItems)) { return; }
    free(d->ownedItems[idx]);
    arrdel(d->ownedItems, idx);
    comboBoxSyncOwned(w);
    if (d->selectedIdx >= d->itemCount) {
        d->selectedIdx = d->itemCount > 0 ? d->itemCount - 1 : -1;
    }
}


void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    d->items      = items;
    d->itemCount  = count;
    d->maxItemLen = widgetMaxItemLen(items, count);

    if (d->selectedIdx >= count) {
        d->selectedIdx = -1;
    }

    wgtInvalidate(w);
}


void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    d->selectedIdx = idx;

    // Copy selected item text to buffer
    if (idx >= 0 && idx < d->itemCount && d->buf) {
        strncpy(d->buf, d->items[idx], d->bufSize - 1);
        d->buf[d->bufSize - 1] = '\0';
        d->len       = (int32_t)strlen(d->buf);
        d->cursorPos = d->len;
        d->scrollOff = 0;
        d->selStart  = -1;
        d->selEnd    = -1;
    }

    wgtInvalidatePaint(w);
}


void widgetComboBoxAccelActivate(WidgetT *w, WidgetT *root) {
    (void)root;
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    d->open     = true;
    d->hoverIdx = d->selectedIdx;
    sOpenPopup = w;
    wgtInvalidatePaint(w);
}


void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    int32_t maxItemW = d->maxItemLen * font->charWidth;
    int32_t minW     = font->charWidth * 8;

    if (maxItemW < minW) {
        maxItemW = minW;
    }

    w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4;
    w->calcMinH = font->charHeight + TEXT_INPUT_PAD * 2;
}


static bool widgetComboBoxClearSelection(WidgetT *w) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    if (d->selStart >= 0 && d->selEnd >= 0 && d->selStart != d->selEnd) {
        d->selStart = -1;
        d->selEnd   = -1;
        return true;
    }

    return false;
}


void widgetComboBoxClosePopup(WidgetT *w) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    d->open = false;
    wgtInvalidatePaint(w);
}


void widgetComboBoxDestroy(WidgetT *w) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    if (d) {
        for (int32_t i = 0; i < (int32_t)arrlen(d->ownedItems); i++) {
            free(d->ownedItems[i]);
        }
        arrfree(d->ownedItems);
        free(d->buf);
        free(d->undoBuf);
        free(d);
        w->data = NULL;
    }
}


static void widgetComboBoxGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
    const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
    widgetDropdownPopupRect((WidgetT *)w, font, contentH, d->itemCount, popX, popY, popW, popH);
}


const char *widgetComboBoxGetText(const WidgetT *w) {
    const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
    return d->buf ? d->buf : "";
}


static void widgetComboBoxOnDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
    (void)root;
    (void)vy;
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    AppContextT *ctx = wgtGetContext(w);
    int32_t fieldW   = w->w - DROPDOWN_BTN_WIDTH;
    int32_t maxChars = (fieldW - TEXT_INPUT_PAD * 2) / ctx->font.charWidth;

    widgetTextEditDragUpdateLine(vx, w->x + TEXT_INPUT_PAD, maxChars, &ctx->font, d->len, &d->cursorPos, &d->scrollOff, &d->selEnd);
}


// Key handling has two modes: when the popup is open, Up/Down navigate the list
// and Enter confirms the selection. When closed, keys go to the text editor
// (via widgetTextEditOnKey) except Down-arrow which opens the popup. This split
// behavior is necessary because the same widget must serve as both a text input
// and a list selector depending on popup state.
// Key codes: 0x48|0x100 = Up, 0x50|0x100 = Down (BIOS scan codes with extended bit).
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    if (d->open) {
        if (key == KEY_UP) {
            if (d->hoverIdx > 0) {
                d->hoverIdx--;

                if (d->hoverIdx < d->listScrollPos) {
                    d->listScrollPos = d->hoverIdx;
                }
            }

            wgtInvalidatePaint(w);
            return;
        }

        if (key == KEY_DOWN) {
            if (d->hoverIdx < d->itemCount - 1) {
                d->hoverIdx++;

                if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
                    d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
                }
            }

            wgtInvalidatePaint(w);
            return;
        }

        if (key == 0x0D) {
            int32_t idx = d->hoverIdx;

            if (idx >= 0 && idx < d->itemCount) {
                d->selectedIdx = idx;

                const char *itemText = d->items[idx];
                strncpy(d->buf, itemText, d->bufSize - 1);
                d->buf[d->bufSize - 1] = '\0';
                d->len       = (int32_t)strlen(d->buf);
                d->cursorPos = d->len;
                d->scrollOff = 0;
                d->selStart  = -1;
                d->selEnd    = -1;
            }

            d->open    = false;
            sOpenPopup = NULL;

            if (w->onChange) {
                w->onChange(w);
            }

            wgtInvalidate(w);
            return;
        }

        // Type-ahead in open popup
        if (key >= 0x20 && key < 0x7F) {
            int32_t found = widgetTypeAheadSearch((char)key, d->items, d->itemCount, d->hoverIdx);

            if (found >= 0) {
                d->hoverIdx = found;

                if (d->hoverIdx < d->listScrollPos) {
                    d->listScrollPos = d->hoverIdx;
                } else if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
                    d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
                }

                wgtInvalidatePaint(w);
                return;
            }
        }
    }

    // Down arrow on closed combobox opens the popup
    if (!d->open && key == KEY_DOWN) {
        d->open     = true;
        d->hoverIdx = d->selectedIdx;
        sOpenPopup = w;

        if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
            d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
        }

        if (d->hoverIdx < d->listScrollPos) {
            d->listScrollPos = d->hoverIdx;
        }

        wgtInvalidatePaint(w);
        return;
    }

    // Text editing (when popup is closed, or non-navigation keys with popup open)
    if (!d->buf) {
        return;
    }

    clearOtherSelections(w);

    widgetTextEditOnKey(w, key, mod, d->buf, d->bufSize,
                        &d->len, &d->cursorPos,
                        &d->scrollOff,
                        &d->selStart, &d->selEnd,
                        d->undoBuf, &d->undoLen,
                        &d->undoCursor,
                        w->w - DROPDOWN_BTN_WIDTH);
}


void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
    sFocusedWidget = w;
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    // If popup is open, this click is on a popup item or scrollbar
    if (d->open) {
        AppContextT *ctx = wgtGetContext(w);
        const BitmapFontT *font = &ctx->font;
        int32_t popX;
        int32_t popY;
        int32_t popW;
        int32_t popH;

        widgetDropdownPopupRect(w, font, w->window->contentH, d->itemCount, &popX, &popY, &popW, &popH);

        int32_t visibleItems = popH / font->charHeight;

        // Check scrollbar click first
        if (widgetPopupScrollbarClick(vx, vy, popX, popY, popW, popH, d->itemCount, visibleItems, &d->listScrollPos)) {
            wgtInvalidatePaint(w);
            return;
        }

        int32_t itemIdx = d->listScrollPos + (vy - popY - 2) / font->charHeight;

        if (itemIdx >= 0 && itemIdx < d->itemCount) {
            d->selectedIdx = itemIdx;

            int32_t slen = (int32_t)strlen(d->items[itemIdx]);

            if (slen >= d->bufSize) {
                slen = d->bufSize - 1;
            }

            memcpy(d->buf, d->items[itemIdx], slen);
            d->buf[slen] = '\0';
            d->len       = slen;
            d->cursorPos = slen;
            d->scrollOff = 0;
            d->selStart  = -1;
            d->selEnd    = -1;

            if (w->onChange) {
                w->onChange(w);
            }
        }

        d->open    = false;
        sOpenPopup = NULL;
        return;
    }

    // Check if click is on the button area
    int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH;

    if (vx >= w->x + textAreaW) {
        if (w == sClosedPopup) {
            return;
        }

        d->open     = true;
        d->hoverIdx = d->selectedIdx;
        sOpenPopup  = w;
    } else {
        // Text area click  -- focus for editing
        clearOtherSelections(w);

        AppContextT *ctx = (AppContextT *)root->userData;
        widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, d->buf, d->len, d->scrollOff, &d->cursorPos, &d->selStart, &d->selEnd, true, true);
    }
}


// Paint: two regions side-by-side  -- a sunken text area (left) and a raised
// dropdown button (right). The text area renders the edit buffer with optional
// selection highlighting (up to 3 text runs: pre-selection, selection,
// post-selection). The dropdown button has a small triangular arrow glyph
// drawn as horizontal lines of decreasing width. When the popup is open,
// the button bevel is inverted (sunken) to show it's active.
void widgetComboBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->contentFg) : colors->windowShadow;
    uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;

    // Sunken text area
    int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH;
    BevelStyleT bevel;
    bevel.highlight = colors->windowShadow;
    bevel.shadow    = colors->windowHighlight;
    bevel.face      = bg;
    bevel.width     = 2;
    drawBevel(disp, ops, w->x, w->y, textAreaW, w->h, &bevel);

    // Draw text content
    if (d->buf) {
        int32_t textX    = w->x + TEXT_INPUT_PAD;
        int32_t textY    = w->y + (w->h - font->charHeight) / 2;
        int32_t maxChars = (textAreaW - TEXT_INPUT_PAD * 2 - 4) / font->charWidth;
        int32_t off      = d->scrollOff;
        int32_t len      = d->len - off;

        if (len > maxChars) {
            len = maxChars;
        }

        widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, d->buf + off, len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w == sFocusedWidget && w->enabled && !d->open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
    }

    // Drop button
    BevelStyleT btnBevel;
    btnBevel.highlight = d->open ? colors->windowShadow : colors->windowHighlight;
    btnBevel.shadow    = d->open ? colors->windowHighlight : colors->windowShadow;
    btnBevel.face      = colors->buttonFace;
    btnBevel.width     = 2;
    drawBevel(disp, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);

    // Down arrow
    uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
    int32_t  arrowX  = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
    int32_t  arrowY  = w->y + w->h / 2 - 1;
    widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
}


void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;
    int32_t popX;
    int32_t popY;
    int32_t popW;
    int32_t popH;

    widgetDropdownPopupRect(w, font, disp->clipH, d->itemCount, &popX, &popY, &popW, &popH);
    widgetPaintPopupList(disp, ops, font, colors, popX, popY, popW, popH, d->items, d->itemCount, d->hoverIdx, d->listScrollPos);
}


void widgetComboBoxSetText(WidgetT *w, const char *text) {
    ComboBoxDataT *d = (ComboBoxDataT *)w->data;

    if (d->buf) {
        strncpy(d->buf, text, d->bufSize - 1);
        d->buf[d->bufSize - 1] = '\0';
        d->len       = (int32_t)strlen(d->buf);
        d->cursorPos = d->len;
        d->scrollOff = 0;
        d->selStart  = -1;
        d->selEnd    = -1;
    }
}


// ============================================================
// DXE registration
// ============================================================


static const WidgetClassT sClassComboBox = {
    .version = WGT_CLASS_VERSION,
    .flags   = WCLASS_FOCUSABLE | WCLASS_HAS_POPUP | WCLASS_SCROLLABLE,
    .handlers = {
        [WGT_METHOD_PAINT]             = (void *)widgetComboBoxPaint,
        [WGT_METHOD_PAINT_OVERLAY]     = (void *)widgetComboBoxPaintPopup,
        [WGT_METHOD_CALC_MIN_SIZE]     = (void *)widgetComboBoxCalcMinSize,
        [WGT_METHOD_ON_MOUSE]          = (void *)widgetComboBoxOnMouse,
        [WGT_METHOD_ON_KEY]            = (void *)widgetComboBoxOnKey,
        [WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetComboBoxAccelActivate,
        [WGT_METHOD_DESTROY]           = (void *)widgetComboBoxDestroy,
        [WGT_METHOD_GET_TEXT]          = (void *)widgetComboBoxGetText,
        [WGT_METHOD_SET_TEXT]          = (void *)widgetComboBoxSetText,
        [WGT_METHOD_CLOSE_POPUP]       = (void *)widgetComboBoxClosePopup,
        [WGT_METHOD_CLEAR_SELECTION]   = (void *)widgetComboBoxClearSelection,
        [WGT_METHOD_ON_DRAG_UPDATE]    = (void *)widgetComboBoxOnDragUpdate,
        [WGT_METHOD_GET_POPUP_RECT]    = (void *)widgetComboBoxGetPopupRect,
    }
};

static const struct {
    WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
    void     (*setItems)(WidgetT *w, const char **items, int32_t count);
    int32_t  (*getSelected)(const WidgetT *w);
    void     (*setSelected)(WidgetT *w, int32_t index);
    void     (*addItem)(WidgetT *w, const char *text);
    void     (*removeItem)(WidgetT *w, int32_t idx);
    void     (*clear)(WidgetT *w);
    const char *(*getItem)(const WidgetT *w, int32_t idx);
    int32_t  (*getItemCount)(const WidgetT *w);
} sApi = {
    .create       = wgtComboBox,
    .setItems     = wgtComboBoxSetItems,
    .getSelected  = wgtComboBoxGetSelected,
    .setSelected  = wgtComboBoxSetSelected,
    .addItem      = wgtComboBoxAddItem,
    .removeItem   = wgtComboBoxRemoveItem,
    .clear        = wgtComboBoxClear,
    .getItem      = wgtComboBoxGetItem,
    .getItemCount = wgtComboBoxGetItemCount
};

static const WgtPropDescT sProps[] = {
    { "ListIndex", WGT_IFACE_INT, (void *)wgtComboBoxGetSelected, (void *)wgtComboBoxSetSelected, NULL }
};

static const WgtMethodDescT sMethods[] = {
    { "AddItem",    WGT_SIG_STR,         (void *)wgtComboBoxAddItem },
    { "Clear",      WGT_SIG_VOID,        (void *)wgtComboBoxClear },
    { "List",       WGT_SIG_RET_STR_INT, (void *)wgtComboBoxGetItem },
    { "ListCount",  WGT_SIG_RET_INT,     (void *)wgtComboBoxGetItemCount },
    { "RemoveItem", WGT_SIG_INT,         (void *)wgtComboBoxRemoveItem },
};

static const WgtIfaceT sIface = {
    .basName      = "ComboBox",
    .props        = sProps,
    .propCount    = 1,
    .methods      = sMethods,
    .methodCount  = 5,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT_INT,
    .createArgs   = { 256 },
    .defaultEvent = "Click"
};

void wgtRegister(void) {
    sTypeId = wgtRegisterClass(&sClassComboBox);
    wgtRegisterApi("combobox", &sApi);
    wgtRegisterIface("combobox", &sIface);
}
