// 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
// widgetImageButton.c  -- Image button widget (button with image instead of text)
//
// Combines a Button's beveled border and press behavior with an Image's
// bitmap rendering. The image is centered within the button bounds and
// shifts by 1px on press, just like text in a regular button.
//
// Uses the same two-phase press model as Button: mouse press registers as
// sDragWidget, keyboard press (Space/Enter) stores in sKeyPressedBtn,
// and the event dispatcher handles release/cancel. The onClick callback
// fires on release, not press. The drag/press state machine itself lives
// in widgetOps.c (widgetPressableOn*) and drives w->pressed on the WidgetT.
//
// The widget takes ownership of the image data buffer  -- if widget creation
// fails, the data is freed to prevent leaks.
//
// The 4px added to min size (widgetImageButtonCalcMinSize) accounts for
// the 2px bevel on each side  -- no extra padding is added beyond that,
// keeping image buttons compact for toolbar use.

#include "dvxWgtP.h"

// 2px bevel on every side -- min size and draw inset both derive from this.
#define IMAGEBUTTON_BEVEL_W  2
#define IMAGEBUTTON_FOCUS_INSET   3   // focus-rect inset from the button edge
#define IMAGEBUTTON_PRESS_OFFSET  1   // px shift of content while pressed

static int32_t sTypeId = -1;

typedef struct {
    uint8_t *pixelData;
    uint8_t *grayData;     // lazily generated grayscale cache (NULL until needed)
    int32_t  imgW;
    int32_t  imgH;
    int32_t  imgPitch;
    char     picturePath[DVX_MAX_PATH];
} ImageButtonDataT;


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

WidgetT *wgtImageButton(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path);
static int32_t wgtImageButtonGetHeight(const WidgetT *w);
static const char *wgtImageButtonGetPicture(const WidgetT *w);
static int32_t wgtImageButtonGetWidth(const WidgetT *w);
static void wgtImageButtonLoadFile(WidgetT *w, const char *path);
void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void wgtRegister(void);
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetImageButtonDestroy(WidgetT *w);
void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);


WidgetT *wgtImageButton(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
    if (!parent || !pixelData || w <= 0 || h <= 0) {
        return NULL;
    }

    WidgetT *wgt = widgetAlloc(parent, sTypeId);

    if (!wgt) {
        free(pixelData);
        return NULL;
    }

    ImageButtonDataT *d = calloc(1, sizeof(ImageButtonDataT));

    if (!d) {
        free(pixelData);
        widgetAllocRollback(wgt);
        return NULL;
    }

    d->pixelData = pixelData;
    d->imgW      = w;
    d->imgH      = h;
    d->imgPitch  = pitch;
    wgt->data    = d;

    return wgt;
}


WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path) {
    if (!parent || !path) {
        return NULL;
    }

    AppContextT *ctx = wgtGetContext(parent);

    if (!ctx) {
        return NULL;
    }

    int32_t  imgW;
    int32_t  imgH;
    int32_t  pitch;
    uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);

    if (!buf) {
        return NULL;
    }

    return wgtImageButton(parent, buf, imgW, imgH, pitch);
}


static int32_t wgtImageButtonGetHeight(const WidgetT *w) {
    VALIDATE_WIDGET(w, sTypeId, 0);
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;
    return d->imgH;
}


static const char *wgtImageButtonGetPicture(const WidgetT *w) {
    VALIDATE_WIDGET(w, sTypeId, "");
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;
    return d->picturePath;
}


static int32_t wgtImageButtonGetWidth(const WidgetT *w) {
    VALIDATE_WIDGET(w, sTypeId, 0);
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;
    return d->imgW;
}


static void wgtImageButtonLoadFile(WidgetT *w, const char *path) {
    VALIDATE_WIDGET_VOID(w, sTypeId);

    if (!path) {
        return;
    }

    AppContextT *ctx = wgtGetContext(w);

    if (!ctx) {
        return;
    }

    int32_t  imgW;
    int32_t  imgH;
    int32_t  pitch;
    uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);

    if (!buf) {
        return;
    }

    ImageButtonDataT *d = (ImageButtonDataT *)w->data;
    snprintf(d->picturePath, sizeof(d->picturePath), "%s", path);

    wgtImageButtonSetData(w, buf, imgW, imgH, pitch);
}


void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;

    free(d->pixelData);
    d->pixelData = pixelData;
    d->imgW      = imgW;
    d->imgH      = imgH;
    d->imgPitch  = pitch;

    // Drop the cached grayscale (disabled) image: it was sized for the
    // old dimensions, so the disabled-paint path would otherwise reuse a
    // stale, wrongly-sized buffer and over-read it.
    free(d->grayData);
    d->grayData = NULL;

    wgtInvalidate(w);
}


void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    (void)font;
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;

    // Bevel border only, no extra padding
    w->calcMinW = d->imgW + 2 * IMAGEBUTTON_BEVEL_W;
    w->calcMinH = d->imgH + 2 * IMAGEBUTTON_BEVEL_W;
}


void widgetImageButtonDestroy(WidgetT *w) {
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;

    if (d) {
        free(d->pixelData);
        free(d->grayData);
        free(d);
    }
}


void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    (void)font;
    ImageButtonDataT *d = (ImageButtonDataT *)w->data;

    uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
    bool     pressed = w->pressed && w->enabled;

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

    if (d->pixelData) {
        int32_t imgX = w->x + (w->w - d->imgW) / 2;
        int32_t imgY = w->y + (w->h - d->imgH) / 2;

        if (pressed) {
            imgX++;
            imgY++;
        }

        if (w->enabled) {
            rectCopy(disp, ops, imgX, imgY,
                     d->pixelData, d->imgPitch,
                     0, 0, d->imgW, d->imgH);
        } else {
            // Lazy-generate grayscale cache on first disabled paint
            if (!d->grayData) {
                int32_t bufSize = d->imgPitch * d->imgH;
                d->grayData = (uint8_t *)malloc(bufSize);

                if (d->grayData) {
                    // Use a temp display to blit grayscale into the cache
                    DisplayT tmp = *disp;
                    tmp.backBuf = d->grayData;
                    tmp.width   = d->imgW;
                    tmp.height  = d->imgH;
                    tmp.pitch   = d->imgPitch;
                    tmp.clipX   = 0;
                    tmp.clipY   = 0;
                    tmp.clipW   = d->imgW;
                    tmp.clipH   = d->imgH;
                    rectCopyGrayscale(&tmp, ops, 0, 0,
                                     d->pixelData, d->imgPitch,
                                     0, 0, d->imgW, d->imgH);
                }
            }

            if (d->grayData) {
                rectCopy(disp, ops, imgX, imgY,
                         d->grayData, d->imgPitch,
                         0, 0, d->imgW, d->imgH);
            } else {
                // Fallback if malloc failed
                rectCopy(disp, ops, imgX, imgY,
                         d->pixelData, d->imgPitch,
                         0, 0, d->imgW, d->imgH);
            }
        }
    }

    if (w == sFocusedWidget) {
        uint32_t fg  = w->fgColor ? w->fgColor : colors->contentFg;
        int32_t  off = pressed ? IMAGEBUTTON_PRESS_OFFSET : 0;
        drawFocusRect(disp, ops, w->x + IMAGEBUTTON_FOCUS_INSET + off, w->y + IMAGEBUTTON_FOCUS_INSET + off, w->w - IMAGEBUTTON_FOCUS_INSET * 2, w->h - IMAGEBUTTON_FOCUS_INSET * 2, fg);
    }
}


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

static const WidgetClassT sClassImageButton = {
    .version = WGT_CLASS_VERSION,
    .flags   = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
    .handlers = {
        [WGT_METHOD_PAINT]             = (void *)widgetImageButtonPaint,
        [WGT_METHOD_CALC_MIN_SIZE]     = (void *)widgetImageButtonCalcMinSize,
        [WGT_METHOD_ON_MOUSE]          = (void *)widgetPressableOnMouse,
        [WGT_METHOD_ON_KEY]            = (void *)widgetPressableOnKey,
        [WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetPressableOnAccelActivate,
        [WGT_METHOD_DESTROY]           = (void *)widgetImageButtonDestroy,
        [WGT_METHOD_ON_DRAG_UPDATE]    = (void *)widgetPressableOnDragUpdate,
        [WGT_METHOD_ON_DRAG_END]       = (void *)widgetPressableOnDragEnd,
    }
};

static const struct {
    WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
    WidgetT *(*fromFile)(WidgetT *parent, const char *path);
    void     (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
    void     (*loadFile)(WidgetT *w, const char *path);
} sApi = {
    .create   = wgtImageButton,
    .fromFile = wgtImageButtonFromFile,
    .setData  = wgtImageButtonSetData,
    .loadFile = wgtImageButtonLoadFile
};

static const WgtPropDescT sImgBtnProps[] = {
    { "Picture",     WGT_IFACE_STRING, (void *)wgtImageButtonGetPicture, (void *)wgtImageButtonLoadFile, NULL },
    { "ImageWidth",  WGT_IFACE_INT,    (void *)wgtImageButtonGetWidth,  NULL, NULL },
    { "ImageHeight", WGT_IFACE_INT,    (void *)wgtImageButtonGetHeight, NULL, NULL }
};

static const WgtIfaceT sIface = {
    .basName      = "ImageButton",
    .props        = sImgBtnProps,
    .propCount    = 3,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT_DATA,
    .defaultEvent = "Click"
};

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