/// <summary>
/// Manages the FFI vtable and delegates for a {{ trait_pascal }} implementation
/// </summary>
public sealed class {{ trait_pascal }}Bridge : IDisposable {

    private readonly I{{ trait_pascal }} _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    private readonly object[] _delegates;

    // Vtable slot delegates ({{ num_vtable_fields }})
{% if has_super_trait %}

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int NameFn(IntPtr userData, out IntPtr outName);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int VersionFn(IntPtr userData, out IntPtr outVersion);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int InitializeFn(IntPtr userData, out IntPtr outError);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ShutdownFn(IntPtr userData, out IntPtr outError);
{% endif %}
{% for method in methods %}

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
{% if method.is_primitive_return %}
{# Primitive returns: return directly, no out params #}
{% if method.params_empty %}
    private delegate {{ method.delegate_return_type }} {{ method.pascal_name }}Fn(IntPtr userData);
{% else %}
    private delegate {{ method.delegate_return_type }} {{ method.pascal_name }}Fn(IntPtr userData, {{ method.unmanaged_params }});
{% endif %}
{% else %}
{# Complex returns: use out params #}
{% if method.params_empty %}
{% if is_options_field %}
    private delegate int {{ method.pascal_name }}Fn(IntPtr userData, out IntPtr outResult);
{% else %}
    private delegate int {{ method.pascal_name }}Fn(IntPtr userData, out IntPtr outResult, out IntPtr outError);
{% endif %}
{% else %}
{% if is_options_field %}
    private delegate int {{ method.pascal_name }}Fn(IntPtr userData, {{ method.unmanaged_params }}, out IntPtr outResult);
{% else %}
    private delegate int {{ method.pascal_name }}Fn(IntPtr userData, {{ method.unmanaged_params }}, out IntPtr outResult, out IntPtr outError);
{% endif %}
{% endif %}
{% endif %}
{% endfor %}

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeUserDataFn(IntPtr userData);

    public {{ trait_pascal }}Bridge(I{{ trait_pascal }} impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        _vtable = IntPtr.Zero;
        _disposed = false;
        _delegates = new object[{{ num_vtable_fields }}];
        BuildVtable();
    }

    private void BuildVtable() {
        // Allocate unmanaged vtable struct (array of function pointers)
        _vtable = global::System.Runtime.InteropServices.Marshal.AllocHGlobal(IntPtr.Size * {{ num_vtable_fields }});

{{ vtable_slots }}
    }

    private static string ToJsonString<T>(T value) {
        return JsonSerializer.Serialize(value);
    }

{{ callbacks }}

    public void Dispose() {
        if (_disposed) return;
        _disposed = true;

        if (_vtable != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeHGlobal(_vtable);
            _vtable = IntPtr.Zero;
        }

        if (_implHandle.IsAllocated) {
            _implHandle.Free();
        }
    }

    /// <summary>Register a {{ trait_pascal }} implementation and return its native handle</summary>
{% if has_super_trait %}
    public static IntPtr Register(I{{ trait_pascal }} impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;

{% else %}
    public static IntPtr Register(I{{ trait_pascal }} impl, string name) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

{% endif %}

        var bridge = new {{ trait_pascal }}Bridge(impl);

        try {
            var userDataHandle = GCHandle.Alloc(bridge, GCHandleType.Normal);
            var userData = GCHandle.ToIntPtr(userDataHandle);
            var vtablePtr = bridge._vtable;

            var result = NativeMethods.Register{{ trait_pascal }}(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                userDataHandle.Free();
                bridge.Dispose();
                var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
                global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
                throw new InvalidOperationException($"Failed to register {name}: {errorMsg}");
            }

            return userData;
        } catch {
            bridge.Dispose();
            throw;
        }
    }
}
