// DO NOT EDIT — generated by alef
// This file bridges managed C# trait implementations to the native FFI layer
#nullable enable

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Kreuzberg;
/// <summary>
/// Bridge interface for OcrBackend trait implementation via native FFI
/// </summary>
public interface IOcrBackend {

    /// <summary>Get the plugin name.</summary>
    string Name { get; }

    /// <summary>Get the plugin version.</summary>
    string Version { get; }

    /// <summary>Initialize the plugin.</summary>
    void Initialize();

    /// <summary>Shut down the plugin.</summary>
    void Shutdown();

    /// <summary>process_image</summary>
    ExtractionResult ProcessImage(byte[] ImageBytes, OcrConfig Config);

    /// <summary>process_image_file</summary>
    ExtractionResult ProcessImageFile(string Path, OcrConfig Config);

    /// <summary>supports_language</summary>
    bool SupportsLanguage(string Lang);

    /// <summary>backend_type</summary>
    OcrBackendType BackendType();

    /// <summary>supported_languages</summary>
    List<string> SupportedLanguages();

    /// <summary>supports_table_detection</summary>
    bool SupportsTableDetection();

    /// <summary>supports_document_processing</summary>
    bool SupportsDocumentProcessing();

    /// <summary>process_document</summary>
    ExtractionResult ProcessDocument(string Path, OcrConfig Config);
}

/// <summary>
/// Manages the FFI vtable and delegates for a OcrBackend implementation
/// </summary>
public sealed class OcrBackendBridge : IDisposable {

    internal readonly IOcrBackend _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    // Keep all delegates alive for the lifetime of the bridge.
    // Delegates must not be GC'd because Rust holds function pointers obtained via
    // GetFunctionPointerForDelegate; those pointers become invalid if the delegate is collected.
    private readonly List<object> _delegateRoots;
    internal readonly IntPtr _bridgeId;
    private int _callbackRefCount = 0;

    // Static registry: maps bridge ID (IntPtr) to bridge instance
    // This prevents GC while Rust holds the bridge ID as userData.
    internal static readonly Dictionary<IntPtr, OcrBackendBridge> _bridgeRegistry = new();
    internal static int _nextBridgeId = 1;
    internal static readonly object _registryLock = new();

    // Vtable slot delegates (14)

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

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

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ProcessImageFn(IntPtr userData, IntPtr imageBytes, UIntPtr imageBytesLen, IntPtr config, out IntPtr outResult, out IntPtr outError);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ProcessImageFileFn(IntPtr userData, IntPtr path, IntPtr config, out IntPtr outResult, out IntPtr outError);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int SupportsLanguageFn(IntPtr userData, IntPtr lang);

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int SupportsTableDetectionFn(IntPtr userData);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int SupportsDocumentProcessingFn(IntPtr userData);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ProcessDocumentFn(IntPtr userData, IntPtr path, IntPtr config, out IntPtr outResult, out IntPtr outError);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeStringFn(IntPtr ptr);

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

    public OcrBackendBridge(IOcrBackend impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        // Keep impl alive via normal GCHandle (sufficient for callback rooting).
        // The impl instance itself is not pinned, just kept in the GC root set.
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        // Delegate list roots all delegates by reference to prevent GC.
        // This avoids trying to pin an object array containing references, which .NET 10 forbids.
        _delegateRoots = new List<object>(14);
        _vtable = IntPtr.Zero;
        _disposed = false;
        // Allocate unique bridge ID for registry lookup during callbacks
        lock (_registryLock) {
            _bridgeId = new IntPtr(_nextBridgeId++);
        }
        BuildVtable();
    }

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

        // Slot 0: name_fn
        var nameFn = new NameFn(NameFnCallback);
        _delegateRoots.Add(nameFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 0, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(nameFn));

        // Slot 1: version_fn
        var versionFn = new VersionFn(VersionFnCallback);
        _delegateRoots.Add(versionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 8, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(versionFn));

        // Slot 2: initialize_fn
        var initFn = new InitializeFn(InitializeFnCallback);
        _delegateRoots.Add(initFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 16, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(initFn));

        // Slot 3: shutdown_fn
        var shutdownFn = new ShutdownFn(ShutdownFnCallback);
        _delegateRoots.Add(shutdownFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 24, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shutdownFn));

        // Slot 4: process_image_fn
        var processImageFn = new ProcessImageFn(ProcessImageFnCallback);
        _delegateRoots.Add(processImageFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 32, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(processImageFn));

        // Slot 5: process_image_file_fn
        var processImageFileFn = new ProcessImageFileFn(ProcessImageFileFnCallback);
        _delegateRoots.Add(processImageFileFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 40, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(processImageFileFn));

        // Slot 6: supports_language_fn
        var supportsLanguageFn = new SupportsLanguageFn(SupportsLanguageFnCallback);
        _delegateRoots.Add(supportsLanguageFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 48, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(supportsLanguageFn));

        // Slot 7: backend_type_fn
        var backendTypeFn = new BackendTypeFn(BackendTypeFnCallback);
        _delegateRoots.Add(backendTypeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 56, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(backendTypeFn));

        // Slot 8: supported_languages_fn
        var supportedLanguagesFn = new SupportedLanguagesFn(SupportedLanguagesFnCallback);
        _delegateRoots.Add(supportedLanguagesFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 64, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(supportedLanguagesFn));

        // Slot 9: supports_table_detection_fn
        var supportsTableDetectionFn = new SupportsTableDetectionFn(SupportsTableDetectionFnCallback);
        _delegateRoots.Add(supportsTableDetectionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 72, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(supportsTableDetectionFn));

        // Slot 10: supports_document_processing_fn
        var supportsDocumentProcessingFn = new SupportsDocumentProcessingFn(SupportsDocumentProcessingFnCallback);
        _delegateRoots.Add(supportsDocumentProcessingFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 80, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(supportsDocumentProcessingFn));

        // Slot 11: process_document_fn
        var processDocumentFn = new ProcessDocumentFn(ProcessDocumentFnCallback);
        _delegateRoots.Add(processDocumentFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 88, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(processDocumentFn));

        // Slot 12: free_string
        var freeStringFn = new FreeStringFn(FreeStringCallback);
        _delegateRoots.Add(freeStringFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 96, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeStringFn));

        // Slot 13: free_user_data
        var freeFn = new FreeUserDataFn(FreeUserDataCallback);
        _delegateRoots.Add(freeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 104, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeFn));

    }

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

    /// <summary>Called by Rust via FreeUserDataCallback when done with this bridge</summary>
    internal static void FreeUserData(IntPtr bridgeId) {
        lock (_registryLock) {
            // Mark bridge as disposed but DON'T remove from registry yet.
            // Callbacks in flight will still be able to look it up and execute safely.
            // The bridge will stay alive as long as _callbackRefCount > 0.
            if (_bridgeRegistry.TryGetValue(bridgeId, out var bridge)) {
                bridge._disposed = true;
            }
        }
    }

    private void IncrementCallbackRef() {
        lock (_registryLock) {
            _callbackRefCount++;
        }
    }

    private void DecrementCallbackRef() {
        lock (_registryLock) {
            if (_callbackRefCount > 0) {
                _callbackRefCount--;
            }
            // Once refcount reaches 0 and bridge is disposed, remove from registry
            if (_callbackRefCount == 0 && _disposed) {
                _bridgeRegistry.Remove(_bridgeId);
            }
        }
    }

    private int NameFnCallback(IntPtr userData, out IntPtr outName, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _name = null!;
            lock (OcrBackendBridge._registryLock) {
                if (!OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outName = IntPtr.Zero;
                    return 1;
                }
                _name = bridge._impl.Name;
            }
            outName = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_name);
            return 0;
        } catch (Exception ex) {
            outName = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _version = null!;
            lock (OcrBackendBridge._registryLock) {
                if (!OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outVersion = IntPtr.Zero;
                    return 1;
                }
                _version = bridge._impl.Version;
            }
            outVersion = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_version);
            return 0;
        } catch (Exception ex) {
            outVersion = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (OcrBackendBridge._registryLock) {
                if (!OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Initialize();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (OcrBackendBridge._registryLock) {
                if (!OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Shutdown();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ProcessImageFnCallback(IntPtr userData, IntPtr imageBytes, UIntPtr imageBytesLen, IntPtr config, out IntPtr outResult, out IntPtr outError) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var managed_imageBytes = new byte[(int)imageBytesLen];
            Marshal.Copy(imageBytes, managed_imageBytes, 0, (int)imageBytesLen);
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<OcrConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ProcessImage(managed_imageBytes, managed_config);
            try {
                string __result_str = (methodResult.ToFfiJson()) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int ProcessImageFileFnCallback(IntPtr userData, IntPtr path, IntPtr config, out IntPtr outResult, out IntPtr outError) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_path = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(path) ?? "{}";
            var managed_path = JsonSerializer.Deserialize<string>(json_path, FfiJsonExtensions.FfiJsonOptions)!;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<OcrConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ProcessImageFile(managed_path, managed_config);
            try {
                string __result_str = (methodResult.ToFfiJson()) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int SupportsLanguageFnCallback(IntPtr userData, IntPtr lang) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var managed_lang = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(lang) ?? string.Empty;
            var methodResult = bridge._impl.SupportsLanguage(managed_lang);
            return methodResult ? 1 : 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int BackendTypeFnCallback(IntPtr userData, out IntPtr outResult, out IntPtr outError) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.BackendType();
            try {
                string __result_str = (methodResult.ToFfiJson()) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int SupportedLanguagesFnCallback(IntPtr userData, out IntPtr outResult, out IntPtr outError) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.SupportedLanguages();
            try {
                string __result_str = (ToJsonString(methodResult)) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int SupportsTableDetectionFnCallback(IntPtr userData) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.SupportsTableDetection();
            return methodResult ? 1 : 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int SupportsDocumentProcessingFnCallback(IntPtr userData) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.SupportsDocumentProcessing();
            return methodResult ? 1 : 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int ProcessDocumentFnCallback(IntPtr userData, IntPtr path, IntPtr config, out IntPtr outResult, out IntPtr outError) {
        OcrBackendBridge? _bridgeFromRegistry = null;
        lock (OcrBackendBridge._registryLock) {
            if (OcrBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_path = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(path) ?? "{}";
            var managed_path = JsonSerializer.Deserialize<string>(json_path, FfiJsonExtensions.FfiJsonOptions)!;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<OcrConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ProcessDocument(managed_path, managed_config);
            try {
                string __result_str = (methodResult.ToFfiJson()) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private void FreeStringCallback(IntPtr ptr) {
        if (ptr != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr);
        }
    }

    private void FreeUserDataCallback(IntPtr userData) {
        if (userData != IntPtr.Zero) {
            OcrBackendBridge.FreeUserData(userData);
        }
    }


    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();
        }

        // _delegateRoots is kept as a managed list; GC handles it automatically.
        // No need to free individual delegates or the list.
    }

    /// <summary>Register a OcrBackend implementation and return its native handle</summary>
    public static IntPtr Register(IOcrBackend impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new OcrBackendBridge(impl);

        try {
            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (_registryLock) {
                _bridgeRegistry[bridge._bridgeId] = bridge;
            }

            var vtablePtr = bridge._vtable;
            var userData = bridge._bridgeId;

            var result = NativeMethods.RegisterOcrBackend(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (_registryLock) {
                    _bridgeRegistry.Remove(bridge._bridgeId);
                }
                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 the bridge ID (userData).
            // The FFI layer holds this ID and will call FreeUserDataCallback with it when done.
            return userData;
        } catch {
            lock (_registryLock) {
                _bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }
}

/// <summary>Static helpers for registering trait implementations</summary>
public static class OcrBackendRegistry {

    private static readonly ConcurrentDictionary<string, OcrBackendBridge> _bridges =
        new ConcurrentDictionary<string, OcrBackendBridge>();

    /// <summary>Register a OcrBackend implementation and return its native handle</summary>
    public static IntPtr RegisterOcrBackend(IOcrBackend impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var bridge = new OcrBackendBridge(impl);
        var userData = bridge._bridgeId;
        var name = impl.Name;

        lock (OcrBackendBridge._registryLock) {
            OcrBackendBridge._bridgeRegistry[userData] = bridge;
        }
        return userData;
    }

    /// <summary>Register a OcrBackend implementation and return its native handle</summary>

    public static IntPtr Register(IOcrBackend impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new OcrBackendBridge(impl);

        try {
            var userData = bridge._bridgeId;
            var vtablePtr = bridge._vtable;

            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (OcrBackendBridge._registryLock) {
                OcrBackendBridge._bridgeRegistry[userData] = bridge;
            }

            var result = NativeMethods.RegisterOcrBackend(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (OcrBackendBridge._registryLock) {
                    OcrBackendBridge._bridgeRegistry.Remove(userData);
                }
                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 {
            lock (OcrBackendBridge._registryLock) {
                OcrBackendBridge._bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }

    /// <summary>Unregister a OcrBackend implementation</summary>
    public static void Unregister(string name) {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));

        var result = NativeMethods.UnregisterOcrBackend(name, out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to unregister {name}: {errorMsg}");
        }

        if (_bridges.TryRemove(name, out var bridge)) {
            bridge.Dispose();
        }
    }

    /// <summary>Clear all registered OcrBackend implementations</summary>
    public static void Clear() {
        var result = NativeMethods.ClearOcrBackend(out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to clear OcrBackend registry: {errorMsg}");
        }

        _bridges.Clear();
    }
}

/// <summary>
/// Bridge interface for PostProcessor trait implementation via native FFI
/// </summary>
public interface IPostProcessor {

    /// <summary>Get the plugin name.</summary>
    string Name { get; }

    /// <summary>Get the plugin version.</summary>
    string Version { get; }

    /// <summary>Initialize the plugin.</summary>
    void Initialize();

    /// <summary>Shut down the plugin.</summary>
    void Shutdown();

    /// <summary>process</summary>
    void Process(ExtractionResult Result, ExtractionConfig Config);

    /// <summary>processing_stage</summary>
    ProcessingStage ProcessingStage();

    /// <summary>should_process</summary>
    bool ShouldProcess(ExtractionResult Result, ExtractionConfig Config);

    /// <summary>estimated_duration_ms</summary>
    ulong EstimatedDurationMs(ExtractionResult Result);

    /// <summary>priority</summary>
    int Priority();
}

/// <summary>
/// Manages the FFI vtable and delegates for a PostProcessor implementation
/// </summary>
public sealed class PostProcessorBridge : IDisposable {

    internal readonly IPostProcessor _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    // Keep all delegates alive for the lifetime of the bridge.
    // Delegates must not be GC'd because Rust holds function pointers obtained via
    // GetFunctionPointerForDelegate; those pointers become invalid if the delegate is collected.
    private readonly List<object> _delegateRoots;
    internal readonly IntPtr _bridgeId;
    private int _callbackRefCount = 0;

    // Static registry: maps bridge ID (IntPtr) to bridge instance
    // This prevents GC while Rust holds the bridge ID as userData.
    internal static readonly Dictionary<IntPtr, PostProcessorBridge> _bridgeRegistry = new();
    internal static int _nextBridgeId = 1;
    internal static readonly object _registryLock = new();

    // Vtable slot delegates (11)

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

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

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ProcessFn(IntPtr userData, IntPtr result, IntPtr config);

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ShouldProcessFn(IntPtr userData, IntPtr result, IntPtr config);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate ulong EstimatedDurationMsFn(IntPtr userData, IntPtr result);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int PriorityFn(IntPtr userData);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeStringFn(IntPtr ptr);

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

    public PostProcessorBridge(IPostProcessor impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        // Keep impl alive via normal GCHandle (sufficient for callback rooting).
        // The impl instance itself is not pinned, just kept in the GC root set.
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        // Delegate list roots all delegates by reference to prevent GC.
        // This avoids trying to pin an object array containing references, which .NET 10 forbids.
        _delegateRoots = new List<object>(11);
        _vtable = IntPtr.Zero;
        _disposed = false;
        // Allocate unique bridge ID for registry lookup during callbacks
        lock (_registryLock) {
            _bridgeId = new IntPtr(_nextBridgeId++);
        }
        BuildVtable();
    }

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

        // Slot 0: name_fn
        var nameFn = new NameFn(NameFnCallback);
        _delegateRoots.Add(nameFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 0, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(nameFn));

        // Slot 1: version_fn
        var versionFn = new VersionFn(VersionFnCallback);
        _delegateRoots.Add(versionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 8, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(versionFn));

        // Slot 2: initialize_fn
        var initFn = new InitializeFn(InitializeFnCallback);
        _delegateRoots.Add(initFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 16, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(initFn));

        // Slot 3: shutdown_fn
        var shutdownFn = new ShutdownFn(ShutdownFnCallback);
        _delegateRoots.Add(shutdownFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 24, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shutdownFn));

        // Slot 4: process_fn
        var processFn = new ProcessFn(ProcessFnCallback);
        _delegateRoots.Add(processFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 32, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(processFn));

        // Slot 5: processing_stage_fn
        var processingStageFn = new ProcessingStageFn(ProcessingStageFnCallback);
        _delegateRoots.Add(processingStageFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 40, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(processingStageFn));

        // Slot 6: should_process_fn
        var shouldProcessFn = new ShouldProcessFn(ShouldProcessFnCallback);
        _delegateRoots.Add(shouldProcessFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 48, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shouldProcessFn));

        // Slot 7: estimated_duration_ms_fn
        var estimatedDurationMsFn = new EstimatedDurationMsFn(EstimatedDurationMsFnCallback);
        _delegateRoots.Add(estimatedDurationMsFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 56, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(estimatedDurationMsFn));

        // Slot 8: priority_fn
        var priorityFn = new PriorityFn(PriorityFnCallback);
        _delegateRoots.Add(priorityFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 64, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(priorityFn));

        // Slot 9: free_string
        var freeStringFn = new FreeStringFn(FreeStringCallback);
        _delegateRoots.Add(freeStringFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 72, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeStringFn));

        // Slot 10: free_user_data
        var freeFn = new FreeUserDataFn(FreeUserDataCallback);
        _delegateRoots.Add(freeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 80, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeFn));

    }

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

    /// <summary>Called by Rust via FreeUserDataCallback when done with this bridge</summary>
    internal static void FreeUserData(IntPtr bridgeId) {
        lock (_registryLock) {
            // Mark bridge as disposed but DON'T remove from registry yet.
            // Callbacks in flight will still be able to look it up and execute safely.
            // The bridge will stay alive as long as _callbackRefCount > 0.
            if (_bridgeRegistry.TryGetValue(bridgeId, out var bridge)) {
                bridge._disposed = true;
            }
        }
    }

    private void IncrementCallbackRef() {
        lock (_registryLock) {
            _callbackRefCount++;
        }
    }

    private void DecrementCallbackRef() {
        lock (_registryLock) {
            if (_callbackRefCount > 0) {
                _callbackRefCount--;
            }
            // Once refcount reaches 0 and bridge is disposed, remove from registry
            if (_callbackRefCount == 0 && _disposed) {
                _bridgeRegistry.Remove(_bridgeId);
            }
        }
    }

    private int NameFnCallback(IntPtr userData, out IntPtr outName, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _name = null!;
            lock (PostProcessorBridge._registryLock) {
                if (!PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outName = IntPtr.Zero;
                    return 1;
                }
                _name = bridge._impl.Name;
            }
            outName = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_name);
            return 0;
        } catch (Exception ex) {
            outName = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _version = null!;
            lock (PostProcessorBridge._registryLock) {
                if (!PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outVersion = IntPtr.Zero;
                    return 1;
                }
                _version = bridge._impl.Version;
            }
            outVersion = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_version);
            return 0;
        } catch (Exception ex) {
            outVersion = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (PostProcessorBridge._registryLock) {
                if (!PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Initialize();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (PostProcessorBridge._registryLock) {
                if (!PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Shutdown();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ProcessFnCallback(IntPtr userData, IntPtr result, IntPtr config) {
        PostProcessorBridge? _bridgeFromRegistry = null;
        lock (PostProcessorBridge._registryLock) {
            if (PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_result = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(result) ?? "{}";
            var managed_result = JsonSerializer.Deserialize<ExtractionResult>(json_result, FfiJsonExtensions.FfiJsonOptions)!;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<ExtractionConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            bridge._impl.Process(managed_result, managed_config);
            return 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int ProcessingStageFnCallback(IntPtr userData, out IntPtr outResult, out IntPtr outError) {
        PostProcessorBridge? _bridgeFromRegistry = null;
        lock (PostProcessorBridge._registryLock) {
            if (PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.ProcessingStage();
            try {
                string __result_str = (methodResult.ToFfiJson()) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int ShouldProcessFnCallback(IntPtr userData, IntPtr result, IntPtr config) {
        PostProcessorBridge? _bridgeFromRegistry = null;
        lock (PostProcessorBridge._registryLock) {
            if (PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_result = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(result) ?? "{}";
            var managed_result = JsonSerializer.Deserialize<ExtractionResult>(json_result, FfiJsonExtensions.FfiJsonOptions)!;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<ExtractionConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ShouldProcess(managed_result, managed_config);
            return methodResult ? 1 : 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private ulong EstimatedDurationMsFnCallback(IntPtr userData, IntPtr result) {
        PostProcessorBridge? _bridgeFromRegistry = null;
        lock (PostProcessorBridge._registryLock) {
            if (PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_result = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(result) ?? "{}";
            var managed_result = JsonSerializer.Deserialize<ExtractionResult>(json_result, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.EstimatedDurationMs(managed_result);
            return methodResult;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int PriorityFnCallback(IntPtr userData) {
        PostProcessorBridge? _bridgeFromRegistry = null;
        lock (PostProcessorBridge._registryLock) {
            if (PostProcessorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.Priority();
            return methodResult;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private void FreeStringCallback(IntPtr ptr) {
        if (ptr != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr);
        }
    }

    private void FreeUserDataCallback(IntPtr userData) {
        if (userData != IntPtr.Zero) {
            PostProcessorBridge.FreeUserData(userData);
        }
    }


    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();
        }

        // _delegateRoots is kept as a managed list; GC handles it automatically.
        // No need to free individual delegates or the list.
    }

    /// <summary>Register a PostProcessor implementation and return its native handle</summary>
    public static IntPtr Register(IPostProcessor impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new PostProcessorBridge(impl);

        try {
            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (_registryLock) {
                _bridgeRegistry[bridge._bridgeId] = bridge;
            }

            var vtablePtr = bridge._vtable;
            var userData = bridge._bridgeId;

            var result = NativeMethods.RegisterPostProcessor(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (_registryLock) {
                    _bridgeRegistry.Remove(bridge._bridgeId);
                }
                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 the bridge ID (userData).
            // The FFI layer holds this ID and will call FreeUserDataCallback with it when done.
            return userData;
        } catch {
            lock (_registryLock) {
                _bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }
}

/// <summary>Static helpers for registering trait implementations</summary>
public static class PostProcessorRegistry {

    private static readonly ConcurrentDictionary<string, PostProcessorBridge> _bridges =
        new ConcurrentDictionary<string, PostProcessorBridge>();

    /// <summary>Register a PostProcessor implementation and return its native handle</summary>
    public static IntPtr RegisterPostProcessor(IPostProcessor impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var bridge = new PostProcessorBridge(impl);
        var userData = bridge._bridgeId;
        var name = impl.Name;

        lock (PostProcessorBridge._registryLock) {
            PostProcessorBridge._bridgeRegistry[userData] = bridge;
        }
        return userData;
    }

    /// <summary>Register a PostProcessor implementation and return its native handle</summary>

    public static IntPtr Register(IPostProcessor impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new PostProcessorBridge(impl);

        try {
            var userData = bridge._bridgeId;
            var vtablePtr = bridge._vtable;

            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (PostProcessorBridge._registryLock) {
                PostProcessorBridge._bridgeRegistry[userData] = bridge;
            }

            var result = NativeMethods.RegisterPostProcessor(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (PostProcessorBridge._registryLock) {
                    PostProcessorBridge._bridgeRegistry.Remove(userData);
                }
                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 {
            lock (PostProcessorBridge._registryLock) {
                PostProcessorBridge._bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }

    /// <summary>Unregister a PostProcessor implementation</summary>
    public static void Unregister(string name) {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));

        var result = NativeMethods.UnregisterPostProcessor(name, out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to unregister {name}: {errorMsg}");
        }

        if (_bridges.TryRemove(name, out var bridge)) {
            bridge.Dispose();
        }
    }

    /// <summary>Clear all registered PostProcessor implementations</summary>
    public static void Clear() {
        var result = NativeMethods.ClearPostProcessor(out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to clear PostProcessor registry: {errorMsg}");
        }

        _bridges.Clear();
    }
}

/// <summary>
/// Bridge interface for Validator trait implementation via native FFI
/// </summary>
public interface IValidator {

    /// <summary>Get the plugin name.</summary>
    string Name { get; }

    /// <summary>Get the plugin version.</summary>
    string Version { get; }

    /// <summary>Initialize the plugin.</summary>
    void Initialize();

    /// <summary>Shut down the plugin.</summary>
    void Shutdown();

    /// <summary>validate</summary>
    void Validate(ExtractionResult Result, ExtractionConfig Config);

    /// <summary>should_validate</summary>
    bool ShouldValidate(ExtractionResult Result, ExtractionConfig Config);

    /// <summary>priority</summary>
    int Priority();
}

/// <summary>
/// Manages the FFI vtable and delegates for a Validator implementation
/// </summary>
public sealed class ValidatorBridge : IDisposable {

    internal readonly IValidator _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    // Keep all delegates alive for the lifetime of the bridge.
    // Delegates must not be GC'd because Rust holds function pointers obtained via
    // GetFunctionPointerForDelegate; those pointers become invalid if the delegate is collected.
    private readonly List<object> _delegateRoots;
    internal readonly IntPtr _bridgeId;
    private int _callbackRefCount = 0;

    // Static registry: maps bridge ID (IntPtr) to bridge instance
    // This prevents GC while Rust holds the bridge ID as userData.
    internal static readonly Dictionary<IntPtr, ValidatorBridge> _bridgeRegistry = new();
    internal static int _nextBridgeId = 1;
    internal static readonly object _registryLock = new();

    // Vtable slot delegates (9)

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

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

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ValidateFn(IntPtr userData, IntPtr result, IntPtr config);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ShouldValidateFn(IntPtr userData, IntPtr result, IntPtr config);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int PriorityFn(IntPtr userData);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeStringFn(IntPtr ptr);

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

    public ValidatorBridge(IValidator impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        // Keep impl alive via normal GCHandle (sufficient for callback rooting).
        // The impl instance itself is not pinned, just kept in the GC root set.
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        // Delegate list roots all delegates by reference to prevent GC.
        // This avoids trying to pin an object array containing references, which .NET 10 forbids.
        _delegateRoots = new List<object>(9);
        _vtable = IntPtr.Zero;
        _disposed = false;
        // Allocate unique bridge ID for registry lookup during callbacks
        lock (_registryLock) {
            _bridgeId = new IntPtr(_nextBridgeId++);
        }
        BuildVtable();
    }

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

        // Slot 0: name_fn
        var nameFn = new NameFn(NameFnCallback);
        _delegateRoots.Add(nameFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 0, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(nameFn));

        // Slot 1: version_fn
        var versionFn = new VersionFn(VersionFnCallback);
        _delegateRoots.Add(versionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 8, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(versionFn));

        // Slot 2: initialize_fn
        var initFn = new InitializeFn(InitializeFnCallback);
        _delegateRoots.Add(initFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 16, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(initFn));

        // Slot 3: shutdown_fn
        var shutdownFn = new ShutdownFn(ShutdownFnCallback);
        _delegateRoots.Add(shutdownFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 24, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shutdownFn));

        // Slot 4: validate_fn
        var validateFn = new ValidateFn(ValidateFnCallback);
        _delegateRoots.Add(validateFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 32, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(validateFn));

        // Slot 5: should_validate_fn
        var shouldValidateFn = new ShouldValidateFn(ShouldValidateFnCallback);
        _delegateRoots.Add(shouldValidateFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 40, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shouldValidateFn));

        // Slot 6: priority_fn
        var priorityFn = new PriorityFn(PriorityFnCallback);
        _delegateRoots.Add(priorityFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 48, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(priorityFn));

        // Slot 7: free_string
        var freeStringFn = new FreeStringFn(FreeStringCallback);
        _delegateRoots.Add(freeStringFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 56, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeStringFn));

        // Slot 8: free_user_data
        var freeFn = new FreeUserDataFn(FreeUserDataCallback);
        _delegateRoots.Add(freeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 64, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeFn));

    }

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

    /// <summary>Called by Rust via FreeUserDataCallback when done with this bridge</summary>
    internal static void FreeUserData(IntPtr bridgeId) {
        lock (_registryLock) {
            // Mark bridge as disposed but DON'T remove from registry yet.
            // Callbacks in flight will still be able to look it up and execute safely.
            // The bridge will stay alive as long as _callbackRefCount > 0.
            if (_bridgeRegistry.TryGetValue(bridgeId, out var bridge)) {
                bridge._disposed = true;
            }
        }
    }

    private void IncrementCallbackRef() {
        lock (_registryLock) {
            _callbackRefCount++;
        }
    }

    private void DecrementCallbackRef() {
        lock (_registryLock) {
            if (_callbackRefCount > 0) {
                _callbackRefCount--;
            }
            // Once refcount reaches 0 and bridge is disposed, remove from registry
            if (_callbackRefCount == 0 && _disposed) {
                _bridgeRegistry.Remove(_bridgeId);
            }
        }
    }

    private int NameFnCallback(IntPtr userData, out IntPtr outName, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _name = null!;
            lock (ValidatorBridge._registryLock) {
                if (!ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outName = IntPtr.Zero;
                    return 1;
                }
                _name = bridge._impl.Name;
            }
            outName = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_name);
            return 0;
        } catch (Exception ex) {
            outName = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _version = null!;
            lock (ValidatorBridge._registryLock) {
                if (!ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outVersion = IntPtr.Zero;
                    return 1;
                }
                _version = bridge._impl.Version;
            }
            outVersion = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_version);
            return 0;
        } catch (Exception ex) {
            outVersion = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (ValidatorBridge._registryLock) {
                if (!ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Initialize();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (ValidatorBridge._registryLock) {
                if (!ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Shutdown();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ValidateFnCallback(IntPtr userData, IntPtr result, IntPtr config) {
        ValidatorBridge? _bridgeFromRegistry = null;
        lock (ValidatorBridge._registryLock) {
            if (ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_result = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(result) ?? "{}";
            var managed_result = JsonSerializer.Deserialize<ExtractionResult>(json_result, FfiJsonExtensions.FfiJsonOptions)!;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<ExtractionConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            bridge._impl.Validate(managed_result, managed_config);
            return 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int ShouldValidateFnCallback(IntPtr userData, IntPtr result, IntPtr config) {
        ValidatorBridge? _bridgeFromRegistry = null;
        lock (ValidatorBridge._registryLock) {
            if (ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_result = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(result) ?? "{}";
            var managed_result = JsonSerializer.Deserialize<ExtractionResult>(json_result, FfiJsonExtensions.FfiJsonOptions)!;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<ExtractionConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ShouldValidate(managed_result, managed_config);
            return methodResult ? 1 : 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int PriorityFnCallback(IntPtr userData) {
        ValidatorBridge? _bridgeFromRegistry = null;
        lock (ValidatorBridge._registryLock) {
            if (ValidatorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.Priority();
            return methodResult;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private void FreeStringCallback(IntPtr ptr) {
        if (ptr != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr);
        }
    }

    private void FreeUserDataCallback(IntPtr userData) {
        if (userData != IntPtr.Zero) {
            ValidatorBridge.FreeUserData(userData);
        }
    }


    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();
        }

        // _delegateRoots is kept as a managed list; GC handles it automatically.
        // No need to free individual delegates or the list.
    }

    /// <summary>Register a Validator implementation and return its native handle</summary>
    public static IntPtr Register(IValidator impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new ValidatorBridge(impl);

        try {
            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (_registryLock) {
                _bridgeRegistry[bridge._bridgeId] = bridge;
            }

            var vtablePtr = bridge._vtable;
            var userData = bridge._bridgeId;

            var result = NativeMethods.RegisterValidator(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (_registryLock) {
                    _bridgeRegistry.Remove(bridge._bridgeId);
                }
                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 the bridge ID (userData).
            // The FFI layer holds this ID and will call FreeUserDataCallback with it when done.
            return userData;
        } catch {
            lock (_registryLock) {
                _bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }
}

/// <summary>Static helpers for registering trait implementations</summary>
public static class ValidatorRegistry {

    private static readonly ConcurrentDictionary<string, ValidatorBridge> _bridges =
        new ConcurrentDictionary<string, ValidatorBridge>();

    /// <summary>Register a Validator implementation and return its native handle</summary>
    public static IntPtr RegisterValidator(IValidator impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var bridge = new ValidatorBridge(impl);
        var userData = bridge._bridgeId;
        var name = impl.Name;

        lock (ValidatorBridge._registryLock) {
            ValidatorBridge._bridgeRegistry[userData] = bridge;
        }
        return userData;
    }

    /// <summary>Register a Validator implementation and return its native handle</summary>

    public static IntPtr Register(IValidator impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new ValidatorBridge(impl);

        try {
            var userData = bridge._bridgeId;
            var vtablePtr = bridge._vtable;

            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (ValidatorBridge._registryLock) {
                ValidatorBridge._bridgeRegistry[userData] = bridge;
            }

            var result = NativeMethods.RegisterValidator(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (ValidatorBridge._registryLock) {
                    ValidatorBridge._bridgeRegistry.Remove(userData);
                }
                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 {
            lock (ValidatorBridge._registryLock) {
                ValidatorBridge._bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }

    /// <summary>Unregister a Validator implementation</summary>
    public static void Unregister(string name) {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));

        var result = NativeMethods.UnregisterValidator(name, out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to unregister {name}: {errorMsg}");
        }

        if (_bridges.TryRemove(name, out var bridge)) {
            bridge.Dispose();
        }
    }

    /// <summary>Clear all registered Validator implementations</summary>
    public static void Clear() {
        var result = NativeMethods.ClearValidator(out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to clear Validator registry: {errorMsg}");
        }

        _bridges.Clear();
    }
}

/// <summary>
/// Bridge interface for EmbeddingBackend trait implementation via native FFI
/// </summary>
public interface IEmbeddingBackend {

    /// <summary>Get the plugin name.</summary>
    string Name { get; }

    /// <summary>Get the plugin version.</summary>
    string Version { get; }

    /// <summary>Initialize the plugin.</summary>
    void Initialize();

    /// <summary>Shut down the plugin.</summary>
    void Shutdown();

    /// <summary>dimensions</summary>
    ulong Dimensions();

    /// <summary>embed</summary>
    List<List<float>> Embed(List<string> Texts);
}

/// <summary>
/// Manages the FFI vtable and delegates for a EmbeddingBackend implementation
/// </summary>
public sealed class EmbeddingBackendBridge : IDisposable {

    internal readonly IEmbeddingBackend _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    // Keep all delegates alive for the lifetime of the bridge.
    // Delegates must not be GC'd because Rust holds function pointers obtained via
    // GetFunctionPointerForDelegate; those pointers become invalid if the delegate is collected.
    private readonly List<object> _delegateRoots;
    internal readonly IntPtr _bridgeId;
    private int _callbackRefCount = 0;

    // Static registry: maps bridge ID (IntPtr) to bridge instance
    // This prevents GC while Rust holds the bridge ID as userData.
    internal static readonly Dictionary<IntPtr, EmbeddingBackendBridge> _bridgeRegistry = new();
    internal static int _nextBridgeId = 1;
    internal static readonly object _registryLock = new();

    // Vtable slot delegates (8)

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

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

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate ulong DimensionsFn(IntPtr userData);

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeStringFn(IntPtr ptr);

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

    public EmbeddingBackendBridge(IEmbeddingBackend impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        // Keep impl alive via normal GCHandle (sufficient for callback rooting).
        // The impl instance itself is not pinned, just kept in the GC root set.
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        // Delegate list roots all delegates by reference to prevent GC.
        // This avoids trying to pin an object array containing references, which .NET 10 forbids.
        _delegateRoots = new List<object>(8);
        _vtable = IntPtr.Zero;
        _disposed = false;
        // Allocate unique bridge ID for registry lookup during callbacks
        lock (_registryLock) {
            _bridgeId = new IntPtr(_nextBridgeId++);
        }
        BuildVtable();
    }

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

        // Slot 0: name_fn
        var nameFn = new NameFn(NameFnCallback);
        _delegateRoots.Add(nameFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 0, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(nameFn));

        // Slot 1: version_fn
        var versionFn = new VersionFn(VersionFnCallback);
        _delegateRoots.Add(versionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 8, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(versionFn));

        // Slot 2: initialize_fn
        var initFn = new InitializeFn(InitializeFnCallback);
        _delegateRoots.Add(initFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 16, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(initFn));

        // Slot 3: shutdown_fn
        var shutdownFn = new ShutdownFn(ShutdownFnCallback);
        _delegateRoots.Add(shutdownFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 24, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shutdownFn));

        // Slot 4: dimensions_fn
        var dimensionsFn = new DimensionsFn(DimensionsFnCallback);
        _delegateRoots.Add(dimensionsFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 32, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(dimensionsFn));

        // Slot 5: embed_fn
        var embedFn = new EmbedFn(EmbedFnCallback);
        _delegateRoots.Add(embedFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 40, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(embedFn));

        // Slot 6: free_string
        var freeStringFn = new FreeStringFn(FreeStringCallback);
        _delegateRoots.Add(freeStringFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 48, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeStringFn));

        // Slot 7: free_user_data
        var freeFn = new FreeUserDataFn(FreeUserDataCallback);
        _delegateRoots.Add(freeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 56, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeFn));

    }

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

    /// <summary>Called by Rust via FreeUserDataCallback when done with this bridge</summary>
    internal static void FreeUserData(IntPtr bridgeId) {
        lock (_registryLock) {
            // Mark bridge as disposed but DON'T remove from registry yet.
            // Callbacks in flight will still be able to look it up and execute safely.
            // The bridge will stay alive as long as _callbackRefCount > 0.
            if (_bridgeRegistry.TryGetValue(bridgeId, out var bridge)) {
                bridge._disposed = true;
            }
        }
    }

    private void IncrementCallbackRef() {
        lock (_registryLock) {
            _callbackRefCount++;
        }
    }

    private void DecrementCallbackRef() {
        lock (_registryLock) {
            if (_callbackRefCount > 0) {
                _callbackRefCount--;
            }
            // Once refcount reaches 0 and bridge is disposed, remove from registry
            if (_callbackRefCount == 0 && _disposed) {
                _bridgeRegistry.Remove(_bridgeId);
            }
        }
    }

    private int NameFnCallback(IntPtr userData, out IntPtr outName, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _name = null!;
            lock (EmbeddingBackendBridge._registryLock) {
                if (!EmbeddingBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outName = IntPtr.Zero;
                    return 1;
                }
                _name = bridge._impl.Name;
            }
            outName = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_name);
            return 0;
        } catch (Exception ex) {
            outName = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _version = null!;
            lock (EmbeddingBackendBridge._registryLock) {
                if (!EmbeddingBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outVersion = IntPtr.Zero;
                    return 1;
                }
                _version = bridge._impl.Version;
            }
            outVersion = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_version);
            return 0;
        } catch (Exception ex) {
            outVersion = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (EmbeddingBackendBridge._registryLock) {
                if (!EmbeddingBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Initialize();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (EmbeddingBackendBridge._registryLock) {
                if (!EmbeddingBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Shutdown();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private ulong DimensionsFnCallback(IntPtr userData) {
        EmbeddingBackendBridge? _bridgeFromRegistry = null;
        lock (EmbeddingBackendBridge._registryLock) {
            if (EmbeddingBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.Dimensions();
            return methodResult;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int EmbedFnCallback(IntPtr userData, IntPtr texts, out IntPtr outResult, out IntPtr outError) {
        EmbeddingBackendBridge? _bridgeFromRegistry = null;
        lock (EmbeddingBackendBridge._registryLock) {
            if (EmbeddingBackendBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_texts = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(texts) ?? "{}";
            var managed_texts = JsonSerializer.Deserialize<List<string>>(json_texts, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.Embed(managed_texts);
            try {
                string __result_str = (ToJsonString(methodResult)) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private void FreeStringCallback(IntPtr ptr) {
        if (ptr != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr);
        }
    }

    private void FreeUserDataCallback(IntPtr userData) {
        if (userData != IntPtr.Zero) {
            EmbeddingBackendBridge.FreeUserData(userData);
        }
    }


    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();
        }

        // _delegateRoots is kept as a managed list; GC handles it automatically.
        // No need to free individual delegates or the list.
    }

    /// <summary>Register a EmbeddingBackend implementation and return its native handle</summary>
    public static IntPtr Register(IEmbeddingBackend impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new EmbeddingBackendBridge(impl);

        try {
            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (_registryLock) {
                _bridgeRegistry[bridge._bridgeId] = bridge;
            }

            var vtablePtr = bridge._vtable;
            var userData = bridge._bridgeId;

            var result = NativeMethods.RegisterEmbeddingBackend(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (_registryLock) {
                    _bridgeRegistry.Remove(bridge._bridgeId);
                }
                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 the bridge ID (userData).
            // The FFI layer holds this ID and will call FreeUserDataCallback with it when done.
            return userData;
        } catch {
            lock (_registryLock) {
                _bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }
}

/// <summary>Static helpers for registering trait implementations</summary>
public static class EmbeddingBackendRegistry {

    private static readonly ConcurrentDictionary<string, EmbeddingBackendBridge> _bridges =
        new ConcurrentDictionary<string, EmbeddingBackendBridge>();

    /// <summary>Register a EmbeddingBackend implementation and return its native handle</summary>
    public static IntPtr RegisterEmbeddingBackend(IEmbeddingBackend impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var bridge = new EmbeddingBackendBridge(impl);
        var userData = bridge._bridgeId;
        var name = impl.Name;

        lock (EmbeddingBackendBridge._registryLock) {
            EmbeddingBackendBridge._bridgeRegistry[userData] = bridge;
        }
        return userData;
    }

    /// <summary>Register a EmbeddingBackend implementation and return its native handle</summary>

    public static IntPtr Register(IEmbeddingBackend impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new EmbeddingBackendBridge(impl);

        try {
            var userData = bridge._bridgeId;
            var vtablePtr = bridge._vtable;

            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (EmbeddingBackendBridge._registryLock) {
                EmbeddingBackendBridge._bridgeRegistry[userData] = bridge;
            }

            var result = NativeMethods.RegisterEmbeddingBackend(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (EmbeddingBackendBridge._registryLock) {
                    EmbeddingBackendBridge._bridgeRegistry.Remove(userData);
                }
                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 {
            lock (EmbeddingBackendBridge._registryLock) {
                EmbeddingBackendBridge._bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }

    /// <summary>Unregister a EmbeddingBackend implementation</summary>
    public static void Unregister(string name) {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));

        var result = NativeMethods.UnregisterEmbeddingBackend(name, out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to unregister {name}: {errorMsg}");
        }

        if (_bridges.TryRemove(name, out var bridge)) {
            bridge.Dispose();
        }
    }

    /// <summary>Clear all registered EmbeddingBackend implementations</summary>
    public static void Clear() {
        var result = NativeMethods.ClearEmbeddingBackend(out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to clear EmbeddingBackend registry: {errorMsg}");
        }

        _bridges.Clear();
    }
}

/// <summary>
/// Bridge interface for DocumentExtractor trait implementation via native FFI
/// </summary>
public interface IDocumentExtractor {

    /// <summary>Get the plugin name.</summary>
    string Name { get; }

    /// <summary>Get the plugin version.</summary>
    string Version { get; }

    /// <summary>Initialize the plugin.</summary>
    void Initialize();

    /// <summary>Shut down the plugin.</summary>
    void Shutdown();

    /// <summary>extract_bytes</summary>
    string ExtractBytes(byte[] Content, string MimeType, ExtractionConfig Config);

    /// <summary>extract_file</summary>
    string ExtractFile(string Path, string MimeType, ExtractionConfig Config);

    /// <summary>supported_mime_types</summary>
    List<string> SupportedMimeTypes();

    /// <summary>priority</summary>
    int Priority();

    /// <summary>can_handle</summary>
    bool CanHandle(string Path, string MimeType);
}

/// <summary>
/// Manages the FFI vtable and delegates for a DocumentExtractor implementation
/// </summary>
public sealed class DocumentExtractorBridge : IDisposable {

    internal readonly IDocumentExtractor _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    // Keep all delegates alive for the lifetime of the bridge.
    // Delegates must not be GC'd because Rust holds function pointers obtained via
    // GetFunctionPointerForDelegate; those pointers become invalid if the delegate is collected.
    private readonly List<object> _delegateRoots;
    internal readonly IntPtr _bridgeId;
    private int _callbackRefCount = 0;

    // Static registry: maps bridge ID (IntPtr) to bridge instance
    // This prevents GC while Rust holds the bridge ID as userData.
    internal static readonly Dictionary<IntPtr, DocumentExtractorBridge> _bridgeRegistry = new();
    internal static int _nextBridgeId = 1;
    internal static readonly object _registryLock = new();

    // Vtable slot delegates (11)

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

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

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ExtractBytesFn(IntPtr userData, IntPtr content, UIntPtr contentLen, IntPtr mimeType, IntPtr config, out IntPtr outResult, out IntPtr outError);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int ExtractFileFn(IntPtr userData, IntPtr path, IntPtr mimeType, IntPtr config, out IntPtr outResult, out IntPtr outError);

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int PriorityFn(IntPtr userData);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int CanHandleFn(IntPtr userData, IntPtr path, IntPtr mimeType);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeStringFn(IntPtr ptr);

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

    public DocumentExtractorBridge(IDocumentExtractor impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        // Keep impl alive via normal GCHandle (sufficient for callback rooting).
        // The impl instance itself is not pinned, just kept in the GC root set.
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        // Delegate list roots all delegates by reference to prevent GC.
        // This avoids trying to pin an object array containing references, which .NET 10 forbids.
        _delegateRoots = new List<object>(11);
        _vtable = IntPtr.Zero;
        _disposed = false;
        // Allocate unique bridge ID for registry lookup during callbacks
        lock (_registryLock) {
            _bridgeId = new IntPtr(_nextBridgeId++);
        }
        BuildVtable();
    }

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

        // Slot 0: name_fn
        var nameFn = new NameFn(NameFnCallback);
        _delegateRoots.Add(nameFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 0, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(nameFn));

        // Slot 1: version_fn
        var versionFn = new VersionFn(VersionFnCallback);
        _delegateRoots.Add(versionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 8, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(versionFn));

        // Slot 2: initialize_fn
        var initFn = new InitializeFn(InitializeFnCallback);
        _delegateRoots.Add(initFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 16, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(initFn));

        // Slot 3: shutdown_fn
        var shutdownFn = new ShutdownFn(ShutdownFnCallback);
        _delegateRoots.Add(shutdownFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 24, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shutdownFn));

        // Slot 4: extract_bytes_fn
        var extractBytesFn = new ExtractBytesFn(ExtractBytesFnCallback);
        _delegateRoots.Add(extractBytesFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 32, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(extractBytesFn));

        // Slot 5: extract_file_fn
        var extractFileFn = new ExtractFileFn(ExtractFileFnCallback);
        _delegateRoots.Add(extractFileFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 40, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(extractFileFn));

        // Slot 6: supported_mime_types_fn
        var supportedMimeTypesFn = new SupportedMimeTypesFn(SupportedMimeTypesFnCallback);
        _delegateRoots.Add(supportedMimeTypesFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 48, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(supportedMimeTypesFn));

        // Slot 7: priority_fn
        var priorityFn = new PriorityFn(PriorityFnCallback);
        _delegateRoots.Add(priorityFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 56, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(priorityFn));

        // Slot 8: can_handle_fn
        var canHandleFn = new CanHandleFn(CanHandleFnCallback);
        _delegateRoots.Add(canHandleFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 64, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(canHandleFn));

        // Slot 9: free_string
        var freeStringFn = new FreeStringFn(FreeStringCallback);
        _delegateRoots.Add(freeStringFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 72, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeStringFn));

        // Slot 10: free_user_data
        var freeFn = new FreeUserDataFn(FreeUserDataCallback);
        _delegateRoots.Add(freeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 80, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeFn));

    }

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

    /// <summary>Called by Rust via FreeUserDataCallback when done with this bridge</summary>
    internal static void FreeUserData(IntPtr bridgeId) {
        lock (_registryLock) {
            // Mark bridge as disposed but DON'T remove from registry yet.
            // Callbacks in flight will still be able to look it up and execute safely.
            // The bridge will stay alive as long as _callbackRefCount > 0.
            if (_bridgeRegistry.TryGetValue(bridgeId, out var bridge)) {
                bridge._disposed = true;
            }
        }
    }

    private void IncrementCallbackRef() {
        lock (_registryLock) {
            _callbackRefCount++;
        }
    }

    private void DecrementCallbackRef() {
        lock (_registryLock) {
            if (_callbackRefCount > 0) {
                _callbackRefCount--;
            }
            // Once refcount reaches 0 and bridge is disposed, remove from registry
            if (_callbackRefCount == 0 && _disposed) {
                _bridgeRegistry.Remove(_bridgeId);
            }
        }
    }

    private int NameFnCallback(IntPtr userData, out IntPtr outName, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _name = null!;
            lock (DocumentExtractorBridge._registryLock) {
                if (!DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outName = IntPtr.Zero;
                    return 1;
                }
                _name = bridge._impl.Name;
            }
            outName = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_name);
            return 0;
        } catch (Exception ex) {
            outName = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _version = null!;
            lock (DocumentExtractorBridge._registryLock) {
                if (!DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outVersion = IntPtr.Zero;
                    return 1;
                }
                _version = bridge._impl.Version;
            }
            outVersion = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_version);
            return 0;
        } catch (Exception ex) {
            outVersion = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (DocumentExtractorBridge._registryLock) {
                if (!DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Initialize();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (DocumentExtractorBridge._registryLock) {
                if (!DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Shutdown();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ExtractBytesFnCallback(IntPtr userData, IntPtr content, UIntPtr contentLen, IntPtr mimeType, IntPtr config, out IntPtr outResult, out IntPtr outError) {
        DocumentExtractorBridge? _bridgeFromRegistry = null;
        lock (DocumentExtractorBridge._registryLock) {
            if (DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var managed_content = new byte[(int)contentLen];
            Marshal.Copy(content, managed_content, 0, (int)contentLen);
            var managed_mimeType = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(mimeType) ?? string.Empty;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<ExtractionConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ExtractBytes(managed_content, managed_mimeType, managed_config);
            try {
                string __result_str = (ToJsonString(methodResult)) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int ExtractFileFnCallback(IntPtr userData, IntPtr path, IntPtr mimeType, IntPtr config, out IntPtr outResult, out IntPtr outError) {
        DocumentExtractorBridge? _bridgeFromRegistry = null;
        lock (DocumentExtractorBridge._registryLock) {
            if (DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_path = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(path) ?? "{}";
            var managed_path = JsonSerializer.Deserialize<string>(json_path, FfiJsonExtensions.FfiJsonOptions)!;
            var managed_mimeType = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(mimeType) ?? string.Empty;
            var json_config = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(config) ?? "{}";
            var managed_config = JsonSerializer.Deserialize<ExtractionConfig>(json_config, FfiJsonExtensions.FfiJsonOptions)!;
            var methodResult = bridge._impl.ExtractFile(managed_path, managed_mimeType, managed_config);
            try {
                string __result_str = (ToJsonString(methodResult)) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int SupportedMimeTypesFnCallback(IntPtr userData, out IntPtr outResult, out IntPtr outError) {
        DocumentExtractorBridge? _bridgeFromRegistry = null;
        lock (DocumentExtractorBridge._registryLock) {
            if (DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.SupportedMimeTypes();
            try {
                string __result_str = (ToJsonString(methodResult)) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int PriorityFnCallback(IntPtr userData) {
        DocumentExtractorBridge? _bridgeFromRegistry = null;
        lock (DocumentExtractorBridge._registryLock) {
            if (DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var methodResult = bridge._impl.Priority();
            return methodResult;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private int CanHandleFnCallback(IntPtr userData, IntPtr path, IntPtr mimeType) {
        DocumentExtractorBridge? _bridgeFromRegistry = null;
        lock (DocumentExtractorBridge._registryLock) {
            if (DocumentExtractorBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            return 0;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_path = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(path) ?? "{}";
            var managed_path = JsonSerializer.Deserialize<string>(json_path, FfiJsonExtensions.FfiJsonOptions)!;
            var managed_mimeType = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(mimeType) ?? string.Empty;
            var methodResult = bridge._impl.CanHandle(managed_path, managed_mimeType);
            return methodResult ? 1 : 0;
        } catch (Exception) {
            return 0;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private void FreeStringCallback(IntPtr ptr) {
        if (ptr != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr);
        }
    }

    private void FreeUserDataCallback(IntPtr userData) {
        if (userData != IntPtr.Zero) {
            DocumentExtractorBridge.FreeUserData(userData);
        }
    }


    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();
        }

        // _delegateRoots is kept as a managed list; GC handles it automatically.
        // No need to free individual delegates or the list.
    }

    /// <summary>Register a DocumentExtractor implementation and return its native handle</summary>
    public static IntPtr Register(IDocumentExtractor impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new DocumentExtractorBridge(impl);

        try {
            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (_registryLock) {
                _bridgeRegistry[bridge._bridgeId] = bridge;
            }

            var vtablePtr = bridge._vtable;
            var userData = bridge._bridgeId;

            var result = NativeMethods.RegisterDocumentExtractor(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (_registryLock) {
                    _bridgeRegistry.Remove(bridge._bridgeId);
                }
                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 the bridge ID (userData).
            // The FFI layer holds this ID and will call FreeUserDataCallback with it when done.
            return userData;
        } catch {
            lock (_registryLock) {
                _bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }
}

/// <summary>Static helpers for registering trait implementations</summary>
public static class DocumentExtractorRegistry {

    private static readonly ConcurrentDictionary<string, DocumentExtractorBridge> _bridges =
        new ConcurrentDictionary<string, DocumentExtractorBridge>();

    /// <summary>Register a DocumentExtractor implementation and return its native handle</summary>
    public static IntPtr RegisterDocumentExtractor(IDocumentExtractor impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var bridge = new DocumentExtractorBridge(impl);
        var userData = bridge._bridgeId;
        var name = impl.Name;

        lock (DocumentExtractorBridge._registryLock) {
            DocumentExtractorBridge._bridgeRegistry[userData] = bridge;
        }
        return userData;
    }

    /// <summary>Register a DocumentExtractor implementation and return its native handle</summary>

    public static IntPtr Register(IDocumentExtractor impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new DocumentExtractorBridge(impl);

        try {
            var userData = bridge._bridgeId;
            var vtablePtr = bridge._vtable;

            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (DocumentExtractorBridge._registryLock) {
                DocumentExtractorBridge._bridgeRegistry[userData] = bridge;
            }

            var result = NativeMethods.RegisterDocumentExtractor(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (DocumentExtractorBridge._registryLock) {
                    DocumentExtractorBridge._bridgeRegistry.Remove(userData);
                }
                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 {
            lock (DocumentExtractorBridge._registryLock) {
                DocumentExtractorBridge._bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }

    /// <summary>Unregister a DocumentExtractor implementation</summary>
    public static void Unregister(string name) {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));

        var result = NativeMethods.UnregisterDocumentExtractor(name, out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to unregister {name}: {errorMsg}");
        }

        if (_bridges.TryRemove(name, out var bridge)) {
            bridge.Dispose();
        }
    }

    /// <summary>Clear all registered DocumentExtractor implementations</summary>
    public static void Clear() {
        var result = NativeMethods.ClearDocumentExtractor(out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to clear DocumentExtractor registry: {errorMsg}");
        }

        _bridges.Clear();
    }
}

/// <summary>
/// Bridge interface for Renderer trait implementation via native FFI
/// </summary>
public interface IRenderer {

    /// <summary>Get the plugin name.</summary>
    string Name { get; }

    /// <summary>Get the plugin version.</summary>
    string Version { get; }

    /// <summary>Initialize the plugin.</summary>
    void Initialize();

    /// <summary>Shut down the plugin.</summary>
    void Shutdown();

    /// <summary>render</summary>
    string Render(string Doc);
}

/// <summary>
/// Manages the FFI vtable and delegates for a Renderer implementation
/// </summary>
public sealed class RendererBridge : IDisposable {

    internal readonly IRenderer _impl;
    private readonly GCHandle _implHandle;
    internal IntPtr _vtable;
    private bool _disposed;
    // Keep all delegates alive for the lifetime of the bridge.
    // Delegates must not be GC'd because Rust holds function pointers obtained via
    // GetFunctionPointerForDelegate; those pointers become invalid if the delegate is collected.
    private readonly List<object> _delegateRoots;
    internal readonly IntPtr _bridgeId;
    private int _callbackRefCount = 0;

    // Static registry: maps bridge ID (IntPtr) to bridge instance
    // This prevents GC while Rust holds the bridge ID as userData.
    internal static readonly Dictionary<IntPtr, RendererBridge> _bridgeRegistry = new();
    internal static int _nextBridgeId = 1;
    internal static readonly object _registryLock = new();

    // Vtable slot delegates (7)

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

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

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

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

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

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FreeStringFn(IntPtr ptr);

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

    public RendererBridge(IRenderer impl) {
        _impl = impl ?? throw new ArgumentNullException(nameof(impl));
        // Keep impl alive via normal GCHandle (sufficient for callback rooting).
        // The impl instance itself is not pinned, just kept in the GC root set.
        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
        // Delegate list roots all delegates by reference to prevent GC.
        // This avoids trying to pin an object array containing references, which .NET 10 forbids.
        _delegateRoots = new List<object>(7);
        _vtable = IntPtr.Zero;
        _disposed = false;
        // Allocate unique bridge ID for registry lookup during callbacks
        lock (_registryLock) {
            _bridgeId = new IntPtr(_nextBridgeId++);
        }
        BuildVtable();
    }

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

        // Slot 0: name_fn
        var nameFn = new NameFn(NameFnCallback);
        _delegateRoots.Add(nameFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 0, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(nameFn));

        // Slot 1: version_fn
        var versionFn = new VersionFn(VersionFnCallback);
        _delegateRoots.Add(versionFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 8, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(versionFn));

        // Slot 2: initialize_fn
        var initFn = new InitializeFn(InitializeFnCallback);
        _delegateRoots.Add(initFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 16, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(initFn));

        // Slot 3: shutdown_fn
        var shutdownFn = new ShutdownFn(ShutdownFnCallback);
        _delegateRoots.Add(shutdownFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 24, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(shutdownFn));

        // Slot 4: render_fn
        var renderFn = new RenderFn(RenderFnCallback);
        _delegateRoots.Add(renderFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 32, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(renderFn));

        // Slot 5: free_string
        var freeStringFn = new FreeStringFn(FreeStringCallback);
        _delegateRoots.Add(freeStringFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 40, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeStringFn));

        // Slot 6: free_user_data
        var freeFn = new FreeUserDataFn(FreeUserDataCallback);
        _delegateRoots.Add(freeFn);
        global::System.Runtime.InteropServices.Marshal.WriteIntPtr(_vtable, 48, global::System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(freeFn));

    }

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

    /// <summary>Called by Rust via FreeUserDataCallback when done with this bridge</summary>
    internal static void FreeUserData(IntPtr bridgeId) {
        lock (_registryLock) {
            // Mark bridge as disposed but DON'T remove from registry yet.
            // Callbacks in flight will still be able to look it up and execute safely.
            // The bridge will stay alive as long as _callbackRefCount > 0.
            if (_bridgeRegistry.TryGetValue(bridgeId, out var bridge)) {
                bridge._disposed = true;
            }
        }
    }

    private void IncrementCallbackRef() {
        lock (_registryLock) {
            _callbackRefCount++;
        }
    }

    private void DecrementCallbackRef() {
        lock (_registryLock) {
            if (_callbackRefCount > 0) {
                _callbackRefCount--;
            }
            // Once refcount reaches 0 and bridge is disposed, remove from registry
            if (_callbackRefCount == 0 && _disposed) {
                _bridgeRegistry.Remove(_bridgeId);
            }
        }
    }

    private int NameFnCallback(IntPtr userData, out IntPtr outName, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _name = null!;
            lock (RendererBridge._registryLock) {
                if (!RendererBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outName = IntPtr.Zero;
                    return 1;
                }
                _name = bridge._impl.Name;
            }
            outName = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_name);
            return 0;
        } catch (Exception ex) {
            outName = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion, out IntPtr outError) {
        try {
            outError = IntPtr.Zero;
            string _version = null!;
            lock (RendererBridge._registryLock) {
                if (!RendererBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outVersion = IntPtr.Zero;
                    return 1;
                }
                _version = bridge._impl.Version;
            }
            outVersion = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_version);
            return 0;
        } catch (Exception ex) {
            outVersion = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (RendererBridge._registryLock) {
                if (!RendererBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Initialize();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {
        try {
            lock (RendererBridge._registryLock) {
                if (!RendererBridge._bridgeRegistry.TryGetValue(userData, out var bridge)) {
                    outError = IntPtr.Zero;
                    return 1;
                }
                bridge._impl.Shutdown();
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(ex.Message ?? ex.GetType().Name);
            return 1;
        }
    }

    private int RenderFnCallback(IntPtr userData, IntPtr doc, out IntPtr outResult, out IntPtr outError) {
        RendererBridge? _bridgeFromRegistry = null;
        lock (RendererBridge._registryLock) {
            if (RendererBridge._bridgeRegistry.TryGetValue(userData, out var bridgeFromRegistry)) {
                _bridgeFromRegistry = bridgeFromRegistry;
                // Increment callback refcount to prevent GC while callback executes
                _bridgeFromRegistry.IncrementCallbackRef();
            }
        }
        if (_bridgeFromRegistry == null) {
            outResult = IntPtr.Zero;
            outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8($"Bridge not found for userData (likely unregistered): {userData}");
            return 1;
        }
        try {
            var bridge = _bridgeFromRegistry!;
            var json_doc = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(doc) ?? "{}";
            var methodResult = bridge._impl.Render(json_doc);
            try {
                string __result_str = (ToJsonString(methodResult)) ?? string.Empty;
                outResult = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(__result_str);
            } catch {
                outResult = IntPtr.Zero;
                throw;
            }
            outError = IntPtr.Zero;
            return 0;
        } catch (Exception ex) {
            outResult = IntPtr.Zero;
            outError = IntPtr.Zero;
            // Attempt to marshal exception message, but on ANY failure just leave outError null
            try {
                string _errMsg = null!;
                try {
                    _errMsg = ex?.Message ?? ex?.GetType()?.Name ?? "Unknown exception";
                } catch {
                    _errMsg = "Callback failed";
                }
                if (!string.IsNullOrEmpty(_errMsg)) {
                    outError = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(_errMsg);
                }
            } catch {
                // Marshalling failed; outError stays null — Rust will see return code 1
            }
            return 1;
        } finally {
            if (_bridgeFromRegistry != null) {
                try { _bridgeFromRegistry.DecrementCallbackRef(); } catch { /* Bridge already removed from registry */ }
            }
        }
    }

    private void FreeStringCallback(IntPtr ptr) {
        if (ptr != IntPtr.Zero) {
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr);
        }
    }

    private void FreeUserDataCallback(IntPtr userData) {
        if (userData != IntPtr.Zero) {
            RendererBridge.FreeUserData(userData);
        }
    }


    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();
        }

        // _delegateRoots is kept as a managed list; GC handles it automatically.
        // No need to free individual delegates or the list.
    }

    /// <summary>Register a Renderer implementation and return its native handle</summary>
    public static IntPtr Register(IRenderer impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new RendererBridge(impl);

        try {
            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (_registryLock) {
                _bridgeRegistry[bridge._bridgeId] = bridge;
            }

            var vtablePtr = bridge._vtable;
            var userData = bridge._bridgeId;

            var result = NativeMethods.RegisterRenderer(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (_registryLock) {
                    _bridgeRegistry.Remove(bridge._bridgeId);
                }
                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 the bridge ID (userData).
            // The FFI layer holds this ID and will call FreeUserDataCallback with it when done.
            return userData;
        } catch {
            lock (_registryLock) {
                _bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }
}

/// <summary>Static helpers for registering trait implementations</summary>
public static class RendererRegistry {

    private static readonly ConcurrentDictionary<string, RendererBridge> _bridges =
        new ConcurrentDictionary<string, RendererBridge>();

    /// <summary>Register a Renderer implementation and return its native handle</summary>
    public static IntPtr RegisterRenderer(IRenderer impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var bridge = new RendererBridge(impl);
        var userData = bridge._bridgeId;
        var name = impl.Name;

        lock (RendererBridge._registryLock) {
            RendererBridge._bridgeRegistry[userData] = bridge;
        }
        return userData;
    }

    /// <summary>Register a Renderer implementation and return its native handle</summary>

    public static IntPtr Register(IRenderer impl) {
        if (impl == null)
            throw new ArgumentNullException(nameof(impl));

        var name = impl.Name;


        var bridge = new RendererBridge(impl);

        try {
            var userData = bridge._bridgeId;
            var vtablePtr = bridge._vtable;

            // Register bridge in the static registry using its unique ID.
            // This keeps the bridge alive while Rust holds the ID (userData).
            lock (RendererBridge._registryLock) {
                RendererBridge._bridgeRegistry[userData] = bridge;
            }

            var result = NativeMethods.RegisterRenderer(name, vtablePtr, userData, out var outError);
            if (result != 0) {
                lock (RendererBridge._registryLock) {
                    RendererBridge._bridgeRegistry.Remove(userData);
                }
                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 {
            lock (RendererBridge._registryLock) {
                RendererBridge._bridgeRegistry.Remove(bridge._bridgeId);
            }
            bridge.Dispose();
            throw;
        }
    }

    /// <summary>Unregister a Renderer implementation</summary>
    public static void Unregister(string name) {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be empty", nameof(name));

        var result = NativeMethods.UnregisterRenderer(name, out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to unregister {name}: {errorMsg}");
        }

        if (_bridges.TryRemove(name, out var bridge)) {
            bridge.Dispose();
        }
    }

    /// <summary>Clear all registered Renderer implementations</summary>
    public static void Clear() {
        var result = NativeMethods.ClearRenderer(out var outError);
        if (result != 0) {
            var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
            global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
            throw new InvalidOperationException($"Failed to clear Renderer registry: {errorMsg}");
        }

        _bridges.Clear();
    }
}

/// <summary>FFI JSON serialization extension methods and options</summary>
internal static class FfiJsonExtensions {

    /// <summary>Global JsonSerializerOptions for FFI marshalling with relaxed numeric handling.
    /// Supports: enum-to-snake_case conversion, ignoring default values, and reading numeric values from strings.
    /// Used by both trait bridges and callback deserialization.</summary>
    public static readonly JsonSerializerOptions FfiJsonOptions = new()
    {
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) },
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
        NumberHandling = JsonNumberHandling.AllowReadingFromString
    };

    /// <summary>Serialize any object to JSON for FFI marshalling</summary>
    internal static string ToFfiJson<T>(this T value) {
        return JsonSerializer.Serialize(value, FfiJsonOptions);
    }

}
