// scoreboards_playdate.c - Cell integration for Playdate Scoreboards API
// Wraps pd_api_scoreboards.h functions for JavaScript access

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

// --- Callback Contexts ---
// Note: These callbacks are async. For simplicity, we store results globally.
// A more robust implementation would use promises or callback registration.

static JSContext *g_scoreboard_js = NULL;
static JSValue g_add_score_callback = JS_NULL;
static JSValue g_personal_best_callback = JS_NULL;
static JSValue g_boards_list_callback = JS_NULL;
static JSValue g_scores_callback = JS_NULL;

static JSValue score_to_js(JSContext *js, PDScore *score) {
    if (!score) return JS_NULL;
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "rank", JS_NewInt64(js, score->rank));
    JS_SetPropertyStr(js, obj, "value", JS_NewInt64(js, score->value));
    JS_SetPropertyStr(js, obj, "player", score->player ? JS_NewString(js, score->player) : JS_NULL);
    return obj;
}

static void add_score_cb(PDScore *score, const char *errorMessage) {
    if (!g_scoreboard_js || JS_IsNull(g_add_score_callback)) return;
    JSValue args[2];
    args[0] = score_to_js(g_scoreboard_js, score);
    args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
    JSValue ret = JS_Call(g_scoreboard_js, g_add_score_callback, JS_NULL, 2, args);
    JS_FreeValue(g_scoreboard_js, ret);
    JS_FreeValue(g_scoreboard_js, args[0]);
    JS_FreeValue(g_scoreboard_js, args[1]);
}

static void personal_best_cb(PDScore *score, const char *errorMessage) {
    if (!g_scoreboard_js || JS_IsNull(g_personal_best_callback)) return;
    JSValue args[2];
    args[0] = score_to_js(g_scoreboard_js, score);
    args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
    JSValue ret = JS_Call(g_scoreboard_js, g_personal_best_callback, JS_NULL, 2, args);
    JS_FreeValue(g_scoreboard_js, ret);
    JS_FreeValue(g_scoreboard_js, args[0]);
    JS_FreeValue(g_scoreboard_js, args[1]);
}

static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
    if (!g_scoreboard_js || JS_IsNull(g_boards_list_callback)) return;
    JSValue args[2];
    if (boards) {
        JSValue arr = JS_NewArray(g_scoreboard_js);
        for (unsigned int i = 0; i < boards->count; i++) {
            JSValue board = JS_NewObject(g_scoreboard_js);
            JS_SetPropertyStr(g_scoreboard_js, board, "boardID", 
                boards->boards[i].boardID ? JS_NewString(g_scoreboard_js, boards->boards[i].boardID) : JS_NULL);
            JS_SetPropertyStr(g_scoreboard_js, board, "name",
                boards->boards[i].name ? JS_NewString(g_scoreboard_js, boards->boards[i].name) : JS_NULL);
            JS_SetPropertyNumber(g_scoreboard_js, arr, i, board);
        }
        args[0] = arr;
    } else {
        args[0] = JS_NULL;
    }
    args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
    JSValue ret = JS_Call(g_scoreboard_js, g_boards_list_callback, JS_NULL, 2, args);
    JS_FreeValue(g_scoreboard_js, ret);
    JS_FreeValue(g_scoreboard_js, args[0]);
    JS_FreeValue(g_scoreboard_js, args[1]);
}

static void scores_cb(PDScoresList *scores, const char *errorMessage) {
    if (!g_scoreboard_js || JS_IsNull(g_scores_callback)) return;
    JSValue args[2];
    if (scores) {
        JSValue obj = JS_NewObject(g_scoreboard_js);
        JS_SetPropertyStr(g_scoreboard_js, obj, "boardID",
            scores->boardID ? JS_NewString(g_scoreboard_js, scores->boardID) : JS_NULL);
        JS_SetPropertyStr(g_scoreboard_js, obj, "count", JS_NewInt32(g_scoreboard_js, scores->count));
        JS_SetPropertyStr(g_scoreboard_js, obj, "lastUpdated", JS_NewInt64(g_scoreboard_js, scores->lastUpdated));
        JS_SetPropertyStr(g_scoreboard_js, obj, "playerIncluded", JS_NewBool(g_scoreboard_js, scores->playerIncluded));
        JS_SetPropertyStr(g_scoreboard_js, obj, "limit", JS_NewInt32(g_scoreboard_js, scores->limit));
        JSValue arr = JS_NewArray(g_scoreboard_js);
        for (unsigned int i = 0; i < scores->count; i++) {
            JS_SetPropertyNumber(g_scoreboard_js, arr, i, score_to_js(g_scoreboard_js, &scores->scores[i]));
        }
        JS_SetPropertyStr(g_scoreboard_js, obj, "scores", arr);
        args[0] = obj;
    } else {
        args[0] = JS_NULL;
    }
    args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
    JSValue ret = JS_Call(g_scoreboard_js, g_scores_callback, JS_NULL, 2, args);
    JS_FreeValue(g_scoreboard_js, ret);
    JS_FreeValue(g_scoreboard_js, args[0]);
    JS_FreeValue(g_scoreboard_js, args[1]);
}

// --- API Functions ---

JSC_SCALL(scoreboards_addScore,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    uint32_t value = (uint32_t)js2number(js, argv[1]);
    if (argc > 2 && JS_IsFunction(argv[2])) {
        g_scoreboard_js = js;
        JS_FreeValue(js, g_add_score_callback);
        g_add_score_callback = JS_DupValue(js, argv[2]);
    }
    ret = JS_NewBool(js, pd_scoreboards->addScore(str, value, add_score_cb));
)

JSC_SCALL(scoreboards_getPersonalBest,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    if (argc > 1 && JS_IsFunction(argv[1])) {
        g_scoreboard_js = js;
        JS_FreeValue(js, g_personal_best_callback);
        g_personal_best_callback = JS_DupValue(js, argv[1]);
    }
    ret = JS_NewBool(js, pd_scoreboards->getPersonalBest(str, personal_best_cb));
)

JSC_CCALL(scoreboards_freeScore,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    // Note: PDScore from callbacks is managed by Playdate, this is for user-allocated scores
    // In practice, JS doesn't allocate PDScore directly, so this is a no-op wrapper
    return JS_NULL;
)

JSC_CCALL(scoreboards_getScoreboards,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    if (argc > 0 && JS_IsFunction(argv[0])) {
        g_scoreboard_js = js;
        JS_FreeValue(js, g_boards_list_callback);
        g_boards_list_callback = JS_DupValue(js, argv[0]);
    }
    return JS_NewBool(js, pd_scoreboards->getScoreboards(boards_list_cb));
)

JSC_CCALL(scoreboards_freeBoardsList,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    // Managed by Playdate after callback
    return JS_NULL;
)

JSC_SCALL(scoreboards_getScores,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    if (argc > 1 && JS_IsFunction(argv[1])) {
        g_scoreboard_js = js;
        JS_FreeValue(js, g_scores_callback);
        g_scores_callback = JS_DupValue(js, argv[1]);
    }
    ret = JS_NewBool(js, pd_scoreboards->getScores(str, scores_cb));
)

JSC_CCALL(scoreboards_freeScoresList,
    if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
    // Managed by Playdate after callback
    return JS_NULL;
)

static const JSCFunctionListEntry js_scoreboards_funcs[] = {
    MIST_FUNC_DEF(scoreboards, addScore, 3),
    MIST_FUNC_DEF(scoreboards, getPersonalBest, 2),
    MIST_FUNC_DEF(scoreboards, freeScore, 1),
    MIST_FUNC_DEF(scoreboards, getScoreboards, 1),
    MIST_FUNC_DEF(scoreboards, freeBoardsList, 1),
    MIST_FUNC_DEF(scoreboards, getScores, 2),
    MIST_FUNC_DEF(scoreboards, freeScoresList, 1),
};

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