#include <jni.h>
#include <limits.h>
#include <string.h>
#include "sqs_map.h"

static void fail(JNIEnv *env, const char *message) {
    const size_t length = strlen(message);
    jbyteArray bytes = (*env)->NewByteArray(env, (jsize)length);
    if (!bytes) return;
    (*env)->SetByteArrayRegion(env, bytes, 0, (jsize)length, (const jbyte *)message);
    jclass strings = (*env)->FindClass(env, "java/lang/String");
    jmethodID string_ctor = (*env)->GetMethodID(env, strings, "<init>", "([BLjava/lang/String;)V");
    jstring encoding = (*env)->NewStringUTF(env, "UTF-8");
    jobject text = (*env)->NewObject(env, strings, string_ctor, bytes, encoding);
    jclass errors = (*env)->FindClass(env, "java/lang/IllegalStateException");
    jmethodID error_ctor = (*env)->GetMethodID(env, errors, "<init>", "(Ljava/lang/String;)V");
    jthrowable error = (*env)->NewObject(env, errors, error_ctor, text);
    if (error) (*env)->Throw(env, error);
}
static int checked(JNIEnv *env, int32_t status) {
    if (status == SQSM_MAP_OK) return 1;
    const char *message = sqsm_map_last_error();
    fail(env, message && *message ? message : "Native map operation failed");
    return 0;
}
static jbyteArray output(JNIEnv *env, int32_t status, sqsm_map_bytes_t *value) {
    jbyteArray result = NULL;
    if (checked(env, status)) {
        if (value->len > INT_MAX) fail(env, "Native output exceeds Java array capacity");
        else {
            result = (*env)->NewByteArray(env, (jsize)value->len);
            if (result && value->len) (*env)->SetByteArrayRegion(env, result, 0, (jsize)value->len, (const jbyte *)value->data);
        }
    }
    if (value->allocation) {
        int32_t released = sqsm_map_bytes_free(value->allocation);
        if (!(*env)->ExceptionCheck(env)) checked(env, released);
    }
    return result;
}
JNIEXPORT jlongArray JNICALL Java_io_sqsmap_sdk_NativeBindings_versions(JNIEnv *env, jclass cls) {
    (void)cls;
    jlong values[] = {sqsm_map_api_version(), sqsm_map_script_session_api_version(), sqsm_map_runtime_flags()};
    jlongArray result = (*env)->NewLongArray(env, 3);
    if (result) (*env)->SetLongArrayRegion(env, result, 0, 3, values);
    return result;
}
JNIEXPORT jlong JNICALL Java_io_sqsmap_sdk_NativeBindings_create(JNIEnv *env, jclass cls, jint width, jint height) {
    (void)cls;
    if (width <= 0 || height <= 0) {fail(env, "Viewport must be positive"); return 0;}
    sqsm_map_t map = 0;
    return checked(env, sqsm_map_create((uint32_t)width, (uint32_t)height, &map)) ? (jlong)map : 0;
}
JNIEXPORT void JNICALL Java_io_sqsmap_sdk_NativeBindings_destroy(JNIEnv *env, jclass cls, jlong map) {
    (void)cls; checked(env, sqsm_map_destroy((sqsm_map_t)map));
}
JNIEXPORT void JNICALL Java_io_sqsmap_sdk_NativeBindings_resize(JNIEnv *env, jclass cls, jlong map, jint width, jint height) {
    (void)cls;
    if (width <= 0 || height <= 0) {fail(env, "Viewport must be positive"); return;}
    checked(env, sqsm_map_resize((sqsm_map_t)map, (uint32_t)width, (uint32_t)height));
}
JNIEXPORT void JNICALL Java_io_sqsmap_sdk_NativeBindings_setViewport(JNIEnv *env,jclass cls,jlong map,jint width,jint height,jdouble ratio){
    (void)cls;if(width<=0||height<=0){fail(env,"Logical viewport must be positive");return;}
    checked(env,sqsm_map_set_viewport((sqsm_map_t)map,(uint32_t)width,(uint32_t)height,(double)ratio));
}
JNIEXPORT jbyteArray JNICALL Java_io_sqsmap_sdk_NativeBindings_read(JNIEnv *env, jclass cls, jlong map, jint channel) {
    (void)cls; sqsm_map_bytes_t value = {0}; int32_t status;
    switch (channel) {
        case 0: status = sqsm_map_state_json((sqsm_map_t)map, &value); break;
        case 1: status = sqsm_map_resource_requests_json((sqsm_map_t)map, 32, &value); break;
        case 2: status = sqsm_map_poll_events_json((sqsm_map_t)map, &value); break;
        case 3: status = sqsm_map_capabilities_json((sqsm_map_t)map, &value); break;
        case 4: status = sqsm_map_render_rgba((sqsm_map_t)map, &value); break;
        case 5: status = sqsm_map_attributions_json((sqsm_map_t)map, &value); break;
        case 6: status = sqsm_map_get_style_json((sqsm_map_t)map, &value); break;
        default: fail(env, "Unknown map read channel"); return NULL;
    }
    return output(env, status, &value);
}
JNIEXPORT jbyteArray JNICALL Java_io_sqsmap_sdk_NativeBindings_command(JNIEnv *env, jclass cls, jlong map, jint channel, jbyteArray json) {
    (void)cls;
    if (!json) {fail(env, "Command requires UTF-8 JSON"); return NULL;}
    jsize length = (*env)->GetArrayLength(env, json);
    jbyte *data = (*env)->GetByteArrayElements(env, json, NULL);
    if (!data) return NULL;
    sqsm_map_bytes_t value = {0}; int32_t status;
    switch (channel) {
        case 0: status = sqsm_map_camera_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 1: status = sqsm_map_pointer_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 2: status = sqsm_map_gesture_input_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 3: status = sqsm_map_resource_policy_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 4: status = sqsm_map_source_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 5: status = sqsm_map_layer_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 6: status = sqsm_map_image_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 8: status = sqsm_map_gesture_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        case 7: status = sqsm_map_scene_json((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &value); break;
        default: (*env)->ReleaseByteArrayElements(env, json, data, JNI_ABORT); fail(env, "Unknown map command channel"); return NULL;
    }
    (*env)->ReleaseByteArrayElements(env, json, data, JNI_ABORT);
    return output(env, status, &value);
}
JNIEXPORT jboolean JNICALL Java_io_sqsmap_sdk_NativeBindings_respond(JNIEnv *env, jclass cls, jlong map, jbyteArray metadata, jbyteArray body) {
    (void)cls;
    if (!metadata || !body) {fail(env, "Response requires metadata and body"); return JNI_FALSE;}
    jsize metadata_len = (*env)->GetArrayLength(env, metadata), body_len = (*env)->GetArrayLength(env, body);
    jbyte *meta = (*env)->GetByteArrayElements(env, metadata, NULL);
    if (!meta) return JNI_FALSE;
    jbyte *data = (*env)->GetByteArrayElements(env, body, NULL);
    if (!data) {(*env)->ReleaseByteArrayElements(env, metadata, meta, JNI_ABORT); return JNI_FALSE;}
    uint32_t accepted = 0;
    int32_t status = sqsm_map_resource_response((sqsm_map_t)map, (const uint8_t *)meta, (uint64_t)metadata_len, (const uint8_t *)data, (uint64_t)body_len, &accepted);
    (*env)->ReleaseByteArrayElements(env, body, data, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, metadata, meta, JNI_ABORT);
    return checked(env, status) && accepted ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jlong JNICALL Java_io_sqsmap_sdk_NativeBindings_start(JNIEnv *env, jclass cls, jlong map, jbyteArray source) {
    (void)cls;
    if (!source) {fail(env, "Script requires UTF-8 module"); return 0;}
    jsize length = (*env)->GetArrayLength(env, source);
    jbyte *data = (*env)->GetByteArrayElements(env, source, NULL);
    if (!data) return 0;
    sqsm_map_script_session_t session = 0;
    int32_t status = sqsm_map_script_session_start((sqsm_map_t)map, (const uint8_t *)data, (uint64_t)length, &session);
    (*env)->ReleaseByteArrayElements(env, source, data, JNI_ABORT);
    return checked(env, status) ? (jlong)session : 0;
}
JNIEXPORT jbyteArray JNICALL Java_io_sqsmap_sdk_NativeBindings_pump(JNIEnv *env, jclass cls, jlong session) {
    (void)cls; sqsm_map_bytes_t value = {0};
    int32_t status = sqsm_map_script_session_pump_json((sqsm_map_script_session_t)session, 32, &value);
    return output(env, status, &value);
}
JNIEXPORT jbyteArray JNICALL Java_io_sqsmap_sdk_NativeBindings_sessionState(JNIEnv *env, jclass cls, jlong session) {
    (void)cls; sqsm_map_bytes_t value = {0};
    int32_t status = sqsm_map_script_session_state_json((sqsm_map_script_session_t)session, &value);
    return output(env, status, &value);
}
JNIEXPORT void JNICALL Java_io_sqsmap_sdk_NativeBindings_stop(JNIEnv *env, jclass cls, jlong session) {
    (void)cls; checked(env, sqsm_map_script_session_stop((sqsm_map_script_session_t)session));
}
JNIEXPORT void JNICALL Java_io_sqsmap_sdk_NativeBindings_release(JNIEnv *env, jclass cls, jlong session) {
    (void)cls; checked(env, sqsm_map_script_session_release((sqsm_map_script_session_t)session));
}
