// Generated by alef. Do not edit by hand.
@file:Suppress(
    "ktlint:standard:trailing-comma-on-call-site",
    "ktlint:standard:trailing-comma-on-declaration-site",
    "ktlint:standard:spacing-between-declarations-with-comments",
    "ktlint:standard:spacing-between-declarations-with-annotations",
    "ktlint:standard:when-entry-bracing",
    "ktlint:standard:blank-line-between-when-conditions",
    "ktlint:standard:blank-line-before-declaration",
    "ktlint:standard:chain-method-continuation",
    "ktlint:standard:annotation",
    "ktlint:standard:max-line-length",
    "ktlint:standard:no-semi",
    "ktlint:standard:statement-wrapping",
    "MaxLineLength",
    "TooManyFunctions",
    "FunctionParameterNaming",
    "LongParameterList",
    "CyclomaticComplexMethod",
    "LongMethod",
)

package dev.kreuzberg

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

object Kreuzberg {
    /// Jackson module that marshals ByteArray as a JSON array of unsigned bytes,
    /// matching how Rust serde encodes Vec<u8> on the wire.
    /// Jackson's default writes ByteArray as a Base64 string, which Rust serde rejects
    /// with "invalid type: string, expected a sequence".
    private val byteArrayModule = com.fasterxml.jackson.databind.module.SimpleModule().apply {
        addSerializer(
            ByteArray::class.java,
            object : com.fasterxml.jackson.databind.ser.std.StdSerializer<ByteArray>(ByteArray::class.java) {
                override fun serialize(
                    value: ByteArray,
                    gen: com.fasterxml.jackson.core.JsonGenerator,
                    provider: com.fasterxml.jackson.databind.SerializerProvider,
                ) {
                    gen.writeStartArray()
                    for (b in value) gen.writeNumber(b.toInt() and 0xff)
                    gen.writeEndArray()
                }
            },
        )
        addDeserializer(
            ByteArray::class.java,
            object : com.fasterxml.jackson.databind.deser.std.StdDeserializer<ByteArray>(ByteArray::class.java) {
                override fun deserialize(
                    parser: com.fasterxml.jackson.core.JsonParser,
                    ctx: com.fasterxml.jackson.databind.DeserializationContext,
                ): ByteArray {
                    val node = parser.codec.readTree<com.fasterxml.jackson.databind.JsonNode>(parser)
                    return when {
                        node.isArray -> ByteArray(node.size()) { i -> node.get(i).asInt().toByte() }
                        node.isTextual -> java.util.Base64.getDecoder().decode(node.asText())
                        else -> ByteArray(0)
                    }
                }
            },
        )
    }

    private val mapper = jacksonObjectMapper()
        .registerModule(com.fasterxml.jackson.datatype.jdk8.Jdk8Module())
        .registerModule(byteArrayModule)
        .registerModule(
            com.fasterxml.jackson.module.kotlin.KotlinModule.Builder()
                .configure(com.fasterxml.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault, true)
                .configure(com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyCollection, true)
                .configure(com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyMap, true)
                .build(),
        )
        .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
        .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY)
        .configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

    /**
     * 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
     *
     * **Returns:**
     *
     * An `ExtractionResult` containing the extracted content and metadata.
     *
     * **Errors:**
     *
     * Returns `KreuzbergError.Validation` if MIME type is invalid.
     * Returns `KreuzbergError.UnsupportedFormat` if MIME type is not supported.
     */
    fun extractBytes(content: ByteArray, mimeType: String, config: ExtractionConfig): ExtractionResult {
        val resultJson = KreuzbergBridge.nativeExtractBytes(content, mimeType, mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, ExtractionResult::class.java)
    }

    /**
     * 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
     *
     * **Returns:**
     *
     * An `ExtractionResult` containing the extracted content and metadata.
     *
     * **Errors:**
     *
     * Returns `KreuzbergError.Validation` if MIME type is invalid.
     * Returns `KreuzbergError.UnsupportedFormat` if MIME type is not supported.
     */
    suspend fun extractBytesAsync(content: ByteArray, mimeType: String, config: ExtractionConfig): ExtractionResult =
        withContext(Dispatchers.IO) { extractBytes(content, mimeType, config) }

    /**
     * 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)
     *
     * **Returns:**
     *
     * An `ExtractionResult` containing the extracted content and metadata.
     *
     * **Errors:**
     *
     * 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.
     */
    fun extractFile(path: String, mimeType: String? = null, config: ExtractionConfig): ExtractionResult {
        val resultJson = KreuzbergBridge.nativeExtractFile(path, mimeType ?: "", mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, ExtractionResult::class.java)
    }

    /**
     * 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)
     *
     * **Returns:**
     *
     * An `ExtractionResult` containing the extracted content and metadata.
     *
     * **Errors:**
     *
     * 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.
     */
    suspend fun extractFileAsync(path: String, mimeType: String? = null, config: ExtractionConfig): ExtractionResult =
        withContext(Dispatchers.IO) { extractFile(path, mimeType, config) }

    /**
     * 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.
     */
    fun extractFileSync(path: String, mimeType: String? = null, config: ExtractionConfig): ExtractionResult {
        val resultJson = KreuzbergBridge.nativeExtractFileSync(path, mimeType ?: "", mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, ExtractionResult::class.java)
    }

    /**
     * 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.
     */
    suspend fun extractFileSyncAsync(path: String, mimeType: String? = null, config: ExtractionConfig): ExtractionResult =
        withContext(Dispatchers.IO) { extractFileSync(path, mimeType, config) }

    /**
     * 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.
     */
    fun extractBytesSync(content: ByteArray, mimeType: String, config: ExtractionConfig): ExtractionResult {
        val resultJson = KreuzbergBridge.nativeExtractBytesSync(content, mimeType, mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, ExtractionResult::class.java)
    }

    /**
     * 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.
     */
    suspend fun extractBytesSyncAsync(content: ByteArray, mimeType: String, config: ExtractionConfig): ExtractionResult =
        withContext(Dispatchers.IO) { extractBytesSync(content, mimeType, config) }

    /**
     * Synchronous wrapper for `batch_extract_files`.
     *
     * Uses the global Tokio runtime for optimal performance.
     * Only available with `tokio-runtime` (WASM has no filesystem).
     */
    fun batchExtractFilesSync(items: List<BatchFileItem>, config: ExtractionConfig): List<ExtractionResult> {
        val resultJson = KreuzbergBridge.nativeBatchExtractFilesSync(mapper.writeValueAsString(items), mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, object : TypeReference<List<ExtractionResult>>() {})
    }

    /**
     * Synchronous wrapper for `batch_extract_files`.
     *
     * Uses the global Tokio runtime for optimal performance.
     * Only available with `tokio-runtime` (WASM has no filesystem).
     */
    suspend fun batchExtractFilesSyncAsync(items: List<BatchFileItem>, config: ExtractionConfig): List<ExtractionResult> =
        withContext(Dispatchers.IO) { batchExtractFilesSync(items, config) }

    /**
     * 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()`.
     */
    fun batchExtractBytesSync(items: List<BatchBytesItem>, config: ExtractionConfig): List<ExtractionResult> {
        val resultJson = KreuzbergBridge.nativeBatchExtractBytesSync(mapper.writeValueAsString(items), mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, object : TypeReference<List<ExtractionResult>>() {})
    }

    /**
     * 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()`.
     */
    suspend fun batchExtractBytesSyncAsync(items: List<BatchBytesItem>, config: ExtractionConfig): List<ExtractionResult> =
        withContext(Dispatchers.IO) { batchExtractBytesSync(items, config) }

    /**
     * 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 `null` 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`.
     *
     *   per-file configuration overrides.
     *
     * - `config` - Batch-level extraction configuration (provides defaults and batch settings)
     *
     * **Returns:**
     *
     * A vector of `ExtractionResult` in the same order as the input items.
     *
     * **Errors:**
     *
     * Individual file errors are captured in the result metadata. System errors
     * (IO, RuntimeError equivalents) will bubble up and fail the entire batch.
     *
     * Simple usage with no per-file overrides:
     *
     *
     * Per-file configuration overrides:
     */
    fun batchExtractFiles(items: List<BatchFileItem>, config: ExtractionConfig): List<ExtractionResult> {
        val resultJson = KreuzbergBridge.nativeBatchExtractFiles(mapper.writeValueAsString(items), mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, object : TypeReference<List<ExtractionResult>>() {})
    }

    /**
     * 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 `null` 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`.
     *
     *   per-file configuration overrides.
     *
     * - `config` - Batch-level extraction configuration (provides defaults and batch settings)
     *
     * **Returns:**
     *
     * A vector of `ExtractionResult` in the same order as the input items.
     *
     * **Errors:**
     *
     * Individual file errors are captured in the result metadata. System errors
     * (IO, RuntimeError equivalents) will bubble up and fail the entire batch.
     *
     * Simple usage with no per-file overrides:
     *
     *
     * Per-file configuration overrides:
     */
    suspend fun batchExtractFilesAsync(items: List<BatchFileItem>, config: ExtractionConfig): List<ExtractionResult> =
        withContext(Dispatchers.IO) { batchExtractFiles(items, config) }

    /**
     * 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 `null` as the config to use
     * the batch-level defaults for that item.
     *
     *   MIME type, and optional per-item configuration overrides.
     *
     * - `config` - Batch-level extraction configuration
     *
     * **Returns:**
     *
     * A vector of `ExtractionResult` in the same order as the input items.
     *
     * Simple usage with no per-item overrides:
     *
     *
     * Per-item configuration overrides:
     */
    fun batchExtractBytes(items: List<BatchBytesItem>, config: ExtractionConfig): List<ExtractionResult> {
        val resultJson = KreuzbergBridge.nativeBatchExtractBytes(mapper.writeValueAsString(items), mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, object : TypeReference<List<ExtractionResult>>() {})
    }

    /**
     * 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 `null` as the config to use
     * the batch-level defaults for that item.
     *
     *   MIME type, and optional per-item configuration overrides.
     *
     * - `config` - Batch-level extraction configuration
     *
     * **Returns:**
     *
     * A vector of `ExtractionResult` in the same order as the input items.
     *
     * Simple usage with no per-item overrides:
     *
     *
     * Per-item configuration overrides:
     */
    suspend fun batchExtractBytesAsync(items: List<BatchBytesItem>, config: ExtractionConfig): List<ExtractionResult> =
        withContext(Dispatchers.IO) { batchExtractBytes(items, config) }

    /**
     * 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.
     *
     * **Returns:**
     *
     * The detected MIME type string.
     *
     * **Errors:**
     *
     * Returns `KreuzbergError.UnsupportedFormat` if MIME type cannot be determined.
     */
    fun detectMimeTypeFromBytes(content: ByteArray): String = KreuzbergBridge.nativeDetectMimeTypeFromBytes(content)
    /**
     * Get file extensions for a given MIME type.
     *
     * Returns all known file extensions that map to the specified MIME type.
     *
     * **Returns:**
     *
     * A vector of file extensions (without leading dot) for the MIME type.
     */
    fun getExtensionsForMime(mimeType: String): List<String> {
        val resultJson = KreuzbergBridge.nativeGetExtensionsForMime(mimeType)
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /**
     * Get file extensions for a given MIME type.
     *
     * Returns all known file extensions that map to the specified MIME type.
     *
     * **Returns:**
     *
     * A vector of file extensions (without leading dot) for the MIME type.
     */
    suspend fun getExtensionsForMimeAsync(mimeType: String): List<String> =
        withContext(Dispatchers.IO) { getExtensionsForMime(mimeType) }

    /**
     * Clear all embedding backends from the global registry.
     *
     * Calls `shutdown()` on every registered backend, then empties the registry.
     *
     * **Errors:**
     *
     * - Any error returned by a backend's `shutdown()` method. The first error
     *   encountered stops processing of remaining backends.
     */
    fun clearEmbeddingBackends(): Unit = KreuzbergBridge.nativeClearEmbeddingBackends()
    /**
     * List the names of all registered embedding backends.
     *
     * Used by `kreuzberg-cli`, the api/mcp endpoints, and generated language
     * bindings.
     */
    fun listEmbeddingBackends(): List<String> {
        val resultJson = KreuzbergBridge.nativeListEmbeddingBackends()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /**
     * List the names of all registered embedding backends.
     *
     * Used by `kreuzberg-cli`, the api/mcp endpoints, and generated language
     * bindings.
     */
    suspend fun listEmbeddingBackendsAsync(): List<String> =
        withContext(Dispatchers.IO) { listEmbeddingBackends() }

    /** List names of all registered document extractors. */
    fun listDocumentExtractors(): List<String> {
        val resultJson = KreuzbergBridge.nativeListDocumentExtractors()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /** List names of all registered document extractors. */
    suspend fun listDocumentExtractorsAsync(): List<String> =
        withContext(Dispatchers.IO) { listDocumentExtractors() }

    /**
     * Clear all document extractors from the global registry.
     *
     * Calls `shutdown()` on every registered extractor, then empties the registry.
     *
     * **Errors:**
     *
     * - Any error returned by an extractor's `shutdown()` method. The first error
     *   encountered stops processing of remaining extractors.
     */
    fun clearDocumentExtractors(): Unit = KreuzbergBridge.nativeClearDocumentExtractors()
    /**
     * List all registered OCR backends.
     *
     * Returns the names of all OCR backends currently registered in the global registry.
     *
     * **Returns:**
     *
     * A vector of OCR backend names.
     */
    fun listOcrBackends(): List<String> {
        val resultJson = KreuzbergBridge.nativeListOcrBackends()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /**
     * List all registered OCR backends.
     *
     * Returns the names of all OCR backends currently registered in the global registry.
     *
     * **Returns:**
     *
     * A vector of OCR backend names.
     */
    suspend fun listOcrBackendsAsync(): List<String> =
        withContext(Dispatchers.IO) { listOcrBackends() }

    /**
     * Clear all OCR backends from the global registry.
     *
     * Removes all OCR backends and calls their `shutdown()` methods.
     *
     * **Returns:**
     *
     * - `Ok(())` if all backends were cleared successfully
     * - `Err(...)` if any shutdown method failed
     */
    fun clearOcrBackends(): Unit = KreuzbergBridge.nativeClearOcrBackends()
    /**
     * List all registered post-processor names.
     *
     * Returns a vector of all post-processor names currently registered in the
     * global registry.
     *
     * **Returns:**
     *
     * - `Ok(Vec<String>)` - Vector of post-processor names
     * - `Err(...)` if the registry lock is poisoned
     */
    fun listPostProcessors(): List<String> {
        val resultJson = KreuzbergBridge.nativeListPostProcessors()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /**
     * List all registered post-processor names.
     *
     * Returns a vector of all post-processor names currently registered in the
     * global registry.
     *
     * **Returns:**
     *
     * - `Ok(Vec<String>)` - Vector of post-processor names
     * - `Err(...)` if the registry lock is poisoned
     */
    suspend fun listPostProcessorsAsync(): List<String> =
        withContext(Dispatchers.IO) { listPostProcessors() }

    /** Remove all registered post-processors. */
    fun clearPostProcessors(): Unit = KreuzbergBridge.nativeClearPostProcessors()
    /**
     * List names of all registered renderers.
     *
     * **Errors:**
     *
     * Returns an error if the registry lock is poisoned.
     */
    fun listRenderers(): List<String> {
        val resultJson = KreuzbergBridge.nativeListRenderers()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /**
     * List names of all registered renderers.
     *
     * **Errors:**
     *
     * Returns an error if the registry lock is poisoned.
     */
    suspend fun listRenderersAsync(): List<String> =
        withContext(Dispatchers.IO) { listRenderers() }

    /**
     * Clear all renderers from the global registry.
     *
     * Removes every renderer, including the built-in defaults (markdown, html,
     * djot, plain). After calling this no renderers are registered; re-register
     * as needed.
     *
     * **Errors:**
     *
     * Returns an error if the registry lock is poisoned.
     */
    fun clearRenderers(): Unit = KreuzbergBridge.nativeClearRenderers()
    /** List names of all registered validators. */
    fun listValidators(): List<String> {
        val resultJson = KreuzbergBridge.nativeListValidators()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /** List names of all registered validators. */
    suspend fun listValidatorsAsync(): List<String> =
        withContext(Dispatchers.IO) { listValidators() }

    /** Remove all registered validators. */
    fun clearValidators(): Unit = KreuzbergBridge.nativeClearValidators()
    /**
     * 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`.
     */
    fun compare(a: ExtractionResult, b: ExtractionResult, opts: DiffOptions): ExtractionDiff {
        val resultJson = KreuzbergBridge.nativeCompare(mapper.writeValueAsString(a), mapper.writeValueAsString(b), mapper.writeValueAsString(opts))
        return mapper.readValue(resultJson, ExtractionDiff::class.java)
    }

    /**
     * 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`.
     */
    suspend fun compareAsync(a: ExtractionResult, b: ExtractionResult, opts: DiffOptions): ExtractionDiff =
        withContext(Dispatchers.IO) { compare(a, b, opts) }

    /**
     * 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.
     *
     * **Errors:**
     *
     * Returns `KreuzbergError.Parsing` if the PDF cannot be opened, authenticated,
     * or rendered, or if `page_index` is out of range.
     */
    fun renderPdfPageToPng(pdfBytes: ByteArray, pageIndex: Long, dpi: Int? = null, password: String? = null): ByteArray = KreuzbergBridge.nativeRenderPdfPageToPng(pdfBytes, pageIndex, dpi ?: 0, password ?: "")
    /**
     * 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.
     */
    fun detectMimeType(path: String, checkExists: Boolean): String = KreuzbergBridge.nativeDetectMimeType(path, checkExists)
    /**
     * 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.
     */
    fun embedTexts(texts: List<String>, config: EmbeddingConfig): List<List<Float>> {
        val resultJson = KreuzbergBridge.nativeEmbedTexts(mapper.writeValueAsString(texts), mapper.writeValueAsString(config))
        return mapper.readValue(resultJson, object : TypeReference<List<List<Float>>>() {})
    }

    /**
     * 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.
     */
    suspend fun embedTextsAsync(texts: List<String>, config: EmbeddingConfig): List<List<Float>> =
        withContext(Dispatchers.IO) { embedTexts(texts, config) }

    /**
     * Get an embedding preset by name.
     *
     * Returns `null` if no preset with the given name exists. Returns an owned
     * clone so the value is safe to pass across FFI boundaries.
     */
    fun getEmbeddingPreset(name: String): String? = KreuzbergBridge.nativeGetEmbeddingPreset(name)
    /**
     * List the names of all available embedding presets.
     *
     * Returns owned `String`s so the values are safe to pass across FFI boundaries.
     */
    fun listEmbeddingPresets(): List<String> {
        val resultJson = KreuzbergBridge.nativeListEmbeddingPresets()
        return mapper.readValue(resultJson, object : TypeReference<List<String>>() {})
    }

    /**
     * List the names of all available embedding presets.
     *
     * Returns owned `String`s so the values are safe to pass across FFI boundaries.
     */
    suspend fun listEmbeddingPresetsAsync(): List<String> =
        withContext(Dispatchers.IO) { listEmbeddingPresets() }

}
