// sprite_playdate.c - Cell integration for Playdate Sprite API
// Wraps pd_api_sprite.h functions for JavaScript access

#include "cell.h"
#include "common.h"

// --- Helpers ---

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

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

// --- Sprite Functions ---

JSC_CCALL(sprite_setAlwaysRedraw,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    pd_sprite->setAlwaysRedraw(JS_ToBool(js, argv[0]));
    return JS_NULL;
)

JSC_CCALL(sprite_drawSprites,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    pd_sprite->drawSprites();
    return JS_NULL;
)

JSC_CCALL(sprite_updateAndDrawSprites,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    pd_sprite->updateAndDrawSprites();
    return JS_NULL;
)

JSC_CCALL(sprite_newSprite,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = pd_sprite->newSprite();
    if (!s) return JS_RaiseDisrupt(js, "failed to create sprite");
    return JS_NewInt64(js, (int64_t)(intptr_t)s);
)

JSC_CCALL(sprite_freeSprite,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (s) pd_sprite->freeSprite(s);
    return JS_NULL;
)

JSC_CCALL(sprite_copy,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    LCDSprite *copy = pd_sprite->copy(s);
    return copy ? JS_NewInt64(js, (int64_t)(intptr_t)copy) : JS_NULL;
)

JSC_CCALL(sprite_addSprite,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->addSprite(s);
    return JS_NULL;
)

JSC_CCALL(sprite_removeSprite,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->removeSprite(s);
    return JS_NULL;
)

JSC_CCALL(sprite_removeAllSprites,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    pd_sprite->removeAllSprites();
    return JS_NULL;
)

JSC_CCALL(sprite_getSpriteCount,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    return JS_NewInt32(js, pd_sprite->getSpriteCount());
)

JSC_CCALL(sprite_setBounds,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    PDRect bounds = { (float)js2number(js, argv[1]), (float)js2number(js, argv[2]),
                      (float)js2number(js, argv[3]), (float)js2number(js, argv[4]) };
    pd_sprite->setBounds(s, bounds);
    return JS_NULL;
)

JSC_CCALL(sprite_getBounds,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    PDRect b = pd_sprite->getBounds(s);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, b.x));
    JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, b.y));
    JS_SetPropertyStr(js, obj, "width", JS_NewFloat64(js, b.width));
    JS_SetPropertyStr(js, obj, "height", JS_NewFloat64(js, b.height));
    return obj;
)

JSC_CCALL(sprite_moveTo,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->moveTo(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)

JSC_CCALL(sprite_moveBy,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->moveBy(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)

JSC_CCALL(sprite_setImage,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    LCDBitmap *img = js2bitmap(js, argv[1]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    LCDBitmapFlip flip = argc > 2 ? (LCDBitmapFlip)(int)js2number(js, argv[2]) : kBitmapUnflipped;
    pd_sprite->setImage(s, img, flip);
    return JS_NULL;
)

JSC_CCALL(sprite_getImage,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    LCDBitmap *img = pd_sprite->getImage(s);
    return img ? JS_NewInt64(js, (int64_t)(intptr_t)img) : JS_NULL;
)

JSC_CCALL(sprite_setSize,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setSize(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)

JSC_CCALL(sprite_setZIndex,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setZIndex(s, (int16_t)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_getZIndex,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    return JS_NewInt32(js, pd_sprite->getZIndex(s));
)

JSC_CCALL(sprite_setDrawMode,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setDrawMode(s, (LCDBitmapDrawMode)(int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_setImageFlip,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setImageFlip(s, (LCDBitmapFlip)(int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_getImageFlip,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    return JS_NewInt32(js, pd_sprite->getImageFlip(s));
)

JSC_CCALL(sprite_setVisible,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setVisible(s, JS_ToBool(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_isVisible,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    return JS_NewBool(js, pd_sprite->isVisible(s));
)

JSC_CCALL(sprite_setOpaque,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setOpaque(s, JS_ToBool(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_markDirty,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->markDirty(s);
    return JS_NULL;
)

JSC_CCALL(sprite_setTag,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setTag(s, (uint8_t)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_getTag,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    return JS_NewInt32(js, pd_sprite->getTag(s));
)

JSC_CCALL(sprite_setIgnoresDrawOffset,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setIgnoresDrawOffset(s, JS_ToBool(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_getPosition,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    float x, y;
    pd_sprite->getPosition(s, &x, &y);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, x));
    JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, y));
    return obj;
)

JSC_CCALL(sprite_setCenter,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setCenter(s, (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)

JSC_CCALL(sprite_getCenter,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    float x, y;
    pd_sprite->getCenter(s, &x, &y);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, x));
    JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, y));
    return obj;
)

// --- Collision Functions ---

JSC_CCALL(sprite_resetCollisionWorld,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    pd_sprite->resetCollisionWorld();
    return JS_NULL;
)

JSC_CCALL(sprite_setCollideRect,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    PDRect rect = { (float)js2number(js, argv[1]), (float)js2number(js, argv[2]),
                    (float)js2number(js, argv[3]), (float)js2number(js, argv[4]) };
    pd_sprite->setCollideRect(s, rect);
    return JS_NULL;
)

JSC_CCALL(sprite_getCollideRect,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    PDRect r = pd_sprite->getCollideRect(s);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, r.x));
    JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, r.y));
    JS_SetPropertyStr(js, obj, "width", JS_NewFloat64(js, r.width));
    JS_SetPropertyStr(js, obj, "height", JS_NewFloat64(js, r.height));
    return obj;
)

JSC_CCALL(sprite_clearCollideRect,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->clearCollideRect(s);
    return JS_NULL;
)

JSC_CCALL(sprite_setCollisionsEnabled,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setCollisionsEnabled(s, JS_ToBool(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_collisionsEnabled,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    return JS_NewBool(js, pd_sprite->collisionsEnabled(s));
)

JSC_CCALL(sprite_setUpdatesEnabled,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    pd_sprite->setUpdatesEnabled(s, JS_ToBool(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(sprite_updatesEnabled,
    if (!pd_sprite) return JS_RaiseDisrupt(js, "sprite not initialized");
    LCDSprite *s = js2sprite(js, argv[0]);
    if (!s) return JS_RaiseDisrupt(js, "invalid sprite");
    return JS_NewBool(js, pd_sprite->updatesEnabled(s));
)

static const JSCFunctionListEntry js_sprite_funcs[] = {
    MIST_FUNC_DEF(sprite, setAlwaysRedraw, 1),
    MIST_FUNC_DEF(sprite, drawSprites, 0),
    MIST_FUNC_DEF(sprite, updateAndDrawSprites, 0),
    MIST_FUNC_DEF(sprite, newSprite, 0),
    MIST_FUNC_DEF(sprite, freeSprite, 1),
    MIST_FUNC_DEF(sprite, copy, 1),
    MIST_FUNC_DEF(sprite, addSprite, 1),
    MIST_FUNC_DEF(sprite, removeSprite, 1),
    MIST_FUNC_DEF(sprite, removeAllSprites, 0),
    MIST_FUNC_DEF(sprite, getSpriteCount, 0),
    MIST_FUNC_DEF(sprite, setBounds, 5),
    MIST_FUNC_DEF(sprite, getBounds, 1),
    MIST_FUNC_DEF(sprite, moveTo, 3),
    MIST_FUNC_DEF(sprite, moveBy, 3),
    MIST_FUNC_DEF(sprite, setImage, 3),
    MIST_FUNC_DEF(sprite, getImage, 1),
    MIST_FUNC_DEF(sprite, setSize, 3),
    MIST_FUNC_DEF(sprite, setZIndex, 2),
    MIST_FUNC_DEF(sprite, getZIndex, 1),
    MIST_FUNC_DEF(sprite, setDrawMode, 2),
    MIST_FUNC_DEF(sprite, setImageFlip, 2),
    MIST_FUNC_DEF(sprite, getImageFlip, 1),
    MIST_FUNC_DEF(sprite, setVisible, 2),
    MIST_FUNC_DEF(sprite, isVisible, 1),
    MIST_FUNC_DEF(sprite, setOpaque, 2),
    MIST_FUNC_DEF(sprite, markDirty, 1),
    MIST_FUNC_DEF(sprite, setTag, 2),
    MIST_FUNC_DEF(sprite, getTag, 1),
    MIST_FUNC_DEF(sprite, setIgnoresDrawOffset, 2),
    MIST_FUNC_DEF(sprite, getPosition, 1),
    MIST_FUNC_DEF(sprite, setCenter, 3),
    MIST_FUNC_DEF(sprite, getCenter, 1),
    MIST_FUNC_DEF(sprite, resetCollisionWorld, 0),
    MIST_FUNC_DEF(sprite, setCollideRect, 5),
    MIST_FUNC_DEF(sprite, getCollideRect, 1),
    MIST_FUNC_DEF(sprite, clearCollideRect, 1),
    MIST_FUNC_DEF(sprite, setCollisionsEnabled, 2),
    MIST_FUNC_DEF(sprite, collisionsEnabled, 1),
    MIST_FUNC_DEF(sprite, setUpdatesEnabled, 2),
    MIST_FUNC_DEF(sprite, updatesEnabled, 1),
};

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