// 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
// widgetButton.c  -- Button widget
//
// Standard push button with text label, Motif-style 2px beveled border,
// and press animation. The button uses a two-phase press model:
//   - Mouse press: sets w->pressed=true and registers as sDragWidget.
//     The event dispatcher tracks mouse movement  -- if the mouse leaves the
//     button bounds, pressed is cleared (visual feedback), and if it re-enters,
//     pressed is re-set. The onClick callback fires only on mouse-up while
//     still inside the button. This gives the user a chance to cancel by
//     dragging away, matching standard GUI behavior.
//   - Keyboard press (Space/Enter): sets w->pressed=true and stores in
//     sKeyPressedBtn. The onClick fires on key-up. This uses a different
//     global than mouse press because key and mouse can't happen simultaneously.
//
// The press/drag/key state machine is shared with ImageButton via the
// widgetPressableOn* helpers in widgetOps.c (using w->pressed on WidgetT).
//
// The press animation shifts text and focus rect by BUTTON_PRESS_OFFSET (1px)
// down and right, and swaps the bevel highlight/shadow colors to create the
// illusion of the button being pushed "into" the screen.
//
// Text supports accelerator keys via '&' prefix (e.g., "&OK" underlines 'O'
// and Alt+O activates the button).

#include "dvxWgtP.h"

#define BUTTON_PAD_H         8
#define BUTTON_PAD_V         4
#define BUTTON_FOCUS_INSET   3
#define BUTTON_PRESS_OFFSET  1

static int32_t sTypeId = -1;

typedef struct {
    const char *text;
} ButtonDataT;


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

WidgetT *wgtButton(WidgetT *parent, const char *text);
void wgtRegister(void);
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);


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


// Min size includes horizontal and vertical padding around the text. The text
// width is computed by textWidthAccel which excludes the '&' accelerator prefix
// character from the measurement.
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    ButtonDataT *d = (ButtonDataT *)w->data;
    w->calcMinW = textWidthAccel(font, d->text) + BUTTON_PAD_H * 2;
    w->calcMinH = font->charHeight + BUTTON_PAD_V * 2;
}


// Paint: draws the beveled border, centered text, and optional focus rect.
// When pressed, the bevel colors swap (highlight<->shadow) creating the sunken
// appearance, and the text shifts by BUTTON_PRESS_OFFSET pixels. Disabled
// buttons use the "embossed" text technique (highlight color at +1,+1, then
// shadow color at 0,0) to create a chiseled/etched look  -- this is the
// standard Windows 3.1 disabled control appearance.
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    ButtonDataT *bd = (ButtonDataT *)w->data;
    uint32_t fg     = w->fgColor ? w->fgColor : colors->contentFg;
    uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;

    drawPressableBevel(d, ops, w->x, w->y, w->w, w->h, w->pressed, bgFace, colors);

    int32_t textX;
    int32_t textY;
    calcCenteredText(font, w->x, w->y, w->w, w->h, bd->text, &textX, &textY);

    if (w->pressed) {
        textX += BUTTON_PRESS_OFFSET;
        textY += BUTTON_PRESS_OFFSET;
    }

    drawWidgetTextAccel(d, ops, font, textX, textY, bd->text, fg, bgFace, true, w->enabled, colors);

    if (w == sFocusedWidget) {
        int32_t off = w->pressed ? BUTTON_PRESS_OFFSET : 0;
        drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg);
    }
}


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


static const WidgetClassT sClassButton = {
    .version = WGT_CLASS_VERSION,
    .flags   = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE | WCLASS_HAS_TEXT,
    .handlers = {
        [WGT_METHOD_PAINT]             = (void *)widgetButtonPaint,
        [WGT_METHOD_CALC_MIN_SIZE]     = (void *)widgetButtonCalcMinSize,
        [WGT_METHOD_ON_MOUSE]          = (void *)widgetPressableOnMouse,
        [WGT_METHOD_ON_KEY]            = (void *)widgetPressableOnKey,
        [WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetPressableOnAccelActivate,
        [WGT_METHOD_GET_TEXT]          = (void *)widgetTextGet,
        [WGT_METHOD_SET_TEXT]          = (void *)widgetTextSet,
        [WGT_METHOD_ON_DRAG_UPDATE]    = (void *)widgetPressableOnDragUpdate,
        [WGT_METHOD_ON_DRAG_END]       = (void *)widgetPressableOnDragEnd,
    }
};

static const struct {
    WidgetT *(*create)(WidgetT *parent, const char *text);
} sApi = {
    .create = wgtButton
};

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

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