#define DVX_WIDGET_IMPL
// widgetImage.c  -- Image widget (displays bitmap, responds to clicks)
//
// Displays a bitmap image, optionally responding to clicks. The image data
// must be in the display's native pixel format (pre-converted). Two creation
// paths are provided:
//   - wgtImage: from raw pixel data already in display format (takes ownership)
//   - wgtImageFromFile: loads from file via stb_image and converts to display
//     format during load
//
// The widget supports a simple press effect (1px offset on click) and fires
// onClick immediately on mouse-down. Unlike Button which has press/release
// tracking, Image fires instantly  -- this is intentional for image-based
// click targets where visual press feedback is less important than
// responsiveness.
//
// No border or bevel is drawn  -- the image fills its widget bounds with
// centering if the widget is larger than the image.

#include "dvxWidgetPlugin.h"

static int32_t sTypeId = -1;

typedef struct {
    uint8_t *pixelData;
    uint8_t *grayData;
    int32_t  imgW;
    int32_t  imgH;
    int32_t  imgPitch;
    bool     pressed;
} ImageDataT;


// ============================================================
// widgetImageDestroy
// ============================================================

void widgetImageDestroy(WidgetT *w) {
    ImageDataT *d = (ImageDataT *)w->data;

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


// ============================================================
// widgetImageCalcMinSize
// ============================================================

void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    (void)font;
    ImageDataT *d = (ImageDataT *)w->data;
    w->calcMinW = d->imgW;
    w->calcMinH = d->imgH;
}


// ============================================================
// widgetImageOnMouse
// ============================================================

// Mouse click: briefly sets pressed=true (for the 1px offset effect),
// invalidates to show the press, fires onClick, then immediately clears
// pressed. The press is purely visual  -- there's no release tracking like
// Button has, since Image clicks are meant for instant actions (e.g.,
// clicking a logo or icon area).
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
    (void)root;
    (void)vx;
    (void)vy;
    ImageDataT *d = (ImageDataT *)w->data;
    d->pressed = true;
    wgtInvalidatePaint(w);

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

    d->pressed = false;
}


// ============================================================
// widgetImagePaint
// ============================================================

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

    if (!d->pixelData) {
        return;
    }

    // Center the image within the widget bounds
    int32_t imgW = d->imgW;
    int32_t imgH = d->imgH;
    int32_t dx   = w->x + (w->w - imgW) / 2;
    int32_t dy   = w->y + (w->h - imgH) / 2;

    // Offset by 1px when pressed (button-press effect)
    if (d->pressed) {
        dx++;
        dy++;
    }

    if (w->enabled) {
        rectCopy(disp, ops, dx, dy,
                 d->pixelData, d->imgPitch,
                 0, 0, imgW, imgH);
    } else {
        if (!d->grayData) {
            int32_t bufSize = d->imgPitch * d->imgH;
            d->grayData = (uint8_t *)malloc(bufSize);

            if (d->grayData) {
                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, dx, dy,
                     d->grayData, d->imgPitch,
                     0, 0, imgW, imgH);
        } else {
            rectCopy(disp, ops, dx, dy,
                     d->pixelData, d->imgPitch,
                     0, 0, imgW, imgH);
        }
    }
}


// Forward declaration
static void wgtImageLoadFile(WidgetT *w, const char *path);

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

static const WidgetClassT sClassImage = {
    .version = WGT_CLASS_VERSION,
    .flags   = 0,
    .handlers = {
        [WGT_METHOD_PAINT]         = (void *)widgetImagePaint,
        [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetImageCalcMinSize,
        [WGT_METHOD_ON_MOUSE]      = (void *)widgetImageOnMouse,
        [WGT_METHOD_DESTROY]       = (void *)widgetImageDestroy,
        [WGT_METHOD_SET_TEXT]      = (void *)wgtImageLoadFile,
    }
};


// ============================================================
// Widget creation functions
// ============================================================


WidgetT *wgtImage(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
    WidgetT *wgt = widgetAlloc(parent, sTypeId);

    if (wgt) {
        ImageDataT *d = calloc(1, sizeof(ImageDataT));
        d->pixelData = pixelData;
        d->imgW      = w;
        d->imgH      = h;
        d->imgPitch  = pitch;
        d->pressed   = false;
        wgt->data    = d;
    }

    return wgt;
}


WidgetT *wgtImageFromFile(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 wgtImage(parent, buf, imgW, imgH, pitch);
}


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

    free(d->pixelData);
    free(d->grayData);
    d->pixelData = pixelData;
    d->grayData  = NULL;
    d->imgW      = imgW;
    d->imgH      = imgH;
    d->imgPitch  = pitch;
    wgtInvalidate(w);
}


// ============================================================
// BASIC-facing accessors
// ============================================================

static void wgtImageLoadFile(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;
    }

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


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


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


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


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   = wgtImage,
    .fromFile = wgtImageFromFile,
    .setData  = wgtImageSetData,
    .loadFile = wgtImageLoadFile
};

static const WgtPropDescT sProps[] = {
    { "Picture",     WGT_IFACE_STRING, NULL, (void *)wgtImageLoadFile },
    { "ImageWidth",  WGT_IFACE_INT,    (void *)wgtImageGetWidth,  NULL },
    { "ImageHeight", WGT_IFACE_INT,    (void *)wgtImageGetHeight, NULL }
};

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

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