package {{ package_name }};

{%- if callback.has_async_methods() %}
import java.util.concurrent.CompletableFuture;

{%- endif %}
final class {{ callback.callbacks_class_name() }} {
    private static final java.util.concurrent.ConcurrentHashMap<Long, {{ callback.interface_name }}> HANDLE_MAP = new java.util.concurrent.ConcurrentHashMap<>();
    private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(1L);
{%- if callback.supports_cleaner %}
    private static final java.lang.ref.Cleaner CLEANER = java.lang.ref.Cleaner.create();
{%- endif %}

    private {{ callback.callbacks_class_name() }}() {}

    private static long insert({{ callback.interface_name }} impl_) {
        long handle = COUNTER.getAndAdd(2L);
        HANDLE_MAP.put(handle, impl_);
        return handle;
    }

    public static void free(long handle) {
        HANDLE_MAP.remove(handle);
    }

    public static long clone(long handle) {
        {{ callback.interface_name }} obj = HANDLE_MAP.get(handle);
        return obj != null ? insert(obj) : 0L;
    }

{%- if callback.supports_cleaner %}
    private static final class ProxyReleaser implements Runnable {
        private long handle;

        private ProxyReleaser(long handle) {
            this.handle = handle;
        }

        @Override
        public void run() {
            if (handle == 0L) return;
            Native.{{ callback.proxy_release_native_name() }}(handle);
            handle = 0L;
        }
    }
{%- endif %}

    private static final class Proxy implements {{ callback.interface_name }} {
        private final long handle;
{%- if callback.supports_cleaner %}
        private final java.lang.ref.Cleaner.Cleanable cleanable;
{%- endif %}

        private Proxy(long handle) {
            this.handle = handle;
{%- if callback.supports_cleaner %}
            this.cleanable = CLEANER.register(this, new ProxyReleaser(handle));
{%- endif %}
        }

        long rawHandle() {
            return handle;
        }

{%- if !callback.supports_cleaner %}
        @Override
        protected void finalize() throws Throwable {
            try {
                if (handle != 0L) {
                    Native.{{ callback.proxy_release_native_name() }}(handle);
                }
            } finally {
                super.finalize();
            }
        }
{%- endif %}
{%- for method in callback.sync_methods %}

        @Override
        public {{ method.proxy.return_type }} {{ method.name }}({% for param in method.proxy.params %}{{ param.java_type }} {{ param.name }}{% if !loop.last %}, {% endif %}{% endfor %}) {
{%- let proxy = method.proxy %}
{%- let handle_name = "handle" %}
{%- include "render_java/proxy_sync_method_body.txt" %}
        }
{%- endfor %}
{%- for method in callback.async_methods %}

        @Override
        @SuppressWarnings("unchecked")
        public CompletableFuture<{{ method.boxed_return_type() }}> {{ method.name }}({% for param in method.proxy.params %}{{ param.java_type }} {{ param.name }}{% if !loop.last %}, {% endif %}{% endfor %}) {
            CompletableFuture<{{ method.boxed_return_type() }}> future = new CompletableFuture<>();
            long callbackData = BoltFFICallbackFutureMap.insert(
{%- if !method.proxy.return_plan.is_void() %}
                    value -> future.complete(({{ method.proxy.boxed_return_type() }}) value),
{%- else %}
                    ignored -> future.complete(null),
{%- endif %}
                    future::completeExceptionally
            );
            try {
{%- if method.proxy.wire_writers.is_empty() %}
                Native.{{ method.proxy.native_name }}(handle{% if !method.proxy.params.is_empty() %}, {% endif %}{% for param in method.proxy.params %}{{ param.native_expr }}{% if !loop.last %}, {% endif %}{% endfor %}, callbackData);
{%- else %}
{%- for writer in method.proxy.wire_writers %}
                try (WireWriter {{ writer.binding_name }} = new WireWriter({{ writer.size_expr }})) {
                    {{ writer.encode_expr }};
{%- endfor %}
                    Native.{{ method.proxy.native_name }}(handle{% if !method.proxy.params.is_empty() %}, {% endif %}{% for param in method.proxy.params %}{{ param.native_expr }}{% if !loop.last %}, {% endif %}{% endfor %}, callbackData);
{%- for _writer in method.proxy.wire_writers %}
                }
{%- endfor %}
{%- endif %}
            } catch (Throwable throwable) {
                BoltFFICallbackFutureMap.completeFailure(callbackData, throwable);
            }
            return future;
        }
{%- endfor %}
    }

    static {{ callback.interface_name }} wrap(long handle) {
        {{ callback.interface_name }} existing = HANDLE_MAP.get(handle);
        return existing != null ? existing : new Proxy(handle);
    }
{%- if callback.has_async_methods() %}

    private static Throwable unwrapAsyncThrowable(Throwable throwable) {
        Throwable unwrapped = throwable;
        while (unwrapped instanceof java.util.concurrent.CompletionException
                || unwrapped instanceof java.util.concurrent.ExecutionException) {
            Throwable cause = unwrapped.getCause();
            if (cause == null) {
                return unwrapped;
            }
            unwrapped = cause;
        }
        return unwrapped;
    }
{%- endif %}
{%- for method in callback.sync_methods %}

    public static {% match method.return_info %}{% when Some with (ret) %}{{ ret.jni_type() }}{% when None %}void{% endmatch %} {{ method.ffi_name }}(long handle{% for param in method.params %}, {{ param.jni_type }} {{ param.name }}{% endfor %}) {
        {{ callback.interface_name }} impl_ = HANDLE_MAP.get(handle);
        if (impl_ == null) throw new RuntimeException("invalid callback handle");
{%- match method.return_info %}
{%- when Some with (ret) %}
{%- match ret %}
{%- when crate::render::java::plan::JavaBridgeReturn::Value with (value_return) %}
{%- if value_return.is_encoded() %}
        {{ value_return.java_type }} value = impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %});
        try (WireWriter wire = new WireWriter({{ value_return.encode_size_expr() }})) {
            {{ value_return.encode_expr() }};
            return wire.toByteArray();
        }
{%- else %}
        return {{ value_return.direct_prefix() }}impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %}){{ value_return.direct_suffix() }};
{%- endif %}
{%- when crate::render::java::plan::JavaBridgeReturn::Result with (result_return) %}
        BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result;
        try {
            result = BoltFFIResult.ok(impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %}));
{% if result_return.has_exception_class() %}
        } catch ({{ result_return.exception_class() }} exception) {
            result = BoltFFIResult.err(exception.getError());
{% endif %}
{% if result_return.error_capture.is_string %}
        } catch (RuntimeException exception) {
            result = BoltFFIResult.err(exception.getMessage());
{% endif %}
{% if !result_return.has_exception_class() && !result_return.error_capture.is_string %}
        } catch (Exception exception) {
            throw new RuntimeException("callback returned unexpected error", exception);
{% endif %}
        }
        try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
            {{ result_return.encode_expr }};
            return wire.toByteArray();
        }
{%- endmatch %}
{%- when None %}
        impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- endmatch %}
    }
{%- endfor %}
{%- for method in callback.async_methods %}

    public static void {{ method.ffi_name }}(long handle{% for param in method.params %}, {{ param.jni_type }} {{ param.name }}{% endfor %}, long callbackPtr, long callbackData) {
        {{ callback.interface_name }} impl_ = HANDLE_MAP.get(handle);
        if (impl_ == null) throw new RuntimeException("invalid callback handle");
        CompletableFuture<{{ method.boxed_return_type() }}> future;
        try {
            future = impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %});
        } catch (Throwable throwable) {
            future = new CompletableFuture<>();
            future.completeExceptionally(throwable);
        }
        future.whenComplete((value, throwable) -> {
            if (throwable != null) {
                Throwable callbackThrowable = unwrapAsyncThrowable(throwable);
{% match method.return_info %}
{% when Some with (ret) %}
{% match ret %}
{% when crate::render::java::plan::JavaBridgeReturn::Result with (result_return) %}
{% if result_return.has_exception_class() %}
                if (callbackThrowable instanceof {{ result_return.exception_class() }} exception) {
                    BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result = BoltFFIResult.err(exception.getError());
                    try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
                        {{ result_return.encode_expr }};
                        Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
                    }
                    return;
                }
{% endif %}
{% if result_return.error_capture.is_string %}
                if (callbackThrowable instanceof RuntimeException exception) {
                    BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result = BoltFFIResult.err(exception.getMessage());
                    try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
                        {{ result_return.encode_expr }};
                        Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
                    }
                    return;
                }
{% endif %}
{% when crate::render::java::plan::JavaBridgeReturn::Value with (_value_return) %}
{% endmatch %}
{% when None %}
{% endmatch %}
                Native.{{ method.failure_invoker_name() }}(callbackPtr, callbackData);
                return;
            }
{% match method.return_info %}
{% when Some with (ret) %}
{% match ret %}
{% when crate::render::java::plan::JavaBridgeReturn::Value with (value_return) %}
{% if value_return.is_encoded() %}
            try (WireWriter wire = new WireWriter({{ value_return.encode_size_expr() }})) {
                {{ value_return.encode_expr() }};
                Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
            }
{% else %}
            Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, {{ value_return.direct_prefix() }}value{{ value_return.direct_suffix() }});
{% endif %}
{% when crate::render::java::plan::JavaBridgeReturn::Result with (result_return) %}
            BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result = BoltFFIResult.ok(value);
            try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
                {{ result_return.encode_expr }};
                Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
            }
{% endmatch %}
{% when None %}
            Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData);
{% endmatch %}
        });
    }

    static void {{ method.proxy.success_name }}(long callbackData{% if method.proxy.return_plan.is_void() %}{% elif method.proxy.return_plan.is_direct() || method.proxy.return_plan.is_c_style_enum() %}, {{ method.proxy.return_plan.native_return_type }} value{% elif method.proxy.return_plan.is_handle() || method.proxy.return_plan.is_callback() %}, long value{% else %}, byte[] value{% endif %}) {
{%- if method.proxy.return_plan.is_void() %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, null);
{%- elif method.proxy.return_plan.is_direct() %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, value);
{%- elif method.proxy.return_plan.is_c_style_enum() %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.c_style_enum_class() }}.fromValue(value));
{%- elif method.proxy.return_plan.is_handle() %}
{%- if method.proxy.return_plan.handle_nullable() %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, value == 0L ? null : new {{ method.proxy.return_plan.handle_class() }}(value));
{%- else %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, new {{ method.proxy.return_plan.handle_class() }}(value));
{%- endif %}
{%- elif method.proxy.return_plan.is_callback() %}
{%- if method.proxy.return_plan.callback_nullable() %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, value == 0L ? null : {{ method.proxy.return_plan.callback_bridge_class() }}.wrap(value));
{%- else %}
        BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.callback_bridge_class() }}.wrap(value));
{%- endif %}
{%- elif method.proxy.return_plan.is_decode() %}
        if (value == null) {
            BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException("FFI call returned null buffer"));
            return;
        }
        WireReader reader = new WireReader(value);
        BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.decode_expr() }});
{%- elif method.proxy.return_plan.is_result() %}
        if (value == null) {
            BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException("FFI call returned null buffer"));
            return;
        }
        WireReader reader = new WireReader(value);
        if (reader.readI8() != 0) {
{%- if method.proxy.return_plan.result_err_throws_directly() %}
            BoltFFICallbackFutureMap.completeFailure(callbackData, {{ method.proxy.return_plan.result_err_decode() }});
{%- elif method.proxy.return_plan.result_has_typed_exception() %}
            BoltFFICallbackFutureMap.completeFailure(callbackData, new {{ method.proxy.return_plan.result_err_exception_class() }}({{ method.proxy.return_plan.result_err_decode() }}));
{%- elif method.proxy.return_plan.result_err_is_string() %}
            BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException({{ method.proxy.return_plan.result_err_decode() }}));
{%- else %}
            BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException(String.valueOf({{ method.proxy.return_plan.result_err_decode() }})));
{%- endif %}
            return;
        }
        BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.result_ok_decode() }});
{%- endif %}
    }

    static void {{ method.proxy.failure_name }}(long callbackData) {
        BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException("callback failed"));
    }
{%- endfor %}

    static long create({{ callback.interface_name }} impl_) {
        return insert(impl_);
    }
}
