{{ self::swift_doc_block(callback.doc, "") }}/// > Warning: 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.
/// > Dispatch to the main thread if updating UI.
public protocol {{ callback.protocol_name }}: AnyObject {
{%- for method in callback.methods %}
{{ self::swift_doc_block(method.doc, "    ") }}    func {{ method.swift_name }}({% for param in method.params %}{{ param.label }}: {{ param.swift_type }}{% if !loop.last %}, {% endif %}{% endfor %}){% if method.is_async() %} async{% endif %}{% if method.throws() %} throws{% endif %}{% if let Some(ret) = method.return_type() %} -> {{ ret }}{% endif %}
{%- endfor %}
}

private class {{ callback.wrapper_class }} {
    let impl_: {{ callback.protocol_name }}
    init(_ impl_: {{ callback.protocol_name }}) { self.impl_ = impl_ }
}
{%- if callback.supports_foreign_wrap %}

private protocol {{ callback.bridge_name }}Bridgeable: {{ callback.protocol_name }} {
    func _boltffiRetainedCallbackHandle() -> BoltFFICallbackHandle
}

private final class {{ callback.protocol_name }}Proxy: {{ callback.protocol_name }}, {{ callback.bridge_name }}Bridgeable {
    private let handle_: BoltFFICallbackHandle

    init(_ handle: BoltFFICallbackHandle) {
        self.handle_ = handle
    }

    deinit {
        {{ callback.bridge_name }}.release(handle_)
    }

    func _boltffiRetainedCallbackHandle() -> BoltFFICallbackHandle {
        {{ callback.bridge_name }}.clone(handle_)
    }
{%- for method in callback.methods %}

    func {{ method.swift_name }}({% for param in method.params %}{{ param.label }}: {{ param.swift_type }}{% if !loop.last %}, {% endif %}{% endfor %}){% if method.is_async() %} async{% endif %}{% if method.throws() %} throws{% endif %}{% if let Some(ret) = method.return_type() %} -> {{ ret }}{% endif %} {
{%- if method.is_async() %}
        fatalError("foreign callback proxies do not support async methods yet")
{%- else %}
        guard let vtable = handle_.vtable?.assumingMemoryBound(to: {{ callback.vtable_type }}.self),
              let invoke = vtable.pointee.{{ method.ffi_name }} else {
            fatalError("missing callback vtable entry")
        }
{%- if method.wire_encoded_return() %}
        var outPtr: UnsafeMutablePointer<UInt8>? = nil
        var outLen: UInt = 0
        var ffiStatus = FfiStatus()
        invoke(handle_.handle{% for param in method.params %}{% for arg in param.proxy_ffi_args %}, {{ arg }}{% endfor %}{% endfor %}, &outPtr, &outLen, &ffiStatus)
{%- if method.throws() %}
        guard ffiStatus.code == 0 else { throw FfiError(message: "callback invocation failed") }
        defer { free(outPtr) }
        return try boltffiDecodeOwnedBuf(outPtr, Int(outLen)) { reader in {{ method.returns.reader_decode_expr().unwrap() }} }
{%- else %}
        guard ffiStatus.code == 0 else { fatalError("callback invocation failed") }
        defer { free(outPtr) }
        return boltffiDecodeOwnedBuf(outPtr, Int(outLen)) { reader in {{ method.returns.reader_decode_expr().unwrap() }} }
{%- endif %}
{%- elif method.has_return() %}
        var result: {{ method.proxy_out_ffi_type().unwrap() }} = 0
        var ffiStatus = FfiStatus()
        invoke(handle_.handle{% for param in method.params %}{% for arg in param.proxy_ffi_args %}, {{ arg }}{% endfor %}{% endfor %}, &result, &ffiStatus)
{%- if method.throws() %}
        guard ffiStatus.code == 0 else { throw FfiError(message: "callback invocation failed") }
{%- else %}
        guard ffiStatus.code == 0 else { fatalError("callback invocation failed") }
{%- endif %}
{%- if method.returns.is_c_style_enum() %}
        return {{ method.returns.c_style_enum_type().unwrap() }}(rawValue: result)!
{%- else %}
        return result
{%- endif %}
{%- else %}
        var ffiStatus = FfiStatus()
        invoke(handle_.handle{% for param in method.params %}{% for arg in param.proxy_ffi_args %}, {{ arg }}{% endfor %}{% endfor %}, &ffiStatus)
{%- if method.throws() %}
        guard ffiStatus.code == 0 else { throw FfiError(message: "callback invocation failed") }
{%- else %}
        guard ffiStatus.code == 0 else { fatalError("callback invocation failed") }
{%- endif %}
{%- endif %}
{%- endif %}
    }
{%- endfor %}
}
{%- endif %}
{% for method in callback.methods %}
{%- if method.is_async() %}

private func _{{ callback.vtable_var }}_{{ method.swift_name }}(_ wrapper: {{ callback.wrapper_class }}, {% for param in method.params %}_ {{ param.label }}: {{ param.swift_type }}, {% endfor %}_ callback: {{ method.async_callback_c_type() }}, _ callbackData: UInt64) {
    _Concurrency.Task {
{%- if method.throws() %}
        do {
            {% if method.has_return() %}let result = {% endif %}try await wrapper.impl_.{{ method.swift_name }}({% for param in method.params %}{{ param.label }}: {{ param.label }}{% if !loop.last %}, {% endif %}{% endfor %})
{%- if method.has_return() %}
{%- if method.wire_encoded_return() %}
{%- if let Some(encoded) = method.wire_return_encode() %}
            {{ encoded }}
{%- endif %}
            boltffiInvokeWireCallback(callback, callbackData, encoded, FfiStatus(code: 0))
{%- else %}
            callback?(callbackData, result, FfiStatus(code: 0))
{%- endif %}
{%- else %}
            callback?(callbackData, FfiStatus(code: 0))
{%- endif %}
        } catch {
{%- if let Some(err_type) = method.err_type() %}
{%- if let Some(err_encoded) = method.wire_err_encode() %}
            guard let error = error as? {{ err_type }} else {
                var errWriter = WireWriter()
                errWriter.writeString(String(describing: error))
                boltffiInvokeWireCallback(callback, callbackData, errWriter.finalize(), FfiStatus(code: 1))
                return
            }
            {{ err_encoded }}
            boltffiInvokeWireCallback(callback, callbackData, encoded, FfiStatus(code: 0))
{%- else %}
            var errWriter = WireWriter()
            errWriter.writeString(String(describing: error))
            boltffiInvokeWireCallback(callback, callbackData, errWriter.finalize(), FfiStatus(code: 1))
{%- endif %}
{%- else %}
            var errWriter = WireWriter()
            errWriter.writeString(String(describing: error))
            boltffiInvokeWireCallback(callback, callbackData, errWriter.finalize(), FfiStatus(code: 1))
{%- endif %}
        }
{%- else %}
        {% if method.has_return() %}let result = {% endif %}await wrapper.impl_.{{ method.swift_name }}({% for param in method.params %}{{ param.label }}: {{ param.label }}{% if !loop.last %}, {% endif %}{% endfor %})
{%- if method.has_return() %}
{%- if method.wire_encoded_return() %}
{%- if let Some(encoded) = method.wire_return_encode() %}
        {{ encoded }}
{%- endif %}
        boltffiInvokeWireCallback(callback, callbackData, encoded, FfiStatus(code: 0))
{%- else %}
        callback?(callbackData, result, FfiStatus(code: 0))
{%- endif %}
{%- else %}
        callback?(callbackData, FfiStatus(code: 0))
{%- endif %}
{%- endif %}
    }
}
{%- endif %}
{%- endfor %}

private var {{ callback.vtable_var }}: {{ callback.vtable_type }} = {
    {{ callback.vtable_type }}(
        free: { handle in
            guard handle != 0 else { return }
            Unmanaged<{{ callback.wrapper_class }}>.fromOpaque(UnsafeRawPointer(bitPattern: UInt(handle))!).release()
        },
        clone: { handle in
            guard handle != 0 else { return 0 }
            let wrapper = Unmanaged<{{ callback.wrapper_class }}>.fromOpaque(UnsafeRawPointer(bitPattern: UInt(handle))!)
            _ = wrapper.retain()
            return handle
        }{% for method in callback.methods %},
        {{ method.ffi_name }}: { handle{% for param in method.params %}{% for arg in param.ffi_args %}, {{ arg }}{% endfor %}{% endfor %}{% if method.has_out_param %}{% if method.wire_encoded_return() %}, outPtr, outLen{% else %}, outPtr{% endif %}{% endif %}{% if !method.is_async() %}, statusPtr{% endif %}{% if method.is_async() %}, callback, callbackData{% endif %} in
            guard handle != 0 else { {% if !method.is_async() %}statusPtr?.pointee = FfiStatus(code: 1); {% endif %}return }
            let wrapper = Unmanaged<{{ callback.wrapper_class }}>.fromOpaque(UnsafeRawPointer(bitPattern: UInt(handle))!).takeUnretainedValue()
{%- for param in method.params %}
{%- match param.decode_prelude %}{% when Some with (prelude) %}
            {{ prelude }}
{%- when None %}{% endmatch %}
{%- endfor %}
{%- if method.is_async() %}
            _{{ callback.vtable_var }}_{{ method.swift_name }}(wrapper, {% for param in method.params %}{{ param.call_arg }}, {% endfor %}callback, callbackData)
{%- else %}
{%- if method.throws() %}
            do {
                {% if method.has_return() %}let result = {% endif %}try wrapper.impl_.{{ method.swift_name }}({% for param in method.params %}{{ param.label }}: {{ param.call_arg }}{% if !loop.last %}, {% endif %}{% endfor %})
{%- if method.has_out_param %}
{%- if method.wire_encoded_return() %}
{%- if let Some(encoded) = method.wire_return_encode() %}
                {{ encoded }}
{%- endif %}
                guard boltffiStoreEncodedCallbackResult(encoded, outPtr, outLen) else {
                    statusPtr?.pointee = FfiStatus(code: 1)
                    return
                }
{%- else %}
                outPtr?.pointee = {{ method.direct_out_expr() }}
{%- endif %}
{%- endif %}
                statusPtr?.pointee = FfiStatus(code: 0)
            } catch {
{%- if let Some(err_type) = method.err_type() %}
{%- if let Some(err_encoded) = method.wire_err_encode() %}
                guard let error = error as? {{ err_type }} else {
                    statusPtr?.pointee = FfiStatus(code: 1)
                    return
                }
                {{ err_encoded }}
                guard boltffiStoreEncodedCallbackResult(encoded, outPtr, outLen) else {
                    statusPtr?.pointee = FfiStatus(code: 1)
                    return
                }
                statusPtr?.pointee = FfiStatus(code: 0)
{%- else %}
                statusPtr?.pointee = FfiStatus(code: 1)
{%- endif %}
{%- else %}
                statusPtr?.pointee = FfiStatus(code: 1)
{%- endif %}
            }
{%- else %}
            {% if method.has_return() %}let result = {% endif %}wrapper.impl_.{{ method.swift_name }}({% for param in method.params %}{{ param.label }}: {{ param.call_arg }}{% if !loop.last %}, {% endif %}{% endfor %})
{%- if method.has_out_param %}
{%- if method.wire_encoded_return() %}
{%- if let Some(encoded) = method.wire_return_encode() %}
            {{ encoded }}
{%- endif %}
            guard boltffiStoreEncodedCallbackResult(encoded, outPtr, outLen) else {
                statusPtr?.pointee = FfiStatus(code: 1)
                return
            }
{%- else %}
            outPtr?.pointee = {{ method.direct_out_expr() }}
{%- endif %}
{%- endif %}
            statusPtr?.pointee = FfiStatus(code: 0)
{%- endif %}
{%- endif %}
        }{% endfor %}
    )
}()

enum {{ callback.bridge_name }} {
    private static let _register: Void = {
        {{ callback.register_fn }}(&{{ callback.vtable_var }})
    }()

{%- if callback.supports_foreign_wrap %}
    static func release(_ handle: BoltFFICallbackHandle) {
        guard handle.handle != 0,
              let vtable = handle.vtable?.assumingMemoryBound(to: {{ callback.vtable_type }}.self),
              let free = vtable.pointee.free else { return }
        free(handle.handle)
    }

    static func clone(_ handle: BoltFFICallbackHandle) -> BoltFFICallbackHandle {
        guard handle.handle != 0,
              let vtable = handle.vtable?.assumingMemoryBound(to: {{ callback.vtable_type }}.self),
              let clone = vtable.pointee.clone else {
            return BoltFFICallbackHandle(handle: 0, vtable: nil)
        }
        return BoltFFICallbackHandle(handle: clone(handle.handle), vtable: handle.vtable)
    }
{%- endif %}

    static func create(_ impl: {{ callback.protocol_name }}) -> BoltFFICallbackHandle {
        _ = _register
{%- if callback.supports_foreign_wrap %}
        if let bridgeable = impl as? {{ callback.bridge_name }}Bridgeable {
            return bridgeable._boltffiRetainedCallbackHandle()
        }
{%- endif %}
        let wrapper = {{ callback.wrapper_class }}(impl)
        let handle = UInt64(UInt(bitPattern: Unmanaged.passRetained(wrapper).toOpaque()))
        return {{ callback.create_fn }}(handle)
    }

{%- if callback.supports_foreign_wrap %}
    static func wrap(_ handle: BoltFFICallbackHandle) -> any {{ callback.protocol_name }} {
        _ = _register
        return {{ callback.protocol_name }}Proxy(handle)
    }
{%- endif %}
}
