{{ self::kdoc_block(doc, "") }}/**
 * ### Thread Safety
 *
 * Callback methods may be invoked from any thread and the thread is not guaranteed to be
 * the same between calls. **Your implementation must be thread-safe.**
 * Use `BoltFFIScope.launchOnMain` if updating UI.
 */
{% if is_closure %}fun {% endif %}interface {{ interface_name }} {
{%- for method in sync_methods %}
{{ self::kdoc_block(method.doc, "    ") }}    fun {{ method.name }}({% for param in method.params %}{{ param.name }}: {{ param.kotlin_type }}{% if !loop.last %}, {% endif %}{% endfor %}){% match method.return_info %}{% when Some with (ret) %}: {{ ret.kotlin_type }}{% when None %}{% endmatch %}
{%- endfor %}
{%- for method in async_methods %}
{{ self::kdoc_block(method.doc, "    ") }}    suspend fun {{ method.name }}({% for param in method.params %}{{ param.name }}: {{ param.kotlin_type }}{% if !loop.last %}, {% endif %}{% endfor %}){% match method.return_info %}{% when Some with (ret) %}: {{ ret.kotlin_type }}{% when None %}{% endmatch %}
{%- endfor %}
}

private object {{ handle_map_name }} {
    private val map = java.util.concurrent.ConcurrentHashMap<Long, {{ interface_name }}>()
    private val counter = java.util.concurrent.atomic.AtomicLong(1L)

    fun insert(obj: {{ interface_name }}): Long {
        val handle = counter.getAndAdd(2L)
        map[handle] = obj
        return handle
    }

    fun get(handle: Long): {{ interface_name }}? = map[handle]

    fun remove(handle: Long): {{ interface_name }}? = map.remove(handle)

    fun clone(handle: Long): Long {
        val obj = map[handle] ?: return 0L
        return insert(obj)
    }
}

object {{ callbacks_object }} {
    @JvmStatic
    fun free(handle: Long) {
        {{ handle_map_name }}.remove(handle)
    }

    @JvmStatic
    fun clone(handle: Long): Long {
        return {{ handle_map_name }}.clone(handle)
    }
{%- for method in sync_methods %}

    @JvmStatic
    fun {{ method.ffi_name }}(handle: Long{% for param in method.params %}, {{ param.name }}: {{ param.jni_type }}{% endfor %}){% match method.return_info %}{% when Some with (ret) %}: {{ ret.jni_type }}{% when None %}{% endmatch %} {
        val impl = {{ handle_map_name }}.get(handle) ?: error("{{ handle_map_name }}: invalid handle $handle")
        {% match method.return_info %}{% when Some with (ret) %}{% match ret.error_type %}{% when Some with (err_ty) %}val result: BoltFFIResult<{{ ret.kotlin_type }}, {{ err_ty }}> = try { BoltFFIResult.Ok(impl.{{ method.name }}({% for param in method.params %}{{ param.conversion }}{% if !loop.last %}, {% endif %}{% endfor %})) } catch (e: {{ err_ty }}) { BoltFFIResult.Err(e) }
        {% match ret.to_jni_result %}{% when Some with (result_encode) %}return result{{ result_encode }}{% when None %}return result{{ ret.to_jni }}{% endmatch %}{% when None %}return impl.{{ method.name }}({% for param in method.params %}{{ param.conversion }}{% if !loop.last %}, {% endif %}{% endfor %}){{ ret.to_jni }}{% endmatch %}{% when None %}impl.{{ method.name }}({% for param in method.params %}{{ param.conversion }}{% if !loop.last %}, {% endif %}{% endfor %}){% endmatch %}
    }
{%- endfor %}
{%- for method in async_methods %}

    @JvmStatic
    fun {{ method.ffi_name }}(handle: Long{% for param in method.params %}, {{ param.name }}: {{ param.jni_type }}{% endfor %}, callbackPtr: Long, callbackData: Long) {
        val impl = {{ handle_map_name }}.get(handle) ?: error("{{ handle_map_name }}: invalid handle $handle")
        {%- for param in method.params %}
        val {{ param.name }}Decoded = {{ param.conversion }}
        {%- endfor %}
        BoltFFIScope.launch {
            {% match method.return_info %}{% when Some with (ret) %}{% match ret.error_type %}{% when Some with (err_ty) %}val result: BoltFFIResult<{{ ret.kotlin_type }}, {{ err_ty }}> = try { BoltFFIResult.Ok(impl.{{ method.name }}({% for param in method.params %}{{ param.name }}Decoded{% if !loop.last %}, {% endif %}{% endfor %})) } catch (e: {{ err_ty }}) { BoltFFIResult.Err(e) }
            {% match ret.to_jni_result %}{% when Some with (result_encode) %}Native.{{ method.invoker_name }}(callbackPtr, callbackData, result{{ result_encode }}){% when None %}Native.{{ method.invoker_name }}(callbackPtr, callbackData, result{{ ret.to_jni }}){% endmatch %}{% when None %}val result = impl.{{ method.name }}({% for param in method.params %}{{ param.name }}Decoded{% if !loop.last %}, {% endif %}{% endfor %})
            Native.{{ method.invoker_name }}(callbackPtr, callbackData, result{{ ret.to_jni }}){% endmatch %}{% when None %}impl.{{ method.name }}({% for param in method.params %}{{ param.name }}Decoded{% if !loop.last %}, {% endif %}{% endfor %})
            Native.{{ method.invoker_name }}(callbackPtr, callbackData){% endmatch %}
        }
    }
{%- endfor %}
}

{%- if supports_proxy_wrap %}

private class {{ proxy_class_name }}(private val handle: Long) : {{ interface_name }} {
    @Suppress("deprecation")
    protected fun finalize() {
        if (handle != 0L) {
            Native.{{ proxy_release_name }}(handle)
        }
    }
{%- for method in proxy_methods %}

{{ method|indent(4, true) }}
{%- endfor %}
}
{%- endif %}

object {{ bridge_name }} {
    private val registered = java.util.concurrent.atomic.AtomicBoolean(false)

    fun register() {
        if (!registered.compareAndSet(false, true)) return
    }

    fun create(impl: {{ interface_name }}): Long {
        register()
        return {{ handle_map_name }}.insert(impl)
    }

{%- if supports_proxy_wrap %}

    fun wrap(handle: Long): {{ interface_name }} {
        return {{ handle_map_name }}.get(handle) ?: {{ proxy_class_name }}(handle)
    }
{%- endif %}
}
