// gfx_playdate.c - Cell integration for Playdate Graphics API
// Wraps pd_api_gfx.h functions for JavaScript access

#include "cell.h"
#include "common.h"
#include <string.h>

// --- Helper: Convert JS value to LCDBitmap* ---
static LCDBitmap* js2bitmap(JSContext *js, JSValueConst val) {
    int64_t ptr;
    if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (LCDBitmap*)(intptr_t)ptr;
}

static LCDBitmapTable* js2bitmaptable(JSContext *js, JSValueConst val) {
    int64_t ptr;
    if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (LCDBitmapTable*)(intptr_t)ptr;
}

static LCDFont* js2font(JSContext *js, JSValueConst val) {
    int64_t ptr;
    if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (LCDFont*)(intptr_t)ptr;
}

// --- Drawing Functions ---

JSC_CCALL(gfx_clear,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDColor color = (LCDColor)js2number(js, argv[0]);
    pd_gfx->clear(color);
    return JS_NULL;
)

JSC_CCALL(gfx_setBackgroundColor,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDSolidColor color = (LCDSolidColor)(int)js2number(js, argv[0]);
    pd_gfx->setBackgroundColor(color);
    return JS_NULL;
)

JSC_CCALL(gfx_setDrawMode,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmapDrawMode mode = (LCDBitmapDrawMode)(int)js2number(js, argv[0]);
    LCDBitmapDrawMode prev = pd_gfx->setDrawMode(mode);
    return JS_NewInt32(js, prev);
)

JSC_CCALL(gfx_setDrawOffset,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->setDrawOffset((int)js2number(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(gfx_setClipRect,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->setClipRect((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                        (int)js2number(js, argv[2]), (int)js2number(js, argv[3]));
    return JS_NULL;
)

JSC_CCALL(gfx_clearClipRect,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->clearClipRect();
    return JS_NULL;
)

JSC_CCALL(gfx_setLineCapStyle,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->setLineCapStyle((LCDLineCapStyle)(int)js2number(js, argv[0]));
    return JS_NULL;
)

JSC_CCALL(gfx_setFont,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->setFont(js2font(js, argv[0]));
    return JS_NULL;
)

JSC_CCALL(gfx_setTextTracking,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->setTextTracking((int)js2number(js, argv[0]));
    return JS_NULL;
)

JSC_CCALL(gfx_getTextTracking,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    return JS_NewInt32(js, pd_gfx->getTextTracking());
)

JSC_CCALL(gfx_pushContext,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->pushContext(argc > 0 ? js2bitmap(js, argv[0]) : NULL);
    return JS_NULL;
)

JSC_CCALL(gfx_popContext,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->popContext();
    return JS_NULL;
)

// --- Drawing Primitives ---

JSC_CCALL(gfx_drawBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap");
    pd_gfx->drawBitmap(bmp, (int)js2number(js, argv[1]), (int)js2number(js, argv[2]),
                       argc > 3 ? (LCDBitmapFlip)(int)js2number(js, argv[3]) : kBitmapUnflipped);
    return JS_NULL;
)

JSC_CCALL(gfx_drawLine,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->drawLine((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                     (int)js2number(js, argv[2]), (int)js2number(js, argv[3]),
                     (int)js2number(js, argv[4]), (LCDColor)js2number(js, argv[5]));
    return JS_NULL;
)

JSC_CCALL(gfx_drawRect,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->drawRect((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                     (int)js2number(js, argv[2]), (int)js2number(js, argv[3]),
                     (LCDColor)js2number(js, argv[4]));
    return JS_NULL;
)

JSC_CCALL(gfx_fillRect,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->fillRect((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                     (int)js2number(js, argv[2]), (int)js2number(js, argv[3]),
                     (LCDColor)js2number(js, argv[4]));
    return JS_NULL;
)

JSC_CCALL(gfx_fillTriangle,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->fillTriangle((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                         (int)js2number(js, argv[2]), (int)js2number(js, argv[3]),
                         (int)js2number(js, argv[4]), (int)js2number(js, argv[5]),
                         (LCDColor)js2number(js, argv[6]));
    return JS_NULL;
)

JSC_CCALL(gfx_drawEllipse,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->drawEllipse((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                        (int)js2number(js, argv[2]), (int)js2number(js, argv[3]),
                        (int)js2number(js, argv[4]), (float)js2number(js, argv[5]),
                        (float)js2number(js, argv[6]), (LCDColor)js2number(js, argv[7]));
    return JS_NULL;
)

JSC_CCALL(gfx_fillEllipse,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->fillEllipse((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                        (int)js2number(js, argv[2]), (int)js2number(js, argv[3]),
                        (float)js2number(js, argv[4]), (float)js2number(js, argv[5]),
                        (LCDColor)js2number(js, argv[6]));
    return JS_NULL;
)

JSC_SCALL(gfx_drawText,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    int w = pd_gfx->drawText(str, strlen(str), kUTF8Encoding,
                             (int)js2number(js, argv[1]), (int)js2number(js, argv[2]));
    ret = JS_NewInt32(js, w);
)

JSC_CCALL(gfx_drawScaledBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap");
    pd_gfx->drawScaledBitmap(bmp, (int)js2number(js, argv[1]), (int)js2number(js, argv[2]),
                             (float)js2number(js, argv[3]), (float)js2number(js, argv[4]));
    return JS_NULL;
)

JSC_CCALL(gfx_drawRotatedBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap");
    pd_gfx->drawRotatedBitmap(bmp, (int)js2number(js, argv[1]), (int)js2number(js, argv[2]),
                              (float)js2number(js, argv[3]), (float)js2number(js, argv[4]),
                              (float)js2number(js, argv[5]), (float)js2number(js, argv[6]),
                              (float)js2number(js, argv[7]));
    return JS_NULL;
)

JSC_CCALL(gfx_setPixel,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->setPixel((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                     (LCDColor)js2number(js, argv[2]));
    return JS_NULL;
)

// --- Bitmap Functions ---

JSC_CCALL(gfx_newBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = pd_gfx->newBitmap((int)js2number(js, argv[0]), (int)js2number(js, argv[1]),
                                       argc > 2 ? (LCDColor)js2number(js, argv[2]) : kColorWhite);
    if (!bmp) return JS_ThrowInternalError(js, "failed to create bitmap");
    return JS_NewInt64(js, (int64_t)(intptr_t)bmp);
)

JSC_CCALL(gfx_freeBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (bmp) pd_gfx->freeBitmap(bmp);
    return JS_NULL;
)

JSC_SCALL(gfx_loadBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    const char *err = NULL;
    LCDBitmap *bmp = pd_gfx->loadBitmap(str, &err);
    if (!bmp) ret = JS_ThrowInternalError(js, "loadBitmap: %s", err ? err : "unknown");
    else ret = JS_NewInt64(js, (int64_t)(intptr_t)bmp);
)

JSC_CCALL(gfx_copyBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap");
    LCDBitmap *copy = pd_gfx->copyBitmap(bmp);
    return copy ? JS_NewInt64(js, (int64_t)(intptr_t)copy) : JS_NULL;
)

JSC_CCALL(gfx_clearBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap");
    pd_gfx->clearBitmap(bmp, argc > 1 ? (LCDColor)js2number(js, argv[1]) : kColorWhite);
    return JS_NULL;
)

JSC_CCALL(gfx_getBitmapData,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = js2bitmap(js, argv[0]);
    if (!bmp) return JS_ThrowTypeError(js, "invalid bitmap");
    int w, h, rb; uint8_t *mask, *data;
    pd_gfx->getBitmapData(bmp, &w, &h, &rb, &mask, &data);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, w));
    JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, h));
    JS_SetPropertyStr(js, obj, "rowbytes", JS_NewInt32(js, rb));
    return obj;
)

// --- Font Functions ---

JSC_SCALL(gfx_loadFont,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    const char *err = NULL;
    LCDFont *font = pd_gfx->loadFont(str, &err);
    if (!font) ret = JS_ThrowInternalError(js, "loadFont: %s", err ? err : "unknown");
    else ret = JS_NewInt64(js, (int64_t)(intptr_t)font);
)

JSC_CCALL(gfx_getFontHeight,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDFont *font = js2font(js, argv[0]);
    return JS_NewInt32(js, pd_gfx->getFontHeight(font));
)

JSC_SCALL(gfx_getTextWidth,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDFont *font = argc > 1 ? js2font(js, argv[1]) : NULL;
    int tracking = argc > 2 ? (int)js2number(js, argv[2]) : 0;
    ret = JS_NewInt32(js, pd_gfx->getTextWidth(font, str, strlen(str), kUTF8Encoding, tracking));
)

// --- Framebuffer ---

JSC_CCALL(gfx_display,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->display();
    return JS_NULL;
)

JSC_CCALL(gfx_markUpdatedRows,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    pd_gfx->markUpdatedRows((int)js2number(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(gfx_copyFrameBufferBitmap,
    if (!pd_gfx) return JS_ThrowInternalError(js, "graphics not initialized");
    LCDBitmap *bmp = pd_gfx->copyFrameBufferBitmap();
    return bmp ? JS_NewInt64(js, (int64_t)(intptr_t)bmp) : JS_NULL;
)

static const JSCFunctionListEntry js_gfx_funcs[] = {
    MIST_FUNC_DEF(gfx, clear, 1),
    MIST_FUNC_DEF(gfx, setBackgroundColor, 1),
    MIST_FUNC_DEF(gfx, setDrawMode, 1),
    MIST_FUNC_DEF(gfx, setDrawOffset, 2),
    MIST_FUNC_DEF(gfx, setClipRect, 4),
    MIST_FUNC_DEF(gfx, clearClipRect, 0),
    MIST_FUNC_DEF(gfx, setLineCapStyle, 1),
    MIST_FUNC_DEF(gfx, setFont, 1),
    MIST_FUNC_DEF(gfx, setTextTracking, 1),
    MIST_FUNC_DEF(gfx, getTextTracking, 0),
    MIST_FUNC_DEF(gfx, pushContext, 1),
    MIST_FUNC_DEF(gfx, popContext, 0),
    MIST_FUNC_DEF(gfx, drawBitmap, 4),
    MIST_FUNC_DEF(gfx, drawLine, 6),
    MIST_FUNC_DEF(gfx, drawRect, 5),
    MIST_FUNC_DEF(gfx, fillRect, 5),
    MIST_FUNC_DEF(gfx, fillTriangle, 7),
    MIST_FUNC_DEF(gfx, drawEllipse, 8),
    MIST_FUNC_DEF(gfx, fillEllipse, 7),
    MIST_FUNC_DEF(gfx, drawText, 3),
    MIST_FUNC_DEF(gfx, drawScaledBitmap, 5),
    MIST_FUNC_DEF(gfx, drawRotatedBitmap, 8),
    MIST_FUNC_DEF(gfx, setPixel, 3),
    MIST_FUNC_DEF(gfx, newBitmap, 3),
    MIST_FUNC_DEF(gfx, freeBitmap, 1),
    MIST_FUNC_DEF(gfx, loadBitmap, 1),
    MIST_FUNC_DEF(gfx, copyBitmap, 1),
    MIST_FUNC_DEF(gfx, clearBitmap, 2),
    MIST_FUNC_DEF(gfx, getBitmapData, 1),
    MIST_FUNC_DEF(gfx, loadFont, 1),
    MIST_FUNC_DEF(gfx, getFontHeight, 1),
    MIST_FUNC_DEF(gfx, getTextWidth, 3),
    MIST_FUNC_DEF(gfx, display, 0),
    MIST_FUNC_DEF(gfx, markUpdatedRows, 2),
    MIST_FUNC_DEF(gfx, copyFrameBufferBitmap, 0),
};

JSValue js_gfx_use(JSContext *js) {
    JSValue mod = JS_NewObject(js);
    JS_SetPropertyFunctionList(js, mod, js_gfx_funcs, countof(js_gfx_funcs));
    return mod;
}
