// 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
// widgetCheckbox.c  -- Checkbox widget
//
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
// label to the right. The check mark is drawn as two diagonal lines forming an
// "X" pattern rather than a traditional checkmark glyph  -- this is simpler to
// render with drawHLine primitives and matches the DVX aesthetic.
//
// State management is simple: a boolean 'checked' flag toggles on each click
// or Space/Enter keypress. The onChange callback fires after each toggle so
// the application can respond immediately.
//
// Focus is shown via a dotted rectangle around the label text (not the box),
// matching the Win3.1 convention where the focus indicator wraps the label.

#include "dvxWgtP.h"

#define CHECKBOX_BOX_SIZE    12
#define CHECKBOX_GAP         4
// Check-glyph inset from the box edge on each side.  Drawing the X this
// far inside leaves a consistent border between the mark and the bevel.
#define CHECKBOX_CHECK_INSET 3

static int32_t sTypeId = -1;

typedef struct {
    const char *text;
    bool        checked;
} CheckboxDataT;


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

WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
bool wgtCheckboxIsChecked(const WidgetT *w);
void wgtCheckboxSetChecked(WidgetT *w, bool checked);
void wgtRegister(void);
void widgetCheckboxAccelActivate(WidgetT *w, WidgetT *root);
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);


WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
    return widgetAllocWithText(parent, sTypeId, sizeof(CheckboxDataT), text);
}


bool wgtCheckboxIsChecked(const WidgetT *w) {
    VALIDATE_WIDGET(w, sTypeId, false);
    CheckboxDataT *d = (CheckboxDataT *)w->data;

    return d->checked;
}


void wgtCheckboxSetChecked(WidgetT *w, bool checked) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    CheckboxDataT *d = (CheckboxDataT *)w->data;

    d->checked = checked;
    wgtInvalidatePaint(w);
}


void widgetCheckboxAccelActivate(WidgetT *w, WidgetT *root) {
    (void)root;
    widgetCheckboxOnMouse(w, NULL, 0, 0);
}


// Min width = box + gap + text. Min height = whichever is taller (the box or
// the font). This ensures the box and text are always vertically centered
// relative to each other regardless of font size.
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    CheckboxDataT *d = (CheckboxDataT *)w->data;
    // +1 on the width: widgetCheckboxPaint draws the focus rect from
    // labelX-1 with width labelW+2, whose rightmost column lands one
    // pixel past the widget's claimed right edge.  Without the +1,
    // rectFill in the next paint doesn't clear that column and the
    // stale dot survives after focus moves away.
    w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
                   textWidthAccel(font, d->text) + 1;
    w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
}


void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod) {
    (void)mod;

    if (key == ' ' || key == 0x0D) {
        CheckboxDataT *d = (CheckboxDataT *)w->data;
        d->checked = !d->checked;

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

        wgtInvalidatePaint(w);
    }
}


void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
    (void)root;
    (void)vx;
    (void)vy;
    CheckboxDataT *d = (CheckboxDataT *)w->data;
    sFocusedWidget = w;
    d->checked = !d->checked;

    // Repaint the toggled check mark / focus ring.  The mouse dispatcher
    // only invalidates the previous focus owner, not the hit widget.
    // Mark dirty before onChange so it lands even if the handler
    // destroys the widget.
    wgtInvalidatePaint(w);

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


void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    CheckboxDataT *cd = (CheckboxDataT *)w->data;
    uint32_t fg       = w->fgColor ? w->fgColor : colors->contentFg;
    uint32_t bg       = w->bgColor ? w->bgColor : colors->contentBg;
    int32_t boxY      = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;

    // Clear full widget area so old focus rect is erased
    rectFill(d, ops, w->x, w->y, w->w, w->h, bg);

    // Draw checkbox box
    BevelStyleT bevel;
    bevel.highlight = colors->windowShadow;
    bevel.shadow    = colors->windowHighlight;
    bevel.face      = bg;
    bevel.width     = 1;
    drawBevel(d, ops, w->x, boxY, CHECKBOX_BOX_SIZE, CHECKBOX_BOX_SIZE, &bevel);

    // Draw check mark if checked. The check is an X pattern drawn as
    // two diagonal lines of single pixels. Using 1-pixel drawHLine calls
    // instead of a real line drawing algorithm avoids Bresenham overhead
    // for what's always a small fixed-size glyph (6x6 pixels). The 3px
    // inset from the box edge keeps the mark visually centered.
    if (cd->checked) {
        int32_t cx       = w->x + CHECKBOX_CHECK_INSET;
        int32_t cy       = boxY + CHECKBOX_CHECK_INSET;
        int32_t cs       = CHECKBOX_BOX_SIZE - 2 * CHECKBOX_CHECK_INSET;
        uint32_t checkFg = w->enabled ? fg : colors->windowShadow;

        for (int32_t i = 0; i < cs; i++) {
            drawHLine(d, ops, cx + i, cy + i, 1, checkFg);
            drawHLine(d, ops, cx + cs - 1 - i, cy + i, 1, checkFg);
        }
    }

    // Draw label
    int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
    int32_t labelY = w->y + (w->h - font->charHeight) / 2;
    int32_t labelW = textWidthAccel(font, cd->text);

    drawWidgetTextAccel(d, ops, font, labelX, labelY, cd->text, fg, bg, false, w->enabled, colors);

    if (w == sFocusedWidget) {
        drawFocusRect(d, ops, labelX - 1, w->y, labelW + 2, w->h, fg);
    }
}


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


static const WidgetClassT sClassCheckbox = {
    .version = WGT_CLASS_VERSION,
    .flags   = WCLASS_FOCUSABLE | WCLASS_HAS_TEXT,
    .handlers = {
        [WGT_METHOD_PAINT]             = (void *)widgetCheckboxPaint,
        [WGT_METHOD_CALC_MIN_SIZE]     = (void *)widgetCheckboxCalcMinSize,
        [WGT_METHOD_ON_MOUSE]          = (void *)widgetCheckboxOnMouse,
        [WGT_METHOD_ON_KEY]            = (void *)widgetCheckboxOnKey,
        [WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetCheckboxAccelActivate,
        [WGT_METHOD_GET_TEXT]          = (void *)widgetTextGet,
        [WGT_METHOD_SET_TEXT]          = (void *)widgetTextSet,
    }
};

static const struct {
    WidgetT *(*create)(WidgetT *parent, const char *text);
    bool     (*isChecked)(const WidgetT *w);
    void     (*setChecked)(WidgetT *w, bool checked);
} sApi = {
    .create     = wgtCheckbox,
    .isChecked  = wgtCheckboxIsChecked,
    .setChecked = wgtCheckboxSetChecked
};

static const WgtPropDescT sProps[] = {
    { "Value", WGT_IFACE_BOOL, (void *)wgtCheckboxIsChecked, (void *)wgtCheckboxSetChecked, NULL }
};

static const WgtIfaceT sIface = {
    .basName      = "CheckBox",
    .props        = sProps,
    .propCount    = 1,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT_TEXT,
    .defaultEvent = "Click"
};

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