// network_playdate.c - Cell integration for Playdate Network API
// Wraps pd_api_network.h functions for JavaScript access

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

// --- Helpers ---
static HTTPConnection* js2http(JSContext *js, JSValueConst val) {
    int64_t ptr;
    if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (HTTPConnection*)(intptr_t)ptr;
}

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

// --- Network Status ---

JSC_CCALL(network_getStatus,
    if (!pd_network) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt32(js, pd_network->getStatus());
)

JSC_CCALL(network_setEnabled,
    if (!pd_network) return JS_RaiseDisrupt(js, "network not initialized");
    // Note: callback not implemented for simplicity
    pd_network->setEnabled(JS_ToBool(js, argv[0]), NULL);
    return JS_NULL;
)

// --- HTTP Functions ---

JSC_SCALL(http_newConnection,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    int port = (int)js2number(js, argv[1]);
    int usessl = argc > 2 ? JS_ToBool(js, argv[2]) : 0;
    HTTPConnection *conn = pd_network->http->newConnection(str, port, usessl);
    ret = conn ? JS_NewInt64(js, (int64_t)(intptr_t)conn) : JS_NULL;
)

JSC_CCALL(http_retain,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    HTTPConnection *conn = js2http(js, argv[0]);
    HTTPConnection *ret_conn = pd_network->http->retain(conn);
    return ret_conn ? JS_NewInt64(js, (int64_t)(intptr_t)ret_conn) : JS_NULL;
)

JSC_CCALL(http_release,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    HTTPConnection *conn = js2http(js, argv[0]);
    if (conn) pd_network->http->release(conn);
    return JS_NULL;
)

JSC_CCALL(http_setConnectTimeout,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->http->setConnectTimeout(js2http(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(http_setKeepAlive,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->http->setKeepAlive(js2http(js, argv[0]), JS_ToBool(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(http_setByteRange,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->http->setByteRange(js2http(js, argv[0]), (int)js2number(js, argv[1]), (int)js2number(js, argv[2]));
    return JS_NULL;
)

JSC_SCALL(http_get,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    HTTPConnection *conn = js2http(js, argv[0]);
    const char *headers = argc > 2 ? JS_ToCString(js, argv[2]) : NULL;
    size_t hlen = headers ? strlen(headers) : 0;
    PDNetErr err = pd_network->http->get(conn, str, headers, hlen);
    if (headers) JS_FreeCString(js, headers);
    ret = JS_NewInt32(js, err);
)

JSC_SCALL(http_post,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    HTTPConnection *conn = js2http(js, argv[0]);
    const char *headers = argc > 2 ? JS_ToCString(js, argv[2]) : NULL;
    size_t hlen = headers ? strlen(headers) : 0;
    size_t blen;
    const char *body = argc > 3 ? js_get_blob_data(js, &blen, argv[3]) : NULL;
    if (body == (void*)-1) body = NULL;
    PDNetErr err = pd_network->http->post(conn, str, headers, hlen, body, body ? blen : 0);
    if (headers) JS_FreeCString(js, headers);
    ret = JS_NewInt32(js, err);
)

JSC_CCALL(http_getError,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt32(js, pd_network->http->getError(js2http(js, argv[0])));
)

JSC_CCALL(http_getProgress,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    int read, total;
    pd_network->http->getProgress(js2http(js, argv[0]), &read, &total);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "read", JS_NewInt32(js, read));
    JS_SetPropertyStr(js, obj, "total", JS_NewInt32(js, total));
    return obj;
)

JSC_CCALL(http_getResponseStatus,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt32(js, pd_network->http->getResponseStatus(js2http(js, argv[0])));
)

JSC_CCALL(http_getBytesAvailable,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt64(js, pd_network->http->getBytesAvailable(js2http(js, argv[0])));
)

JSC_CCALL(http_setReadTimeout,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->http->setReadTimeout(js2http(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(http_setReadBufferSize,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->http->setReadBufferSize(js2http(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(http_read,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    HTTPConnection *conn = js2http(js, argv[0]);
    unsigned int buflen = (unsigned int)js2number(js, argv[1]);
    void *buf = malloc(buflen);
    if (!buf) return JS_RaiseDisrupt(js, "malloc failed");
    int read = pd_network->http->read(conn, buf, buflen);
    if (read < 0) {
        free(buf);
        return JS_NULL;
    }
    JSValue blob = js_new_blob_stoned_copy(js, buf, read);
    free(buf);
    return blob;
)

JSC_CCALL(http_close,
    if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->http->close(js2http(js, argv[0]));
    return JS_NULL;
)

// --- TCP Functions ---

JSC_SCALL(tcp_newConnection,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    int port = (int)js2number(js, argv[1]);
    int usessl = argc > 2 ? JS_ToBool(js, argv[2]) : 0;
    TCPConnection *conn = pd_network->tcp->newConnection(str, port, usessl);
    ret = conn ? JS_NewInt64(js, (int64_t)(intptr_t)conn) : JS_NULL;
)

JSC_CCALL(tcp_retain,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    TCPConnection *conn = js2tcp(js, argv[0]);
    TCPConnection *ret_conn = pd_network->tcp->retain(conn);
    return ret_conn ? JS_NewInt64(js, (int64_t)(intptr_t)ret_conn) : JS_NULL;
)

JSC_CCALL(tcp_release,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    TCPConnection *conn = js2tcp(js, argv[0]);
    if (conn) pd_network->tcp->release(conn);
    return JS_NULL;
)

JSC_CCALL(tcp_getError,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt32(js, pd_network->tcp->getError(js2tcp(js, argv[0])));
)

JSC_CCALL(tcp_setConnectTimeout,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->tcp->setConnectTimeout(js2tcp(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(tcp_close,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt32(js, pd_network->tcp->close(js2tcp(js, argv[0])));
)

JSC_CCALL(tcp_setReadTimeout,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->tcp->setReadTimeout(js2tcp(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(tcp_setReadBufferSize,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    pd_network->tcp->setReadBufferSize(js2tcp(js, argv[0]), (int)js2number(js, argv[1]));
    return JS_NULL;
)

JSC_CCALL(tcp_getBytesAvailable,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    return JS_NewInt64(js, pd_network->tcp->getBytesAvailable(js2tcp(js, argv[0])));
)

JSC_CCALL(tcp_read,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    TCPConnection *conn = js2tcp(js, argv[0]);
    size_t len = (size_t)js2number(js, argv[1]);
    void *buf = malloc(len);
    if (!buf) return JS_RaiseDisrupt(js, "malloc failed");
    int read = pd_network->tcp->read(conn, buf, len);
    if (read < 0) {
        free(buf);
        return JS_NewInt32(js, read); // Return error code
    }
    JSValue blob = js_new_blob_stoned_copy(js, buf, read);
    free(buf);
    return blob;
)

JSC_CCALL(tcp_write,
    if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
    TCPConnection *conn = js2tcp(js, argv[0]);
    size_t len;
    const void *data = js_get_blob_data(js, &len, argv[1]);
    if (data == (void*)-1) return JS_EXCEPTION;
    return JS_NewInt32(js, pd_network->tcp->write(conn, data, len));
)

static const JSCFunctionListEntry js_network_funcs[] = {
    MIST_FUNC_DEF(network, getStatus, 0),
    MIST_FUNC_DEF(network, setEnabled, 1),
    // HTTP
    MIST_FUNC_DEF(http, newConnection, 3),
    MIST_FUNC_DEF(http, retain, 1),
    MIST_FUNC_DEF(http, release, 1),
    MIST_FUNC_DEF(http, setConnectTimeout, 2),
    MIST_FUNC_DEF(http, setKeepAlive, 2),
    MIST_FUNC_DEF(http, setByteRange, 3),
    MIST_FUNC_DEF(http, get, 3),
    MIST_FUNC_DEF(http, post, 4),
    MIST_FUNC_DEF(http, getError, 1),
    MIST_FUNC_DEF(http, getProgress, 1),
    MIST_FUNC_DEF(http, getResponseStatus, 1),
    MIST_FUNC_DEF(http, getBytesAvailable, 1),
    MIST_FUNC_DEF(http, setReadTimeout, 2),
    MIST_FUNC_DEF(http, setReadBufferSize, 2),
    MIST_FUNC_DEF(http, read, 2),
    MIST_FUNC_DEF(http, close, 1),
    // TCP
    MIST_FUNC_DEF(tcp, newConnection, 3),
    MIST_FUNC_DEF(tcp, retain, 1),
    MIST_FUNC_DEF(tcp, release, 1),
    MIST_FUNC_DEF(tcp, getError, 1),
    MIST_FUNC_DEF(tcp, setConnectTimeout, 2),
    MIST_FUNC_DEF(tcp, close, 1),
    MIST_FUNC_DEF(tcp, setReadTimeout, 2),
    MIST_FUNC_DEF(tcp, setReadBufferSize, 2),
    MIST_FUNC_DEF(tcp, getBytesAvailable, 1),
    MIST_FUNC_DEF(tcp, read, 2),
    MIST_FUNC_DEF(tcp, write, 2),
};

JSValue js_network_use(JSContext *js) {
    JSValue mod = JS_NewObject(js);
    JS_SetPropertyFunctionList(js, mod, js_network_funcs, countof(js_network_funcs));
    // Export constants
    JS_SetPropertyStr(js, mod, "NET_OK", JS_NewInt32(js, NET_OK));
    JS_SetPropertyStr(js, mod, "NET_NO_DEVICE", JS_NewInt32(js, NET_NO_DEVICE));
    JS_SetPropertyStr(js, mod, "NET_BUSY", JS_NewInt32(js, NET_BUSY));
    JS_SetPropertyStr(js, mod, "NET_WRITE_ERROR", JS_NewInt32(js, NET_WRITE_ERROR));
    JS_SetPropertyStr(js, mod, "NET_READ_ERROR", JS_NewInt32(js, NET_READ_ERROR));
    JS_SetPropertyStr(js, mod, "NET_CONNECTION_CLOSED", JS_NewInt32(js, NET_CONNECTION_CLOSED));
    JS_SetPropertyStr(js, mod, "WIFI_NOT_CONNECTED", JS_NewInt32(js, kWifiNotConnected));
    JS_SetPropertyStr(js, mod, "WIFI_CONNECTED", JS_NewInt32(js, kWifiConnected));
    JS_SetPropertyStr(js, mod, "WIFI_NOT_AVAILABLE", JS_NewInt32(js, kWifiNotAvailable));
    return mod;
}
