#include <jni.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "{{ module_name }}.h"

static inline void boltffi_free_buf(uint8_t* ptr, size_t len) {
    FfiBuf_u8 buf = { ptr, len, len };
    {{ prefix }}_free_buf_u8(buf);
}

{%- if has_async %}
static JavaVM* g_jvm = NULL;
static jclass g_callback_class = NULL;
static jmethodID g_callback_method = NULL;
static jint boltffi_attach_current_thread(JavaVM* vm, JNIEnv** env) {
#if defined(__ANDROID__)
    return (*vm)->AttachCurrentThread(vm, env, NULL);
#else
    return (*vm)->AttachCurrentThread(vm, (void**)env, NULL);
#endif
}

{%- for cb in callback_traits %}
static void init_{{ cb.trait_name }}_callbacks(JNIEnv* env);
{%- endfor %}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    g_jvm = vm;
    JNIEnv* env;
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    jclass local_class = (*env)->FindClass(env, "{{ package_path }}/{{ class_name }}Kt");
    if (local_class == NULL) {
        return JNI_ERR;
    }
    g_callback_class = (*env)->NewGlobalRef(env, local_class);
    g_callback_method = (*env)->GetStaticMethodID(env, g_callback_class, "boltffiFutureContinuationCallback", "(JB)V");
    if (g_callback_method == NULL) {
        return JNI_ERR;
    }
{%- for cb in callback_traits %}
    init_{{ cb.trait_name }}_callbacks(env);
{%- endfor %}
    return JNI_VERSION_1_6;
}

static void boltffi_jni_continuation_callback(uint64_t handle, int8_t poll_result) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
            return;
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
        return;
    }
    (*env)->CallStaticVoidMethod(env, g_callback_class, g_callback_method, (jlong)handle, (jbyte)poll_result);
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
}
{%- endif %}

{%- for trampoline in closure_trampolines %}
static jclass g_{{ trampoline.signature_id }}_class = NULL;
static jmethodID g_{{ trampoline.signature_id }}_call = NULL;

static void {{ trampoline.trampoline_name }}(void* user_data{{ trampoline.c_params }}) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
            return;
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
        return;
    }
    jlong handle = (jlong)(uintptr_t)user_data;
{%- for rp in trampoline.record_params %}
    jobject buf_p{{ rp.index }} = (*env)->NewDirectByteBuffer(env, (void*)p{{ rp.index }}_ptr, (jlong)p{{ rp.index }}_len);
{%- endfor %}
    if (g_{{ trampoline.signature_id }}_class == NULL) {
        jclass local_class = (*env)->FindClass(env, "{{ trampoline.callbacks_class_jni_path }}");
        if (local_class == NULL) {
            if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
            return;
        }
        g_{{ trampoline.signature_id }}_class = (*env)->NewGlobalRef(env, local_class);
        g_{{ trampoline.signature_id }}_call = (*env)->GetStaticMethodID(env, g_{{ trampoline.signature_id }}_class, "call", "(J{{ trampoline.jni_params_signature }})V");
    }
    (*env)->CallStaticVoidMethod(env, g_{{ trampoline.signature_id }}_class, g_{{ trampoline.signature_id }}_call, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
}
{%- endfor %}

{%- for wire_fn in wire_functions %}
{{ wire_fn }}
{%- endfor %}

JNIEXPORT jstring JNICALL Java_{{ jni_prefix }}_Native_{{ prefix }}_1last_1error_1message(JNIEnv *env, jclass cls) {
    FfiString out = { 0 };
    FfiStatus status = {{ prefix }}_last_error_message(&out);
    if (status.code != 0) {
        return (*env)->NewStringUTF(env, "");
    }
    if (out.ptr == NULL || out.len == 0) {
        {{ prefix }}_free_string(out);
        return (*env)->NewStringUTF(env, "");
    }
    char* tmp = (char*)malloc(out.len + 1);
    if (!tmp) {
        {{ prefix }}_free_string(out);
        return (*env)->NewStringUTF(env, "");
    }
    memcpy(tmp, out.ptr, out.len);
    tmp[out.len] = '\0';
    jstring result = (*env)->NewStringUTF(env, tmp);
    free(tmp);
    {{ prefix }}_free_string(out);
    return result;
}


{%- for func in functions %}

JNIEXPORT {{ func.jni_return }} JNICALL {{ func.jni_name }}(JNIEnv *env, jclass cls{{ func.jni_params }}) {
{%- if func.return_kind.is_void() %}
    {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- else %}
    {{ func.jni_return }} _result = {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- if func.jni_return == "jboolean" %}
    return (jboolean)_result;
{%- else %}
    return _result;
{%- endif %}
{%- endif %}
}
{%- endfor %}

{%- for class in classes %}
{%- for ctor in class.ctors %}

{{ ctor }}
{%- endfor %}

JNIEXPORT void JNICALL Java_{{ class.jni_prefix }}_Native_{{ class.jni_ffi_prefix }}_1free(JNIEnv *env, jclass cls, jlong handle) {
    if (handle != 0) {{ class.ffi_prefix }}_free((void*)handle);
}

{%- for wire_method in class.wire_methods %}
{{ wire_method }}
{%- endfor %}

{%- for func in class.async_methods %}

JNIEXPORT jlong JNICALL {{ func.jni_create_name }}(JNIEnv *env, jclass cls, jlong handle{{ func.jni_params }}) {
    if (handle == 0) return 0;
{%- for param in func.params %}
{%- if param.is_string() %}
    const char* _{{ param.name }}_c = {{ param.name }} ? (*env)->GetStringUTFChars(env, {{ param.name }}, NULL) : NULL;
{%- endif %}
{%- if param.is_primitive_array() %}
    uintptr_t _{{ param.name }}_len = (uintptr_t)(*env)->GetArrayLength(env, {{ param.name }});
    {{ param.array_c_type() }}* _{{ param.name }}_ptr = ({{ param.array_c_type() }}*)(*env)->GetPrimitiveArrayCritical(env, {{ param.name }}, NULL);
{%- endif %}
{%- if param.is_buffer() %}
    jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
    uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
    uintptr_t _{{ param.name }}_len = (_{{ param.name }}_ptr && _{{ param.name }}_size > 0) ? (uintptr_t)_{{ param.name }}_size : 0;
{%- endif %}
{%- endfor %}
    RustFutureHandle _handle = {{ func.ffi_name }}((void*)handle{% for param in func.params %}, {{ param.ffi_arg() }}{% endfor %});
{%- for param in func.params %}
{%- if param.is_primitive_array() %}
    (*env)->ReleasePrimitiveArrayCritical(env, {{ param.name }}, _{{ param.name }}_ptr, {{ param.array_release_mode() }});
{%- endif %}
{%- if param.is_string() %}
    if ({{ param.name }}) (*env)->ReleaseStringUTFChars(env, {{ param.name }}, _{{ param.name }}_c);
{%- endif %}
{%- endfor %}
    return (jlong)_handle;
}

JNIEXPORT void JNICALL {{ func.jni_poll_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future, jlong contHandle) {
    {{ func.ffi_poll }}((RustFutureHandle)future, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}

JNIEXPORT {{ func.complete_kind.jni_return() }} JNICALL {{ func.jni_complete_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
{%- if func.complete_kind.is_void() %}
    FfiStatus _status;
    {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
{%- elif func.complete_kind.is_wire_encoded() %}
    FfiStatus _status;
    FfiBuf_u8 _buf = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    if (_status.code != 0 || _buf.ptr == NULL) return NULL;
    jbyteArray _arr = (*env)->NewByteArray(env, (jint)_buf.len);
    (*env)->SetByteArrayRegion(env, _arr, 0, (jint)_buf.len, (const jbyte*)_buf.ptr);
    boltffi_free_buf(_buf.ptr, _buf.len);
    return _arr;
{%- else %}
    FfiStatus _status;
    {{ func.complete_kind.c_type() }} _result = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    return ({{ func.complete_kind.jni_return() }})_result;
{%- endif %}
}

JNIEXPORT void JNICALL {{ func.jni_cancel_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
    {{ func.ffi_cancel }}((RustFutureHandle)future);
}

JNIEXPORT void JNICALL {{ func.jni_free_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
    {{ func.ffi_free }}((RustFutureHandle)future);
}
{%- endfor %}
{%- for stream in class.streams %}

JNIEXPORT jlong JNICALL {{ stream.subscribe_jni }}(JNIEnv *env, jclass cls, jlong handle) {
    if (handle == 0) return 0;
    SubscriptionHandle _subscription = {{ stream.subscribe_ffi }}((void*)handle);
    return (jlong)_subscription;
}

JNIEXPORT void JNICALL {{ stream.poll_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jlong contHandle) {
    if (subscription_handle == 0) return;
    {{ stream.poll_ffi }}((SubscriptionHandle)subscription_handle, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}

JNIEXPORT jbyteArray JNICALL {{ stream.pop_batch_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jlong max_count) {
    if (subscription_handle == 0) return NULL;
    FfiBuf_u8 _buf = {{ stream.pop_batch_ffi }}((SubscriptionHandle)subscription_handle, (uintptr_t)max_count);
    if (_buf.ptr == NULL || _buf.len == 0) {
        if (_buf.ptr != NULL) boltffi_free_buf(_buf.ptr, _buf.len);
        return NULL;
    }
    jbyteArray _arr = (*env)->NewByteArray(env, (jint)_buf.len);
    (*env)->SetByteArrayRegion(env, _arr, 0, (jint)_buf.len, (const jbyte*)_buf.ptr);
    boltffi_free_buf(_buf.ptr, _buf.len);
    return _arr;
}

JNIEXPORT jint JNICALL {{ stream.wait_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jint timeout) {
    if (subscription_handle == 0) return -1;
    int32_t _result = {{ stream.wait_ffi }}((SubscriptionHandle)subscription_handle, (uint32_t)timeout);
    return (jint)_result;
}

JNIEXPORT void JNICALL {{ stream.unsubscribe_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle) {
    if (subscription_handle == 0) return;
    {{ stream.unsubscribe_ffi }}((SubscriptionHandle)subscription_handle);
}

JNIEXPORT void JNICALL {{ stream.free_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle) {
    if (subscription_handle == 0) return;
    {{ stream.free_ffi }}((SubscriptionHandle)subscription_handle);
}
{%- endfor %}
{%- endfor %}

{%- for func in async_functions %}

JNIEXPORT jlong JNICALL {{ func.jni_create_name }}(JNIEnv *env, jclass cls{{ func.jni_params }}) {
{%- for param in func.params %}
{%- if param.is_string() %}
    const char* _{{ param.name }}_c = {{ param.name }} ? (*env)->GetStringUTFChars(env, {{ param.name }}, NULL) : NULL;
{%- endif %}
{%- if param.is_primitive_array() %}
    uintptr_t _{{ param.name }}_len = (uintptr_t)(*env)->GetArrayLength(env, {{ param.name }});
    {{ param.array_c_type() }}* _{{ param.name }}_ptr = ({{ param.array_c_type() }}*)(*env)->GetPrimitiveArrayCritical(env, {{ param.name }}, NULL);
{%- endif %}
{%- if param.is_buffer() %}
    jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
    uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
    uintptr_t _{{ param.name }}_len = (_{{ param.name }}_ptr && _{{ param.name }}_size > 0) ? (uintptr_t)_{{ param.name }}_size : 0;
{%- endif %}
{%- endfor %}
    RustFutureHandle _handle = {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- for param in func.params %}
{%- if param.is_primitive_array() %}
    (*env)->ReleasePrimitiveArrayCritical(env, {{ param.name }}, _{{ param.name }}_ptr, {{ param.array_release_mode() }});
{%- endif %}
{%- if param.is_string() %}
    if ({{ param.name }}) (*env)->ReleaseStringUTFChars(env, {{ param.name }}, _{{ param.name }}_c);
{%- endif %}
{%- endfor %}
    return (jlong)_handle;
}

JNIEXPORT void JNICALL {{ func.jni_poll_name }}(JNIEnv *env, jclass cls, jlong future, jlong contHandle) {
    {{ func.ffi_poll }}((RustFutureHandle)future, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}

JNIEXPORT {{ func.complete_kind.jni_return() }} JNICALL {{ func.jni_complete_name }}(JNIEnv *env, jclass cls, jlong future) {
{%- if func.complete_kind.is_void() %}
    FfiStatus _status;
    {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
{%- elif func.complete_kind.is_wire_encoded() %}
    FfiStatus _status;
    FfiBuf_u8 _buf = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    if (_status.code != 0 || _buf.ptr == NULL) return NULL;
    jbyteArray _arr = (*env)->NewByteArray(env, (jint)_buf.len);
    (*env)->SetByteArrayRegion(env, _arr, 0, (jint)_buf.len, (const jbyte*)_buf.ptr);
    boltffi_free_buf(_buf.ptr, _buf.len);
    return _arr;
{%- else %}
    FfiStatus _status;
    {{ func.complete_kind.c_type() }} _result = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    return ({{ func.complete_kind.jni_return() }})_result;
{%- endif %}
}

JNIEXPORT void JNICALL {{ func.jni_cancel_name }}(JNIEnv *env, jclass cls, jlong future) {
    {{ func.ffi_cancel }}((RustFutureHandle)future);
}

JNIEXPORT void JNICALL {{ func.jni_free_name }}(JNIEnv *env, jclass cls, jlong future) {
    {{ func.ffi_free }}((RustFutureHandle)future);
}
{%- endfor %}

{%- for cb in callback_traits %}

static jclass g_{{ cb.trait_name }}_callbacks_class = NULL;
static jmethodID g_{{ cb.trait_name }}_free_method = NULL;
static jmethodID g_{{ cb.trait_name }}_clone_method = NULL;
{%- for method in cb.sync_methods %}
static jmethodID g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- for method in cb.async_methods %}
static jmethodID g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}

static void {{ cb.trait_name }}_vtable_free(uint64_t handle) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return;
        attached = 1;
    } else if (get_env_result != JNI_OK) return;
    (*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_free_method, (jlong)handle);
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}

static uint64_t {{ cb.trait_name }}_vtable_clone(uint64_t handle) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return 0;
        attached = 1;
    } else if (get_env_result != JNI_OK) return 0;
    jlong result = (*env)->CallStaticLongMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_clone_method, (jlong)handle);
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
    return (uint64_t)result;
}

{%- for method in cb.sync_methods %}

static void {{ cb.trait_name }}_vtable_{{ method.ffi_name }}(uint64_t handle{% for param in method.c_params %}, {{ param.c_type }} {{ param.name }}{% endfor %}{% if method.has_return() %}, {{ method.c_return_type() }}* out{% endif %}, FfiStatus* status) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
            status->code = 1;
            return;
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
        status->code = 1;
        return;
    }
{%- for line in method.setup_lines %}
    {{ line }}
{%- endfor %}
{%- if method.has_return() %}
    {{ method.jni_return_type() }} _result = (*env)->CallStatic{{ method.jni_call_type() }}Method(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
    *out = ({{ method.c_return_type() }})_result;
{%- else %}
    (*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
{%- endif %}
    status->code = 0;
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}
{%- endfor %}
{%- for method in cb.async_methods %}

static void {{ cb.trait_name }}_vtable_{{ method.ffi_name }}(uint64_t handle{% for param in method.c_params %}, {{ param.c_type }} {{ param.name }}{% endfor %}, void (*callback)(uint64_t{% if method.is_wire() %}, const uint8_t*, size_t{% elif method.has_return() %}, {{ method.return_c_type() }}{% endif %}, FfiStatus), uint64_t callback_data) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return;
        attached = 1;
    } else if (get_env_result != JNI_OK) return;
{%- for line in method.setup_lines %}
    {{ line }}
{%- endfor %}
    (*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %}, (jlong)callback, (jlong)callback_data);
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}
{%- endfor %}

static {{ cb.vtable_type }} g_{{ cb.trait_name }}_vtable = {
    .free = {{ cb.trait_name }}_vtable_free,
    .clone = {{ cb.trait_name }}_vtable_clone,
{%- for method in cb.sync_methods %}
    .{{ method.ffi_name }} = {{ cb.trait_name }}_vtable_{{ method.ffi_name }},
{%- endfor %}
{%- for method in cb.async_methods %}
    .{{ method.ffi_name }} = {{ cb.trait_name }}_vtable_{{ method.ffi_name }},
{%- endfor %}
};

static void init_{{ cb.trait_name }}_callbacks(JNIEnv* env) {
    jclass local_class = (*env)->FindClass(env, "{{ cb.callbacks_class }}");
    if (local_class == NULL) return;
    g_{{ cb.trait_name }}_callbacks_class = (*env)->NewGlobalRef(env, local_class);
    g_{{ cb.trait_name }}_free_method = (*env)->GetStaticMethodID(env, g_{{ cb.trait_name }}_callbacks_class, "free", "(J)V");
    g_{{ cb.trait_name }}_clone_method = (*env)->GetStaticMethodID(env, g_{{ cb.trait_name }}_callbacks_class, "clone", "(J)J");
{%- for method in cb.sync_methods %}
    g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = (*env)->GetStaticMethodID(env, g_{{ cb.trait_name }}_callbacks_class, "{{ method.jni_method_name }}", "{{ method.jni_signature }}");
{%- endfor %}
{%- for method in cb.async_methods %}
    g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = (*env)->GetStaticMethodID(env, g_{{ cb.trait_name }}_callbacks_class, "{{ method.jni_method_name }}", "{{ method.jni_signature }}");
{%- endfor %}
    {{ cb.register_fn }}(&g_{{ cb.trait_name }}_vtable);
}
{%- endfor %}

{%- for invoker in async_callback_invokers %}

JNIEXPORT void JNICALL {{ invoker.jni_fn_name }}(JNIEnv* env, jclass cls, jlong callback_ptr, jlong callback_data{% if invoker.has_result() %}, {{ invoker.jni_result_type() }} result{% endif %}) {
{%- if invoker.is_wire() %}
    void (*callback)(uint64_t, const uint8_t*, size_t, FfiStatus) = (void (*)(uint64_t, const uint8_t*, size_t, FfiStatus))callback_ptr;
    if (result == NULL) {
        callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
        return;
    }
    jsize _len = (*env)->GetArrayLength(env, result);
    jbyte* _ptr = (*env)->GetByteArrayElements(env, result, NULL);
    callback((uint64_t)callback_data, (const uint8_t*)_ptr, (size_t)_len, (FfiStatus){.code = 0});
    (*env)->ReleaseByteArrayElements(env, result, _ptr, JNI_ABORT);
{%- elif invoker.has_result() %}
    (void)env; (void)cls;
    void (*callback)(uint64_t, {{ invoker.c_result_type() }}, FfiStatus) = (void (*)(uint64_t, {{ invoker.c_result_type() }}, FfiStatus))callback_ptr;
    callback((uint64_t)callback_data, ({{ invoker.c_result_type() }})result, (FfiStatus){.code = 0});
{%- else %}
    (void)env; (void)cls;
    void (*callback)(uint64_t, FfiStatus) = (void (*)(uint64_t, FfiStatus))callback_ptr;
    callback((uint64_t)callback_data, (FfiStatus){.code = 0});
{%- endif %}
}
{%- endfor %}
