// This file is auto-generated by alef. DO NOT EDIT.
// alef:hash:4e15143f4af1ae8bafbdb1506ef057da924484c66a19483966333558ad437e75
#nullable enable

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;using System.Threading.Tasks;
namespace Kreuzberg;

public static class KreuzbergLib
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) },
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
    };

    private static readonly JsonSerializerOptions JsonSerializationOptions = new()
    {
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) },
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    /// <summary>
    /// Extract content from a byte array.
    ///
    /// This is the main entry point for in-memory extraction. It performs the following steps:
    /// 1. Validate MIME type
    /// 2. Handle legacy format conversion if needed
    /// 3. Select appropriate extractor from registry
    /// 4. Extract content
    /// 5. Run post-processing pipeline
    /// </summary>
    /// <param name="content">The byte array to extract</param>
    ///
    /// <param name="mime_type">MIME type of the content</param>
    ///
    /// <param name="config">Extraction configuration</param>
    ///
    /// <returns>An `ExtractionResult` containing the extracted content and metadata.</returns>
    ///
    /// <exception cref="KreuzbergException">Returns `KreuzbergError.Validation` if MIME type is invalid.
    /// Returns `KreuzbergError.UnsupportedFormat` if MIME type is not supported.</exception>
    /// <param name="content"></param>
    /// <param name="mimeType"></param>
    /// <param name="config"></param>
    public static async Task<ExtractionResult> ExtractBytesAsync(byte[] content, string mimeType, ExtractionConfig? config)
    {
        ArgumentNullException.ThrowIfNull(content);
        ArgumentNullException.ThrowIfNull(mimeType);
        var contentHandle = GCHandle.Alloc(content, GCHandleType.Pinned);
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            return await Task.Run(() =>
            {
                var nativeResult = NativeMethods.ExtractBytes(
                contentHandle.AddrOfPinnedObject(),
                (UIntPtr)content.Length,
                mimeType,
                configHandle
                );
                if (nativeResult == IntPtr.Zero)
                {
                    throw GetLastError();
                }
                var jsonPtr = NativeMethods.ExtractionResultToJson(nativeResult);
                var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
                NativeMethods.FreeString(jsonPtr);
                NativeMethods.ExtractionResultFree(nativeResult);
                var returnValue = JsonSerializer.Deserialize<ExtractionResult>(json ?? "null", JsonOptions)!;
                return returnValue;
            });
        }
        finally
        {
            if (contentHandle.IsAllocated) contentHandle.Free();
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Extract content from a file.
    ///
    /// This is the main entry point for file-based extraction. It performs the following steps:
    /// 1. Check cache for existing result (if caching enabled)
    /// 2. Detect or validate MIME type
    /// 3. Select appropriate extractor from registry
    /// 4. Extract content
    /// 5. Run post-processing pipeline
    /// 6. Store result in cache (if caching enabled)
    /// </summary>
    /// <param name="path">Path to the file to extract</param>
    ///
    /// <param name="mime_type">Optional MIME type override. If null, will be auto-detected</param>
    ///
    /// <param name="config">Extraction configuration</param>
    ///
    /// <returns>An `ExtractionResult` containing the extracted content and metadata.</returns>
    ///
    /// <exception cref="KreuzbergException">Returns `KreuzbergError.Io` if the file doesn't exist (NotFound) or for other file I/O errors.
    /// Returns `KreuzbergError.UnsupportedFormat` if MIME type is not supported.</exception>
    /// <param name="path"></param>
    /// <param name="mimeType">Optional.</param>
    /// <param name="config"></param>
    public static async Task<ExtractionResult> ExtractFileAsync(string path, string? mimeType, ExtractionConfig? config)
    {
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            return await Task.Run(() =>
            {
                var nativeResult = NativeMethods.ExtractFile(
                path,
                mimeType!,
                configHandle
                );
                if (nativeResult == IntPtr.Zero)
                {
                    throw GetLastError();
                }
                var jsonPtr = NativeMethods.ExtractionResultToJson(nativeResult);
                var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
                NativeMethods.FreeString(jsonPtr);
                NativeMethods.ExtractionResultFree(nativeResult);
                var returnValue = JsonSerializer.Deserialize<ExtractionResult>(json ?? "null", JsonOptions)!;
                return returnValue;
            });
        }
        finally
        {
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Synchronous wrapper for `extract_file`.
    ///
    /// This is a convenience function that blocks the current thread until extraction completes.
    /// For async code, use `extract_file` directly.
    ///
    /// Uses the global Tokio runtime for 100x+ performance improvement over creating
    /// a new runtime per call. Always uses the global runtime to avoid nested runtime issues.
    ///
    /// This function is only available with the `tokio-runtime` feature. For WASM targets,
    /// use a truly synchronous extraction approach instead.
    /// </summary>
    /// <param name="path"></param>
    /// <param name="mimeType">Optional.</param>
    /// <param name="config"></param>
    public static ExtractionResult ExtractFileSync(string path, string? mimeType, ExtractionConfig? config)
    {
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            var nativeResult = NativeMethods.ExtractFileSync(
            path,
            mimeType!,
            configHandle
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var jsonPtr = NativeMethods.ExtractionResultToJson(nativeResult);
            var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
            NativeMethods.FreeString(jsonPtr);
            NativeMethods.ExtractionResultFree(nativeResult);
            var returnValue = JsonSerializer.Deserialize<ExtractionResult>(json ?? "null", JsonOptions)!;
            return returnValue;
        }
        finally
        {
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Synchronous wrapper for `extract_bytes`.
    ///
    /// Uses the global Tokio runtime for 100x+ performance improvement over creating
    /// a new runtime per call.
    ///
    /// With the `tokio-runtime` feature, this blocks the current thread using the global
    /// Tokio runtime. Without it (WASM), this calls a truly synchronous implementation.
    /// </summary>
    /// <param name="content"></param>
    /// <param name="mimeType"></param>
    /// <param name="config"></param>
    public static ExtractionResult ExtractBytesSync(byte[] content, string mimeType, ExtractionConfig? config)
    {
        ArgumentNullException.ThrowIfNull(content);
        ArgumentNullException.ThrowIfNull(mimeType);
        var contentHandle = GCHandle.Alloc(content, GCHandleType.Pinned);
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            var nativeResult = NativeMethods.ExtractBytesSync(
            contentHandle.AddrOfPinnedObject(),
            (UIntPtr)content.Length,
            mimeType,
            configHandle
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var jsonPtr = NativeMethods.ExtractionResultToJson(nativeResult);
            var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
            NativeMethods.FreeString(jsonPtr);
            NativeMethods.ExtractionResultFree(nativeResult);
            var returnValue = JsonSerializer.Deserialize<ExtractionResult>(json ?? "null", JsonOptions)!;
            return returnValue;
        }
        finally
        {
            if (contentHandle.IsAllocated) contentHandle.Free();
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Synchronous wrapper for `batch_extract_files`.
    ///
    /// Uses the global Tokio runtime for optimal performance.
    /// Only available with `tokio-runtime` (WASM has no filesystem).
    /// </summary>
    /// <param name="items"></param>
    /// <param name="config"></param>
    public static List<ExtractionResult> BatchExtractFilesSync(List<BatchFileItem> items, ExtractionConfig? config)
    {
        var itemsJson = JsonSerializer.Serialize(items, JsonSerializationOptions);
        var itemsHandle = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(itemsJson);
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            var nativeResult = NativeMethods.BatchExtractFilesSync(
            itemsHandle,
            configHandle
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
            NativeMethods.FreeString(nativeResult);
            var returnValue = JsonSerializer.Deserialize<List<ExtractionResult>>(json ?? "null", JsonOptions)!;
            return returnValue;
        }
        finally
        {
            if (itemsHandle != global::System.IntPtr.Zero) global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(itemsHandle);
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Synchronous wrapper for `batch_extract_bytes`.
    ///
    /// Uses the global Tokio runtime for optimal performance.
    /// With the `tokio-runtime` feature, this blocks the current thread using the global
    /// Tokio runtime. Without it (WASM), this calls a truly synchronous implementation
    /// that iterates through items and calls `extract_bytes_sync()`.
    /// </summary>
    /// <param name="items"></param>
    /// <param name="config"></param>
    public static List<ExtractionResult> BatchExtractBytesSync(List<BatchBytesItem> items, ExtractionConfig? config)
    {
        var itemsJson = JsonSerializer.Serialize(items, JsonSerializationOptions);
        var itemsHandle = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(itemsJson);
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            var nativeResult = NativeMethods.BatchExtractBytesSync(
            itemsHandle,
            configHandle
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
            NativeMethods.FreeString(nativeResult);
            var returnValue = JsonSerializer.Deserialize<List<ExtractionResult>>(json ?? "null", JsonOptions)!;
            return returnValue;
        }
        finally
        {
            if (itemsHandle != global::System.IntPtr.Zero) global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(itemsHandle);
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Extract content from multiple files concurrently.
    ///
    /// This function processes multiple files in parallel, automatically managing
    /// concurrency to prevent resource exhaustion. The concurrency limit can be
    /// configured via `ExtractionConfig.max_concurrent_extractions` or defaults
    /// to `(num_cpus * 1.5).ceil()`.
    ///
    /// Each file can optionally specify a `FileExtractionConfig` that overrides specific
    /// fields from the batch-level `config`. Pass `None` for a file to use the batch defaults.
    /// Batch-level settings like `max_concurrent_extractions` and `use_cache` are always
    /// taken from the batch-level `config`.
    /// </summary>
    /// <param name="items">Vector of `BatchFileItem` structs, each containing a path and optional per-file configuration overrides.</param>
    ///
    /// <param name="config">Batch-level extraction configuration (provides defaults and batch settings)</param>
    ///
    /// <returns>A vector of `ExtractionResult` in the same order as the input items.</returns>
    ///
    /// <exception cref="KreuzbergException">Individual file errors are captured in the result metadata. System errors
    /// (IO, RuntimeError equivalents) will bubble up and fail the entire batch.</exception>
    /// <param name="items"></param>
    /// <param name="config"></param>
    public static async Task<List<ExtractionResult>> BatchExtractFilesAsync(List<BatchFileItem> items, ExtractionConfig? config)
    {
        var itemsJson = JsonSerializer.Serialize(items, JsonSerializationOptions);
        var itemsHandle = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(itemsJson);
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            return await Task.Run(() =>
            {
                var nativeResult = NativeMethods.BatchExtractFiles(
                itemsHandle,
                configHandle
                );
                if (nativeResult == IntPtr.Zero)
                {
                    throw GetLastError();
                }
                var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
                NativeMethods.FreeString(nativeResult);
                var returnValue = JsonSerializer.Deserialize<List<ExtractionResult>>(json ?? "null", JsonOptions)!;
                return returnValue;
            });
        }
        finally
        {
            if (itemsHandle != global::System.IntPtr.Zero) global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(itemsHandle);
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Extract content from multiple byte arrays concurrently.
    ///
    /// This function processes multiple byte arrays in parallel, automatically managing
    /// concurrency to prevent resource exhaustion. The concurrency limit can be
    /// configured via `ExtractionConfig.max_concurrent_extractions` or defaults
    /// to `(num_cpus * 1.5).ceil()`.
    ///
    /// Each item can optionally specify a `FileExtractionConfig` that overrides specific
    /// fields from the batch-level `config`. Pass `None` as the config to use
    /// the batch-level defaults for that item.
    /// </summary>
    /// <param name="items">Vector of `BatchBytesItem` structs, each containing content bytes, MIME type, and optional per-item configuration overrides.</param>
    ///
    /// <param name="config">Batch-level extraction configuration</param>
    ///
    /// <returns>A vector of `ExtractionResult` in the same order as the input items.</returns>
    /// <param name="items"></param>
    /// <param name="config"></param>
    public static async Task<List<ExtractionResult>> BatchExtractBytesAsync(List<BatchBytesItem> items, ExtractionConfig? config)
    {
        var itemsJson = JsonSerializer.Serialize(items, JsonSerializationOptions);
        var itemsHandle = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(itemsJson);
        var configJson = JsonSerializer.Serialize((config ?? new ExtractionConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.ExtractionConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            return await Task.Run(() =>
            {
                var nativeResult = NativeMethods.BatchExtractBytes(
                itemsHandle,
                configHandle
                );
                if (nativeResult == IntPtr.Zero)
                {
                    throw GetLastError();
                }
                var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
                NativeMethods.FreeString(nativeResult);
                var returnValue = JsonSerializer.Deserialize<List<ExtractionResult>>(json ?? "null", JsonOptions)!;
                return returnValue;
            });
        }
        finally
        {
            if (itemsHandle != global::System.IntPtr.Zero) global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(itemsHandle);
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Detect MIME type from raw file bytes.
    ///
    /// Uses magic byte signatures to detect file type from content.
    /// Falls back to `infer` crate for comprehensive detection.
    ///
    /// For ZIP-based files, inspects contents to distinguish Office Open XML
    /// formats (DOCX, XLSX, PPTX) from plain ZIP archives.
    /// </summary>
    /// <param name="content">Raw file bytes</param>
    ///
    /// <returns>The detected MIME type string.</returns>
    ///
    /// <exception cref="KreuzbergException">Returns `KreuzbergError.UnsupportedFormat` if MIME type cannot be determined.</exception>
    /// <param name="content"></param>
    public static string DetectMimeTypeFromBytes(byte[] content)
    {
        ArgumentNullException.ThrowIfNull(content);
        var contentHandle = GCHandle.Alloc(content, GCHandleType.Pinned);
        try
        {
            var nativeResult = NativeMethods.DetectMimeTypeFromBytes(
            contentHandle.AddrOfPinnedObject(),
            (UIntPtr)content.Length
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var returnValue = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult) ?? string.Empty;
            NativeMethods.FreeString(nativeResult);
            return returnValue;
        }
        finally
        {
            if (contentHandle.IsAllocated) contentHandle.Free();
        }
    }

    /// <summary>
    /// Get file extensions for a given MIME type.
    ///
    /// Returns all known file extensions that map to the specified MIME type.
    /// </summary>
    /// <param name="mime_type">The MIME type to look up</param>
    ///
    /// <returns>A vector of file extensions (without leading dot) for the MIME type.</returns>
    /// <param name="mimeType"></param>
    public static List<string> GetExtensionsForMime(string mimeType)
    {
        ArgumentNullException.ThrowIfNull(mimeType);
            var nativeResult = NativeMethods.GetExtensionsForMime(
            mimeType
            );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List the names of all registered embedding backends.
    ///
    /// Used by `kreuzberg-cli`, the api/mcp endpoints, and generated language
    /// bindings.
    /// </summary>
    public static List<string> ListEmbeddingBackends()
    {
            var nativeResult = NativeMethods.ListEmbeddingBackends();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List names of all registered document extractors.
    /// </summary>
    public static List<string> ListDocumentExtractors()
    {
            var nativeResult = NativeMethods.ListDocumentExtractors();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List all registered OCR backends.
    ///
    /// Returns the names of all OCR backends currently registered in the global registry.
    /// </summary>
    /// <returns>A vector of OCR backend names.</returns>
    public static List<string> ListOcrBackends()
    {
            var nativeResult = NativeMethods.ListOcrBackends();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List all registered post-processor names.
    ///
    /// Returns a vector of all post-processor names currently registered in the
    /// global registry.
    /// </summary>
    /// <returns>- `Ok(Vec&lt;String&gt;)` - Vector of post-processor names
    /// - `Err(...)` if the registry lock is poisoned</returns>
    public static List<string> ListPostProcessors()
    {
            var nativeResult = NativeMethods.ListPostProcessors();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List names of all registered renderers.
    /// </summary>
    /// <exception cref="KreuzbergException">Returns an error if the registry lock is poisoned.</exception>
    public static List<string> ListRenderers()
    {
            var nativeResult = NativeMethods.ListRenderers();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List names of all registered validators.
    /// </summary>
    public static List<string> ListValidators()
    {
            var nativeResult = NativeMethods.ListValidators();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Compare two extraction results and return a structured diff.
    ///
    /// The comparison is purely structural — no I/O, no side effects. All fields
    /// of `ExtractionDiff` are populated according to the provided `DiffOptions`.
    /// </summary>
    /// <param name="a">— the "before" extraction result</param>
    ///
    /// <param name="b">— the "after" extraction result</param>
    ///
    /// <param name="opts">— controls which sections are compared and optional truncation</param>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <param name="opts"></param>
    public static ExtractionDiff Compare(ExtractionResult a, ExtractionResult b, DiffOptions opts)
    {
        ArgumentNullException.ThrowIfNull(a);
        ArgumentNullException.ThrowIfNull(b);
        ArgumentNullException.ThrowIfNull(opts);
        var aJson = JsonSerializer.Serialize(a, JsonSerializationOptions);
        var aHandle = NativeMethods.ExtractionResultFromJson(aJson);
        if (aHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionResultFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        var bJson = JsonSerializer.Serialize(b, JsonSerializationOptions);
        var bHandle = NativeMethods.ExtractionResultFromJson(bJson);
        if (bHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "ExtractionResultFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        var optsJson = JsonSerializer.Serialize(opts, JsonSerializationOptions);
        var optsHandle = NativeMethods.DiffOptionsFromJson(optsJson);
        if (optsHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "DiffOptionsFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            var nativeResult = NativeMethods.Compare(
            aHandle,
            bHandle,
            optsHandle
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var jsonPtr = NativeMethods.ExtractionDiffToJson(nativeResult);
            var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
            NativeMethods.FreeString(jsonPtr);
            NativeMethods.ExtractionDiffFree(nativeResult);
            var returnValue = JsonSerializer.Deserialize<ExtractionDiff>(json ?? "null", JsonOptions)!;
            return returnValue;
        }
        finally
        {
            if (aHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionResultFree(aHandle);
            if (bHandle != global::System.IntPtr.Zero) NativeMethods.ExtractionResultFree(bHandle);
            if (optsHandle != global::System.IntPtr.Zero) NativeMethods.DiffOptionsFree(optsHandle);
        }
    }

    /// <summary>
    /// Generate embeddings asynchronously for a list of text strings.
    ///
    /// This is the async counterpart to `embed_texts`. It offloads the blocking
    /// ONNX inference work to a dedicated blocking thread pool via Tokio's
    /// `spawn_blocking`, keeping the async executor free.
    ///
    /// Returns one embedding vector per input text in the same order.
    /// </summary>
    /// <param name="texts">Vec of strings to embed (owned, sent to blocking thread)</param>
    ///
    /// <param name="config">Embedding configuration specifying model, batch size, and normalization</param>
    ///
    /// <exception cref="KreuzbergException">- `KreuzbergError.MissingDependency` if ONNX Runtime is not installed
    /// - `KreuzbergError.Embedding` if the preset name is unknown, model download fails,
    ///   or the blocking inference task panics</exception>
    /// <param name="texts"></param>
    /// <param name="config"></param>
    public static async Task<List<List<float>>> EmbedTextsAsync(List<string> texts, EmbeddingConfig? config)
    {
        var textsJson = JsonSerializer.Serialize(texts, JsonSerializationOptions);
        var textsHandle = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(textsJson);
        var configJson = JsonSerializer.Serialize((config ?? new EmbeddingConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.EmbeddingConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "EmbeddingConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            return await Task.Run(() =>
            {
                var nativeResult = NativeMethods.EmbedTextsAsync(
                textsHandle,
                configHandle
                );
                if (nativeResult == IntPtr.Zero)
                {
                    throw GetLastError();
                }
                var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
                NativeMethods.FreeString(nativeResult);
                var returnValue = JsonSerializer.Deserialize<List<List<float>>>(json ?? "null", JsonOptions)!;
                return returnValue;
            });
        }
        finally
        {
            if (textsHandle != global::System.IntPtr.Zero) global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(textsHandle);
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.EmbeddingConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Render a single PDF page to PNG bytes.
    ///
    /// Returns raw PNG-encoded bytes for the specified page at the given DPI.
    /// Uses pdf_oxide with tiny-skia for pure-Rust rendering.
    /// </summary>
    /// <param name="pdf_bytes">Raw PDF file bytes</param>
    ///
    /// <param name="page_index">Zero-based page index</param>
    ///
    /// <param name="dpi">Resolution in dots per inch (default: 150)</param>
    ///
    /// <param name="password">Optional password for encrypted PDFs</param>
    ///
    /// <exception cref="KreuzbergException">Returns `KreuzbergError.Parsing` if the PDF cannot be opened, authenticated,
    /// or rendered, or if `page_index` is out of range.</exception>
    /// <param name="pdfBytes"></param>
    /// <param name="pageIndex"></param>
    /// <param name="dpi">Optional.</param>
    /// <param name="password">Optional.</param>
    public static byte[] RenderPdfPageToPng(byte[] pdfBytes, ulong pageIndex, int? dpi, string? password)
    {
        ArgumentNullException.ThrowIfNull(pdfBytes);
        var pdfBytesHandle = GCHandle.Alloc(pdfBytes, GCHandleType.Pinned);
        var rc = NativeMethods.RenderPdfPageToPng(
            pdfBytesHandle.AddrOfPinnedObject(),
            (UIntPtr)pdfBytes.Length,
            pageIndex,
            dpi ?? int.MaxValue,
            password!,
            out var outPtr,
            out var outLen,
            out var outCap
        );
        try
        {
            if (rc != 0)
            {
                throw GetLastError();
            }
            var result = new byte[(int)outLen];
            global::System.Runtime.InteropServices.Marshal.Copy(outPtr, result, 0, (int)outLen);
            NativeMethods.FreeBytes(outPtr, outLen, outCap);
            return result;
        }
        finally
        {
            if (pdfBytesHandle.IsAllocated) pdfBytesHandle.Free();
        }
    }

    /// <summary>
    /// Detect the MIME type of a file at the given path.
    ///
    /// Uses the file extension and optionally the file content to determine the MIME type.
    /// Set `check_exists` to `true` to verify the file exists before detection.
    /// </summary>
    /// <param name="path"></param>
    /// <param name="checkExists"></param>
    public static string DetectMimeType(string path, bool checkExists)
    {
        ArgumentNullException.ThrowIfNull(path);
            var nativeResult = NativeMethods.DetectMimeType(
            path,
            checkExists
            );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var returnValue = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult) ?? string.Empty;
        NativeMethods.FreeString(nativeResult);
        return returnValue;
    }

    /// <summary>
    /// Embed a list of texts using the configured embedding model.
    ///
    /// Returns a 2D vector where each inner vector is the embedding for the corresponding text.
    /// </summary>
    /// <param name="texts"></param>
    /// <param name="config"></param>
    public static List<List<float>> EmbedTexts(List<string> texts, EmbeddingConfig? config)
    {
        var textsJson = JsonSerializer.Serialize(texts, JsonSerializationOptions);
        var textsHandle = global::System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(textsJson);
        var configJson = JsonSerializer.Serialize((config ?? new EmbeddingConfig()), JsonSerializationOptions);
        var configHandle = NativeMethods.EmbeddingConfigFromJson(configJson);
        if (configHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "EmbeddingConfigFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        try
        {
            var nativeResult = NativeMethods.EmbedTexts(
            textsHandle,
            configHandle
            );
            if (nativeResult == IntPtr.Zero)
            {
                throw GetLastError();
            }
            var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
            NativeMethods.FreeString(nativeResult);
            var returnValue = JsonSerializer.Deserialize<List<List<float>>>(json ?? "null", JsonOptions)!;
            return returnValue;
        }
        finally
        {
            if (textsHandle != global::System.IntPtr.Zero) global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(textsHandle);
            if (configHandle != global::System.IntPtr.Zero) NativeMethods.EmbeddingConfigFree(configHandle);
        }
    }

    /// <summary>
    /// Get an embedding preset by name.
    ///
    /// Returns `None` if no preset with the given name exists. Returns an owned
    /// clone so the value is safe to pass across FFI boundaries.
    /// </summary>
    /// <param name="name"></param>
    public static EmbeddingPreset? GetEmbeddingPreset(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
            var nativeResult = NativeMethods.GetEmbeddingPreset(
            name
            );
        if (nativeResult == IntPtr.Zero)
        {
            return null;
        }
        var jsonPtr = NativeMethods.EmbeddingPresetToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.EmbeddingPresetFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<EmbeddingPreset?>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// List the names of all available embedding presets.
    ///
    /// Returns owned `String`s so the values are safe to pass across FFI boundaries.
    /// </summary>
    public static List<string> ListEmbeddingPresets()
    {
            var nativeResult = NativeMethods.ListEmbeddingPresets();
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult);
        NativeMethods.FreeString(nativeResult);
        var returnValue = JsonSerializer.Deserialize<List<string>>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Check if image processing is needed by examining OCR and image extraction settings.
    /// Returns true if either OCR is enabled or image extraction is configured,
    /// indicating that image decompression and processing should occur.
    /// Returns false if both are disabled, allowing optimization to skip unnecessary
    /// image decompression for text-only extraction workflows.
    /// # Optimization Impact
    /// For text-only extractions (no OCR, no image extraction), skipping image
    /// decompression can improve CPU utilization by 5-10% by avoiding wasteful
    /// image I/O and processing when results won't be used.
    /// </summary>
    public static bool ExtractionConfigNeedsImageProcessing(IntPtr handle)
    {
        var nativeResult = NativeMethods.ExtractionConfigNeedsImageProcessing(
            handle
        );
        var returnValue = nativeResult != 0;
        return returnValue;
    }

    /// <summary>
    /// Get the server listen address (host:port).
    /// </summary>
    public static string ServerConfigListenAddr(IntPtr handle)
    {
        var nativeResult = NativeMethods.ServerConfigListenAddr(
            handle
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var returnValue = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(nativeResult) ?? string.Empty;
        NativeMethods.FreeString(nativeResult);
        return returnValue;
    }

    /// <summary>
    /// Check if CORS allows all origins.
    /// Returns true if the cors_origins vector is empty, meaning all origins
    /// are allowed. Returns false if specific origins are configured.
    /// </summary>
    public static bool ServerConfigCorsAllowsAll(IntPtr handle)
    {
        var nativeResult = NativeMethods.ServerConfigCorsAllowsAll(
            handle
        );
        var returnValue = nativeResult != 0;
        return returnValue;
    }

    /// <summary>
    /// Check if a given origin is allowed by CORS configuration.
    /// Returns true if:
    /// - CORS allows all origins (empty origins list), or
    /// - The given origin is in the allowed origins list
    /// </summary>
    /// <param name="origin">The origin to check (e.g., "https://example.com")</param>
    /// <param name="origin"></param>
    public static bool ServerConfigIsOriginAllowed(IntPtr handle, string origin)
    {
        ArgumentNullException.ThrowIfNull(origin);
        var nativeResult = NativeMethods.ServerConfigIsOriginAllowed(
            handle,
            origin
        );
        var returnValue = nativeResult != 0;
        return returnValue;
    }

    /// <summary>
    /// Get maximum request body size in megabytes (rounded up).
    /// </summary>
    public static ulong ServerConfigMaxRequestBodyMb(IntPtr handle)
    {
        var nativeResult = NativeMethods.ServerConfigMaxRequestBodyMb(
            handle
        );
        var returnValue = nativeResult;
        return returnValue;
    }

    /// <summary>
    /// Get maximum multipart field size in megabytes (rounded up).
    /// </summary>
    public static ulong ServerConfigMaxMultipartFieldMb(IntPtr handle)
    {
        var nativeResult = NativeMethods.ServerConfigMaxMultipartFieldMb(
            handle
        );
        var returnValue = nativeResult;
        return returnValue;
    }

    /// <summary>
    /// Compute and populate the node_types field from the current nodes.
    /// Call this after all nodes have been added to the structure. Internal
    /// construction paths (builder, derivation) call this automatically.
    /// </summary>
    public static void DocumentStructureFinalizeNodeTypes(IntPtr handle)
    {
        NativeMethods.DocumentStructureFinalizeNodeTypes(
            handle
        );
    }

    /// <summary>
    /// Check if the document structure is empty.
    /// </summary>
    public static bool DocumentStructureIsEmpty(IntPtr handle)
    {
        var nativeResult = NativeMethods.DocumentStructureIsEmpty(
            handle
        );
        var returnValue = nativeResult != 0;
        return returnValue;
    }

    /// <summary>
    /// Convert from an OCR result.
    /// </summary>
    /// <param name="ocr"></param>
    public static ExtractionResult ExtractionResultFromOcr(OcrExtractionResult ocr)
    {
        ArgumentNullException.ThrowIfNull(ocr);
        var ocrJson = JsonSerializer.Serialize(ocr, JsonSerializationOptions);
        var ocrHandle = NativeMethods.OcrExtractionResultFromJson(ocrJson);
        if (ocrHandle == IntPtr.Zero)
        {
            var ec = NativeMethods.LastErrorCode();
            var ctxPtr = NativeMethods.LastErrorContext();
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "OcrExtractionResultFromJson failed";
            throw new KreuzbergException(ec, msg);
        }
        var nativeResult = NativeMethods.ExtractionResultFromOcr(
            ocrHandle
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.ExtractionResultToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.ExtractionResultFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<ExtractionResult>(json ?? "null", JsonOptions)!;
        if (ocrHandle != global::System.IntPtr.Zero) NativeMethods.OcrExtractionResultFree(ocrHandle);
        return returnValue;
    }

    /// <summary>
    /// Returns true when no metadata fields, format-specific metadata, or
    /// additional postprocessor fields are populated.
    /// </summary>
    public static bool MetadataIsEmpty(IntPtr handle)
    {
        var nativeResult = NativeMethods.MetadataIsEmpty(
            handle
        );
        var returnValue = nativeResult != 0;
        return returnValue;
    }

    /// <summary>
    /// Sets a custom cache directory for model files.
    /// </summary>
    /// <param name="path">Path to cache directory</param>
    /// <param name="path"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithCacheDir(IntPtr handle, string path)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithCacheDir(
            handle,
            path
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Enables or disables table structure detection.
    /// </summary>
    /// <param name="enable">Whether to enable table detection</param>
    /// <param name="enable"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithTableDetection(IntPtr handle, bool enable)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithTableDetection(
            handle,
            enable
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Enables or disables angle classification for rotated text.
    /// </summary>
    /// <param name="enable">Whether to enable angle classification</param>
    /// <param name="enable"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithAngleCls(IntPtr handle, bool enable)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithAngleCls(
            handle,
            enable
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the database threshold for text detection.
    /// </summary>
    /// <param name="threshold">Detection threshold (0.0-1.0)</param>
    /// <param name="threshold"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithDetDbThresh(IntPtr handle, float threshold)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithDetDbThresh(
            handle,
            threshold
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the box threshold for text bounding box refinement.
    /// </summary>
    /// <param name="threshold">Box threshold (0.0-1.0)</param>
    /// <param name="threshold"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithDetDbBoxThresh(IntPtr handle, float threshold)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithDetDbBoxThresh(
            handle,
            threshold
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the unclip ratio for expanding text bounding boxes.
    /// </summary>
    /// <param name="ratio">Unclip ratio (typically 1.5-2.0)</param>
    /// <param name="ratio"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithDetDbUnclipRatio(IntPtr handle, float ratio)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithDetDbUnclipRatio(
            handle,
            ratio
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the maximum side length for detection images.
    /// </summary>
    /// <param name="length">Maximum side length in pixels</param>
    /// <param name="length"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithDetLimitSideLen(IntPtr handle, uint length)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithDetLimitSideLen(
            handle,
            length
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the batch size for recognition inference.
    /// </summary>
    /// <param name="batch_size">Number of text regions to process simultaneously</param>
    /// <param name="batchSize"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithRecBatchNum(IntPtr handle, uint batchSize)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithRecBatchNum(
            handle,
            batchSize
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the minimum recognition confidence threshold.
    /// </summary>
    /// <param name="score">Minimum confidence (0.0-1.0), text below this is dropped</param>
    /// <param name="score"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithDropScore(IntPtr handle, float score)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithDropScore(
            handle,
            score
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets padding in pixels added around images before detection.
    /// </summary>
    /// <param name="padding">Padding in pixels (0-100)</param>
    /// <param name="padding"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithPadding(IntPtr handle, uint padding)
    {
        var nativeResult = NativeMethods.PaddleOcrConfigWithPadding(
            handle,
            padding
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>
    /// Sets the model tier controlling detection/recognition model size.
    /// </summary>
    /// <param name="tier">"mobile" (default, lightweight, faster) or "server" (high accuracy, GPU/complex documents)</param>
    /// <param name="tier"></param>
    public static PaddleOcrConfig PaddleOcrConfigWithModelTier(IntPtr handle, string tier)
    {
        ArgumentNullException.ThrowIfNull(tier);
        var nativeResult = NativeMethods.PaddleOcrConfigWithModelTier(
            handle,
            tier
        );
        if (nativeResult == IntPtr.Zero)
        {
            throw GetLastError();
        }
        var jsonPtr = NativeMethods.PaddleOcrConfigToJson(nativeResult);
        var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(jsonPtr);
        NativeMethods.FreeString(jsonPtr);
        NativeMethods.PaddleOcrConfigFree(nativeResult);
        var returnValue = JsonSerializer.Deserialize<PaddleOcrConfig>(json ?? "null", JsonOptions)!;
        return returnValue;
    }

    /// <summary>Complete native registration of a OcrBackend implementation</summary>
    public static void RegisterOcrBackend(IntPtr handle)
    {
        if (handle == IntPtr.Zero) throw new ArgumentException("handle is null");
        var bridge = GCHandle.FromIntPtr(handle).Target as OcrBackendBridge ?? throw new InvalidOperationException("Invalid bridge handle");
        var impl = bridge.GetType().GetField("_impl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(bridge) as IOcrBackend;
        if (impl == null) throw new InvalidOperationException("Cannot extract impl from bridge");
        var name = impl.Name;
        var ec = NativeMethods.RegisterOcrBackend(name, bridge._vtable, handle, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Register failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Unregister a OcrBackend implementation by name</summary>
    public static void UnregisterOcrBackend(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        var ec = NativeMethods.UnregisterOcrBackend(name, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unregister failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Complete native registration of a PostProcessor implementation</summary>
    public static void RegisterPostProcessor(IntPtr handle)
    {
        if (handle == IntPtr.Zero) throw new ArgumentException("handle is null");
        var bridge = GCHandle.FromIntPtr(handle).Target as PostProcessorBridge ?? throw new InvalidOperationException("Invalid bridge handle");
        var impl = bridge.GetType().GetField("_impl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(bridge) as IPostProcessor;
        if (impl == null) throw new InvalidOperationException("Cannot extract impl from bridge");
        var name = impl.Name;
        var ec = NativeMethods.RegisterPostProcessor(name, bridge._vtable, handle, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Register failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Unregister a PostProcessor implementation by name</summary>
    public static void UnregisterPostProcessor(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        var ec = NativeMethods.UnregisterPostProcessor(name, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unregister failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Complete native registration of a Validator implementation</summary>
    public static void RegisterValidator(IntPtr handle)
    {
        if (handle == IntPtr.Zero) throw new ArgumentException("handle is null");
        var bridge = GCHandle.FromIntPtr(handle).Target as ValidatorBridge ?? throw new InvalidOperationException("Invalid bridge handle");
        var impl = bridge.GetType().GetField("_impl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(bridge) as IValidator;
        if (impl == null) throw new InvalidOperationException("Cannot extract impl from bridge");
        var name = impl.Name;
        var ec = NativeMethods.RegisterValidator(name, bridge._vtable, handle, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Register failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Unregister a Validator implementation by name</summary>
    public static void UnregisterValidator(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        var ec = NativeMethods.UnregisterValidator(name, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unregister failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Complete native registration of a EmbeddingBackend implementation</summary>
    public static void RegisterEmbeddingBackend(IntPtr handle)
    {
        if (handle == IntPtr.Zero) throw new ArgumentException("handle is null");
        var bridge = GCHandle.FromIntPtr(handle).Target as EmbeddingBackendBridge ?? throw new InvalidOperationException("Invalid bridge handle");
        var impl = bridge.GetType().GetField("_impl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(bridge) as IEmbeddingBackend;
        if (impl == null) throw new InvalidOperationException("Cannot extract impl from bridge");
        var name = impl.Name;
        var ec = NativeMethods.RegisterEmbeddingBackend(name, bridge._vtable, handle, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Register failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Unregister a EmbeddingBackend implementation by name</summary>
    public static void UnregisterEmbeddingBackend(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        var ec = NativeMethods.UnregisterEmbeddingBackend(name, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unregister failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Complete native registration of a DocumentExtractor implementation</summary>
    public static void RegisterDocumentExtractor(IntPtr handle)
    {
        if (handle == IntPtr.Zero) throw new ArgumentException("handle is null");
        var bridge = GCHandle.FromIntPtr(handle).Target as DocumentExtractorBridge ?? throw new InvalidOperationException("Invalid bridge handle");
        var impl = bridge.GetType().GetField("_impl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(bridge) as IDocumentExtractor;
        if (impl == null) throw new InvalidOperationException("Cannot extract impl from bridge");
        var name = impl.Name;
        var ec = NativeMethods.RegisterDocumentExtractor(name, bridge._vtable, handle, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Register failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Unregister a DocumentExtractor implementation by name</summary>
    public static void UnregisterDocumentExtractor(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        var ec = NativeMethods.UnregisterDocumentExtractor(name, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unregister failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Complete native registration of a Renderer implementation</summary>
    public static void RegisterRenderer(IntPtr handle)
    {
        if (handle == IntPtr.Zero) throw new ArgumentException("handle is null");
        var bridge = GCHandle.FromIntPtr(handle).Target as RendererBridge ?? throw new InvalidOperationException("Invalid bridge handle");
        var impl = bridge.GetType().GetField("_impl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(bridge) as IRenderer;
        if (impl == null) throw new InvalidOperationException("Cannot extract impl from bridge");
        var name = impl.Name;
        var ec = NativeMethods.RegisterRenderer(name, bridge._vtable, handle, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Register failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Unregister a Renderer implementation by name</summary>
    public static void UnregisterRenderer(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        var ec = NativeMethods.UnregisterRenderer(name, out var outError);
        if (ec != 0) {
            var msg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unregister failed";
            throw new KreuzbergException(ec, msg);
        }
    }

    /// <summary>Clear all registered OcrBackend implementations</summary>
    public static void ClearOcrBackends()
    {
        OcrBackendRegistry.Clear();
    }

    /// <summary>Clear all registered PostProcessor implementations</summary>
    public static void ClearPostProcessors()
    {
        PostProcessorRegistry.Clear();
    }

    /// <summary>Clear all registered Validator implementations</summary>
    public static void ClearValidators()
    {
        ValidatorRegistry.Clear();
    }

    /// <summary>Clear all registered EmbeddingBackend implementations</summary>
    public static void ClearEmbeddingBackends()
    {
        EmbeddingBackendRegistry.Clear();
    }

    /// <summary>Clear all registered DocumentExtractor implementations</summary>
    public static void ClearDocumentExtractors()
    {
        DocumentExtractorRegistry.Clear();
    }

    /// <summary>Clear all registered Renderer implementations</summary>
    public static void ClearRenderers()
    {
        RendererRegistry.Clear();
    }

    private static Exception GetLastError()
    {
        var code = NativeMethods.LastErrorCode();
        var ctxPtr = NativeMethods.LastErrorContext();
        var message = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ctxPtr) ?? "Unknown error";        if (message.StartsWith("Extraction timed out after")) return new TimeoutException(message);        if (message.StartsWith("Image processing error:")) return new ImageProcessingException(message);        if (message.StartsWith("Serialization error:")) return new SerializationException(message);        if (message.StartsWith("Extraction cancelled")) return new CancelledException(message);        if (message.StartsWith("Missing dependency:")) return new MissingDependencyException(message);        if (message.StartsWith("Unsupported format:")) return new UnsupportedFormatException(message);        if (message.StartsWith("Security violation:")) return new SecurityException(message);        if (message.StartsWith("Validation error:")) return new ValidationException(message);        if (message.StartsWith("Plugin error in '")) return new PluginException(message);        if (message.StartsWith("Embedding error:")) return new EmbeddingException(message);        if (message.StartsWith("Parsing error:")) return new ParsingException(message);        if (message.StartsWith("Lock poisoned:")) return new LockPoisonedException(message);        if (message.StartsWith("Cache error:")) return new CacheException(message);        if (message.StartsWith("OCR error:")) return new OcrException(message);        if (message.StartsWith("IO error:")) return new IoException(message);        if (code == 2) return new KreuzbergErrorException(message);        return new KreuzbergException(code, message);
    }
}
