// Generated by alef. Do not edit by hand.
#![allow(unused_variables, unreachable_code)]
#![allow(
    clippy::map_identity,
    clippy::let_and_return,
    clippy::collapsible_match,
    clippy::manual_flatten,
    clippy::too_many_arguments,
    clippy::unit_arg,
    clippy::type_complexity,
    clippy::useless_conversion
)]
mod frb_generated;
pub use flutter_rust_bridge::DartFnFuture;
use flutter_rust_bridge::frb;

/// Opaque JSON carrier for Rust's internal `InternalDocument` trait contract.
/// Dart code should pass this value back to Alef-generated bridge APIs rather
/// than treating it as the public `ExtractionResult` DTO.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct InternalDocumentBridge {
    pub json: String,
}
#[allow(unused_imports)]
pub use kreuzberg::InternalDocument;

#[frb(mirror(CacheStats))]
pub struct CacheStats {
    pub total_files: i64,
    pub total_size_mb: f64,
    pub available_space_mb: f64,
    pub oldest_file_age_days: f64,
    pub newest_file_age_days: f64,
}

/// Hardware acceleration configuration for ONNX Runtime models.
///
/// Controls which execution provider (CPU, CoreML, CUDA, TensorRT) is used
/// for inference in layout detection and embedding generation.
///
/// # Example
///
/// ```rust
/// use kreuzberg::AccelerationConfig;
///
/// // Auto-select: CoreML on macOS, CUDA on Linux, CPU elsewhere
/// let config = AccelerationConfig::default();
///
/// // Force CPU only
/// let config = AccelerationConfig {
///     provider: kreuzberg::ExecutionProviderType::Cpu,
///     ..Default::default()
/// };
/// ```
#[frb(mirror(AccelerationConfig))]
pub struct AccelerationConfig {
    /// Execution provider to use for ONNX inference.
    pub provider: ExecutionProviderType,
    /// GPU device ID (for CUDA/TensorRT). Ignored for CPU/CoreML/Auto.
    pub device_id: i64,
}

/// Cross-extractor content filtering configuration.
///
/// Controls whether "furniture" content (headers, footers, page numbers,
/// watermarks, repeating text) is included in or stripped from extraction
/// results. Applies across all extractors (PDF, DOCX, RTF, ODT, HTML, etc.)
/// with format-specific implementation.
///
/// When `None` on `ExtractionConfig`, each extractor uses its current
/// default behavior unchanged.
#[frb(mirror(ContentFilterConfig))]
pub struct ContentFilterConfig {
    /// Include running headers in extraction output.
    ///
    /// - PDF: Disables top-margin furniture stripping and prevents the layout
    ///   model from treating `PageHeader`-classified regions as furniture.
    /// - DOCX: Includes document headers in text output.
    /// - RTF/ODT: Headers already included; this is a no-op when true.
    /// - HTML/EPUB: Keeps `<header>` element content.
    ///
    /// Default: `false` (headers are stripped or excluded).
    pub include_headers: bool,
    /// Include running footers in extraction output.
    ///
    /// - PDF: Disables bottom-margin furniture stripping and prevents the layout
    ///   model from treating `PageFooter`-classified regions as furniture.
    /// - DOCX: Includes document footers in text output.
    /// - RTF/ODT: Footers already included; this is a no-op when true.
    /// - HTML/EPUB: Keeps `<footer>` element content.
    ///
    /// Default: `false` (footers are stripped or excluded).
    pub include_footers: bool,
    /// Enable the heuristic cross-page repeating text detector.
    ///
    /// When `true` (default), text that repeats verbatim across a supermajority
    /// of pages is classified as furniture and stripped.  Disable this if brand
    /// names or repeated headings are being incorrectly removed by the heuristic.
    ///
    /// Note: when a layout-detection model is active, the model may independently
    /// classify page-header / page-footer regions as furniture on a per-page basis.
    /// To preserve those regions, set `include_headers = true`, `include_footers = true`,
    /// or both, in addition to disabling this flag.
    ///
    /// Primarily affects PDF extraction.
    ///
    /// Default: `true`.
    pub strip_repeating_text: bool,
    /// Include watermark text in extraction output.
    ///
    /// - PDF: Keeps watermark artifacts and arXiv identifiers.
    /// - Other formats: No effect currently.
    ///
    /// Default: `false` (watermarks are stripped).
    pub include_watermarks: bool,
}

/// Configuration for email extraction.
#[frb(mirror(EmailConfig))]
pub struct EmailConfig {
    /// Windows codepage number to use when an MSG file contains no codepage property.
    /// Defaults to `None`, which falls back to windows-1252.
    ///
    /// If an unrecognized or invalid codepage number is supplied (including 0),
    /// the behavior silently falls back to windows-1252 — the same as when the
    /// MSG file itself contains an unrecognized codepage. No error or warning is
    /// emitted. Users should verify output when supplying unusual values.
    ///
    /// Common values:
    /// - 1250: Central European (Polish, Czech, Hungarian, etc.)
    /// - 1251: Cyrillic (Russian, Ukrainian, Bulgarian, etc.)
    /// - 1252: Western European (default)
    /// - 1253: Greek
    /// - 1254: Turkish
    /// - 1255: Hebrew
    /// - 1256: Arabic
    /// - 932:  Japanese (Shift-JIS)
    /// - 936:  Simplified Chinese (GBK)
    pub msg_fallback_codepage: Option<i64>,
}

/// Main extraction configuration.
///
/// This struct contains all configuration options for the extraction process.
/// It can be loaded from TOML, YAML, or JSON files, or created programmatically.
///
/// # Example
///
/// ```rust
/// use kreuzberg::core::config::ExtractionConfig;
///
/// // Create with defaults
/// let config = ExtractionConfig::default();
///
/// // Load from TOML file
/// // let config = ExtractionConfig::from_toml_file("kreuzberg.toml")?;
/// ```
#[frb(mirror(ExtractionConfig))]
pub struct ExtractionConfig {
    /// Enable caching of extraction results
    pub use_cache: bool,
    /// Enable quality post-processing
    pub enable_quality_processing: bool,
    /// OCR configuration (None = OCR disabled)
    pub ocr: Option<OcrConfig>,
    /// Force OCR even for searchable PDFs
    pub force_ocr: bool,
    /// Force OCR on specific pages only (1-indexed page numbers, must be >= 1).
    ///
    /// When set, only the listed pages are OCR'd regardless of text layer quality.
    /// Unlisted pages use native text extraction. Ignored when `force_ocr` is `true`.
    /// Only applies to PDF documents. Duplicates are automatically deduplicated.
    /// An `ocr` config is recommended for backend/language selection; defaults are used if absent.
    pub force_ocr_pages: Option<Vec<i64>>,
    /// Disable OCR entirely, even for images.
    ///
    /// When `true`, OCR is skipped for all document types. Images return metadata
    /// only (dimensions, format, EXIF) without text extraction. PDFs use only
    /// native text extraction without OCR fallback.
    ///
    /// Cannot be `true` simultaneously with `force_ocr`.
    ///
    /// *Added in v4.7.0.*
    pub disable_ocr: bool,
    /// Text chunking configuration (None = chunking disabled)
    pub chunking: Option<ChunkingConfig>,
    /// Content filtering configuration (None = use extractor defaults).
    ///
    /// Controls whether document "furniture" (headers, footers, watermarks,
    /// repeating text) is included in or stripped from extraction results.
    /// See [`ContentFilterConfig`] for per-field documentation.
    pub content_filter: Option<ContentFilterConfig>,
    /// Image extraction configuration (None = no image extraction)
    pub images: Option<ImageExtractionConfig>,
    /// PDF-specific options (None = use defaults)
    pub pdf_options: Option<PdfConfig>,
    /// Token reduction configuration (None = no token reduction)
    pub token_reduction: Option<TokenReductionOptions>,
    /// Language detection configuration (None = no language detection)
    pub language_detection: Option<LanguageDetectionConfig>,
    /// Page extraction configuration (None = no page tracking)
    pub pages: Option<PageConfig>,
    /// Keyword extraction configuration (None = no keyword extraction)
    pub keywords: Option<KeywordConfig>,
    /// Post-processor configuration (None = use defaults)
    pub postprocessor: Option<PostProcessorConfig>,
    /// HTML to Markdown conversion options (None = use defaults)
    ///
    /// Configure how HTML documents are converted to Markdown, including heading styles,
    /// list formatting, code block styles, and preprocessing options.
    pub html_options: Option<String>,
    /// Styled HTML output configuration.
    ///
    /// When set alongside `output_format = OutputFormat::Html`, the extraction
    /// pipeline uses [`StyledHtmlRenderer`](crate::rendering::StyledHtmlRenderer)
    /// which emits stable `kb-*` CSS class hooks on every structural element
    /// and optionally embeds theme CSS or user-supplied CSS in a `<style>` block.
    ///
    /// When `None`, the existing plain comrak-based HTML renderer is used.
    pub html_output: Option<HtmlOutputConfig>,
    /// Default per-file timeout in seconds for batch extraction.
    ///
    /// When set, each file in a batch will be canceled after this duration
    /// unless overridden by [`FileExtractionConfig::timeout_secs`].
    ///
    /// Defaults to `Some(60)` to prevent pathological files (e.g. deeply
    /// nested archives, documents with millions of cells) from running
    /// indefinitely and exhausting caller resources. Set to `None` to
    /// disable the timeout for trusted input or long-running workloads.
    pub extraction_timeout_secs: Option<i64>,
    /// Maximum concurrent extractions in batch operations (None = (num_cpus × 1.5).ceil()).
    ///
    /// Limits parallelism to prevent resource exhaustion when processing
    /// large batches. Defaults to (num_cpus × 1.5).ceil() when not set.
    pub max_concurrent_extractions: Option<i64>,
    /// Result structure format
    ///
    /// Controls whether results are returned in unified format (default) with all
    /// content in the `content` field, or element-based format with semantic
    /// elements (for Unstructured-compatible output).
    pub result_format: ResultFormat,
    /// Security limits for archive extraction.
    ///
    /// Controls maximum archive size, compression ratio, file count, and other
    /// security thresholds to prevent decompression bomb attacks. Also caps
    /// nesting depth, iteration count, entity / token length, total
    /// content size, and table cell count for every extraction path that
    /// ingests user-controlled bytes.
    /// When `None`, default limits are used.
    pub security_limits: Option<SecurityLimits>,
    /// Maximum uncompressed size in bytes for a single embedded file before
    /// recursive extraction is attempted (default: 50 MiB).
    ///
    /// Applies to embedded objects inside OOXML containers (DOCX, PPTX) and
    /// to email attachments processed via recursive extraction. Files that
    /// exceed this limit are skipped with a `ProcessingWarning` rather than
    /// passed to the extraction pipeline, preventing a single oversized
    /// embedded object from consuming unbounded memory or time.
    ///
    /// Set to `None` to disable the per-embedded-file cap (falls back to
    /// `security_limits.max_archive_size` as the only guard).
    pub max_embedded_file_bytes: Option<i64>,
    /// Content text format (default: Plain).
    ///
    /// Controls the format of the extracted content:
    /// - `Plain`: Raw extracted text (default)
    /// - `Markdown`: Markdown formatted output
    /// - `Djot`: Djot markup format (requires djot feature)
    /// - `Html`: HTML formatted output
    ///
    /// When set to a structured format, extraction results will include
    /// formatted output. The `formatted_content` field may be populated
    /// when format conversion is applied.
    pub output_format: OutputFormat,
    /// Layout detection configuration (None = layout detection disabled).
    ///
    /// When set, PDF pages and images are analyzed for document structure
    /// (headings, code, formulas, tables, figures, etc.) using RT-DETR models
    /// via ONNX Runtime. For PDFs, layout hints override paragraph classification
    /// in the markdown pipeline. For images, per-region OCR is performed with
    /// markdown formatting based on detected layout classes.
    /// Requires the `layout-detection` feature to run inference; the field is
    /// present whenever the `layout-types` feature is active (which includes
    /// `layout-detection` as well as the no-ORT target groups).
    pub layout: Option<LayoutDetectionConfig>,
    /// Run layout detection on the non-OCR PDF markdown path.
    ///
    /// When `true` and `layout` is `Some(_)`, layout regions inform heading,
    /// table, list, and figure detection in the structure pipeline that would
    /// otherwise rely on font-clustering heuristics alone. Significantly
    /// improves SF1 (structural F1) at the cost of inference latency
    /// (~150-300ms/page CPU, ~20-50ms/page GPU). Default: `false`.
    /// Requires the `layout-detection` feature.
    pub use_layout_for_markdown: bool,
    /// Enable structured document tree output.
    ///
    /// When true, populates the `document` field on `ExtractionResult` with a
    /// hierarchical `DocumentStructure` containing heading-driven section nesting,
    /// table grids, content layer classification, and inline annotations.
    ///
    /// Independent of `result_format` — can be combined with Unified or ElementBased.
    pub include_document_structure: bool,
    /// Hardware acceleration configuration for ONNX Runtime models.
    ///
    /// Controls execution provider selection for layout detection and embedding
    /// models. When `None`, uses platform defaults (CoreML on macOS, CUDA on
    /// Linux, CPU on Windows).
    pub acceleration: Option<AccelerationConfig>,
    /// Cache namespace for tenant isolation.
    ///
    /// When set, cache entries are stored under `{cache_dir}/{namespace}/`.
    /// Must be alphanumeric, hyphens, or underscores only (max 64 chars).
    /// Different namespaces have isolated cache spaces on the same filesystem.
    pub cache_namespace: Option<String>,
    /// Per-request cache TTL in seconds.
    ///
    /// Overrides the global `max_age_days` for this specific extraction.
    /// When `0`, caching is completely skipped (no read or write).
    /// When `None`, the global TTL applies.
    pub cache_ttl_secs: Option<i64>,
    /// Email extraction configuration (None = use defaults).
    ///
    /// Currently supports configuring the fallback codepage for MSG files
    /// that do not specify one. See `EmailConfig` for details.
    pub email: Option<EmailConfig>,
    /// Concurrency limits for constrained environments (None = use defaults).
    ///
    /// Controls Rayon thread pool size, ONNX Runtime intra-op threads, and
    /// (when `max_concurrent_extractions` is unset) the batch concurrency
    /// semaphore. See `ConcurrencyConfig` for details.
    pub concurrency: Option<String>,
    /// Maximum recursion depth for archive extraction (default: 3).
    /// Set to 0 to disable recursive extraction (legacy behavior).
    pub max_archive_depth: i64,
    /// Tree-sitter language pack configuration (None = tree-sitter disabled).
    ///
    /// When set, enables code file extraction using tree-sitter parsers.
    /// Controls grammar download behavior and code analysis options.
    pub tree_sitter: Option<TreeSitterConfig>,
    /// Structured extraction via LLM (None = disabled).
    ///
    /// When set, the extracted document content is sent to an LLM with the
    /// provided JSON schema. The structured response is stored in
    /// `ExtractionResult::structured_output`.
    pub structured_extraction: Option<StructuredExtractionConfig>,
    /// Cancellation token for this extraction (None = no external cancellation).
    ///
    /// Pass a [`CancellationToken`] clone here and call [`CancellationToken::cancel`]
    /// from another thread / task to abort the extraction in progress. The extractor
    /// checks the token at safe checkpoints (before lock acquisition, between pages,
    /// between batch items) and returns [`KreuzbergError::Cancelled`] when set.
    ///
    /// The field is excluded from serialization because `CancellationToken` is a
    /// runtime handle, not a configuration value.
    pub cancel_token: Option<String>,
}

/// Per-file extraction configuration overrides for batch processing.
///
/// All fields are `Option<T>` — `None` means "use the batch-level default."
/// This type is used with `batch_extract_files` and
/// `batch_extract_bytes` to allow heterogeneous
/// extraction settings within a single batch.
///
/// # Excluded Fields
///
/// The following `ExtractionConfig` fields are batch-level only and
/// cannot be overridden per file:
/// - `max_concurrent_extractions` — controls batch parallelism
/// - `use_cache` — global caching policy
/// - `acceleration` — shared ONNX execution provider
/// - `security_limits` — global archive security policy
///
/// # Example
///
/// ```rust
/// use kreuzberg::FileExtractionConfig;
///
/// // Override just OCR forcing for a specific file
/// let config = FileExtractionConfig {
///     force_ocr: Some(true),
///     ..Default::default()
/// };
/// ```
#[frb(mirror(FileExtractionConfig))]
pub struct FileExtractionConfig {
    /// Override quality post-processing for this file.
    pub enable_quality_processing: Option<bool>,
    /// Override OCR configuration for this file (None in the Option = use batch default).
    pub ocr: Option<OcrConfig>,
    /// Override force OCR for this file.
    pub force_ocr: Option<bool>,
    /// Override force OCR pages for this file (1-indexed page numbers).
    pub force_ocr_pages: Option<Vec<i64>>,
    /// Override disable OCR for this file.
    pub disable_ocr: Option<bool>,
    /// Override chunking configuration for this file.
    pub chunking: Option<ChunkingConfig>,
    /// Override content filtering configuration for this file.
    pub content_filter: Option<ContentFilterConfig>,
    /// Override image extraction configuration for this file.
    pub images: Option<ImageExtractionConfig>,
    /// Override PDF options for this file.
    pub pdf_options: Option<PdfConfig>,
    /// Override token reduction for this file.
    pub token_reduction: Option<TokenReductionOptions>,
    /// Override language detection for this file.
    pub language_detection: Option<LanguageDetectionConfig>,
    /// Override page extraction for this file.
    pub pages: Option<PageConfig>,
    /// Override keyword extraction for this file.
    pub keywords: Option<KeywordConfig>,
    /// Override post-processor for this file.
    pub postprocessor: Option<PostProcessorConfig>,
    /// Override HTML conversion options for this file.
    pub html_options: Option<String>,
    /// Override result format for this file.
    pub result_format: Option<ResultFormat>,
    /// Override output content format for this file.
    pub output_format: Option<OutputFormat>,
    /// Override document structure output for this file.
    pub include_document_structure: Option<bool>,
    /// Override layout detection for this file.
    pub layout: Option<LayoutDetectionConfig>,
    /// Override per-file extraction timeout in seconds.
    ///
    /// When set, the extraction for this file will be canceled after the
    /// specified duration. A timed-out file produces an error result without
    /// affecting other files in the batch.
    pub timeout_secs: Option<i64>,
    /// Override tree-sitter configuration for this file.
    pub tree_sitter: Option<TreeSitterConfig>,
    /// Override structured extraction configuration for this file.
    ///
    /// When set, enables LLM-based structured extraction with a JSON schema
    /// for this specific file. The extracted content is sent to a VLM/LLM
    /// and the response is parsed according to the provided schema.
    pub structured_extraction: Option<StructuredExtractionConfig>,
}

/// Batch item for byte array extraction.
///
/// Used with `batch_extract_bytes` and `batch_extract_bytes_sync`
/// to represent a single item in a batch extraction job.
#[frb(mirror(BatchBytesItem))]
pub struct BatchBytesItem {
    /// The content bytes to extract from
    pub content: Vec<u8>,
    /// MIME type of the content (e.g., "application/pdf", "text/html")
    pub mime_type: String,
    /// Per-item configuration overrides (None uses batch-level defaults)
    pub config: Option<FileExtractionConfig>,
}

/// Batch item for file extraction.
///
/// Used with `batch_extract_files` and `batch_extract_files_sync`
/// to represent a single file in a batch extraction job.
#[frb(mirror(BatchFileItem))]
pub struct BatchFileItem {
    /// Path to the file to extract from
    pub path: String,
    /// Per-file configuration overrides (None uses batch-level defaults)
    pub config: Option<FileExtractionConfig>,
}

/// Image extraction configuration.
#[frb(mirror(ImageExtractionConfig))]
pub struct ImageExtractionConfig {
    /// Extract images from documents
    pub extract_images: bool,
    /// Target DPI for image normalization
    pub target_dpi: i64,
    /// Maximum dimension for images (width or height)
    pub max_image_dimension: i64,
    /// Whether to inject image reference placeholders into markdown output.
    /// When `true` (default), image references like `![Image 1](embedded:p1_i0)`
    /// are appended to the markdown. Set to `false` to extract images as data
    /// without polluting the markdown output.
    pub inject_placeholders: bool,
    /// Automatically adjust DPI based on image content
    pub auto_adjust_dpi: bool,
    /// Minimum DPI threshold
    pub min_dpi: i64,
    /// Maximum DPI threshold
    pub max_dpi: i64,
    /// Maximum number of image objects to extract per PDF page.
    ///
    /// Some PDFs (e.g. technical diagrams stored as thousands of raster fragments)
    /// can trigger extremely long or indefinite extraction times when every image
    /// object on a dense page is decoded individually via the PDF extractor. Setting this
    /// limit causes kreuzberg to stop collecting individual images once the count
    /// per page reaches the cap and emit a warning instead.
    ///
    /// `None` (default) means no limit — all images are extracted.
    pub max_images_per_page: Option<i64>,
    /// When `true` (default), extracted images are classified by kind and grouped
    /// into clusters where they appear to belong to one figure.
    pub classify: bool,
    /// When `true`, full-page renders produced during OCR preprocessing are captured
    /// and returned as `ImageKind::PageRaster` entries in `ExtractionResult.images`.
    ///
    /// **PDF + OCR only.** No rasters are captured for non-PDF inputs or when the
    /// document-level OCR bypass is active (whole-document backend). When OCR is
    /// enabled and this flag is set but the active backend skips per-page rendering,
    /// a `ProcessingWarning` is emitted in `ExtractionResult.processing_warnings`.
    ///
    /// Defaults to `false`. Enable when downstream consumers need page thumbnails
    /// (e.g. citation previews, visual grounding).
    pub include_page_rasters: bool,
    /// Run OCR on extracted images and include the recognized text in the document content.
    ///
    /// When `true` (default) and `ExtractionConfig.ocr` is configured, extracted images
    /// are processed with the configured OCR backend. Set to `false` to extract images
    /// without OCR processing, even when OCR is enabled.
    pub run_ocr_on_images: bool,
    /// When `true`, image OCR results are rendered as plain text without the
    /// `![...](...)` markdown placeholder. Only takes effect when `run_ocr_on_images`
    /// is also `true`.
    pub ocr_text_only: bool,
    /// When `true` and `ocr_text_only` is `false`, append the OCR text after
    /// the image placeholder in the rendered output.
    pub append_ocr_text: bool,
}

/// Token reduction configuration.
#[frb(mirror(TokenReductionOptions))]
pub struct TokenReductionOptions {
    /// Reduction mode: "off", "light", "moderate", "aggressive", "maximum"
    pub mode: String,
    /// Preserve important words (capitalized, technical terms)
    pub preserve_important_words: bool,
}

/// Language detection configuration.
#[frb(mirror(LanguageDetectionConfig))]
pub struct LanguageDetectionConfig {
    /// Enable language detection
    pub enabled: bool,
    /// Minimum confidence threshold (0.0-1.0)
    pub min_confidence: f64,
    /// Detect multiple languages in the document
    pub detect_multiple: bool,
}

/// Configuration for styled HTML output.
///
/// When set on [`ExtractionConfig::html_output`] alongside
/// `output_format = OutputFormat::Html`, the pipeline builds a
/// [`StyledHtmlRenderer`](crate::rendering::StyledHtmlRenderer) instead of
/// the plain comrak-based renderer.
///
/// # Example
///
/// ```rust
/// use kreuzberg::core::config::{HtmlOutputConfig, HtmlTheme};
///
/// let config = HtmlOutputConfig {
///     theme: HtmlTheme::GitHub,
///     css: Some(".kb-p { font-size: 1.1rem; }".to_string()),
///     ..Default::default()
/// };
/// ```
#[frb(mirror(HtmlOutputConfig))]
pub struct HtmlOutputConfig {
    /// Inline CSS string injected into the output after the theme stylesheet.
    /// Concatenated after `css_file` content when both are set.
    pub css: Option<String>,
    /// Path to a CSS file loaded once at renderer construction time.
    /// Concatenated before `css` when both are set.
    pub css_file: Option<String>,
    /// Built-in colour/typography theme. Default: [`HtmlTheme::Unstyled`].
    pub theme: HtmlTheme,
    /// CSS class prefix applied to every emitted class name.
    ///
    /// Default: `"kb-"`. Change this if your host application already uses
    /// classes that start with `kb-`.
    pub class_prefix: String,
    /// When `true` (default), write the resolved CSS into a `<style>` block
    /// immediately after the opening `<div class="{prefix}doc">`.
    ///
    /// Set to `false` to emit only the structural markup and wire up your
    /// own stylesheet targeting the `kb-*` class names.
    pub embed_css: bool,
}

/// Layout detection configuration.
///
/// Controls layout detection behavior in the extraction pipeline.
/// When set on [`ExtractionConfig`](super::ExtractionConfig), layout detection
/// is enabled for PDF extraction.
#[frb(mirror(LayoutDetectionConfig))]
pub struct LayoutDetectionConfig {
    /// Confidence threshold override (None = use model default).
    pub confidence_threshold: Option<f64>,
    /// Whether to apply postprocessing heuristics (default: true).
    pub apply_heuristics: bool,
    /// Table structure recognition model.
    ///
    /// Controls which model is used for table cell detection within layout-detected
    /// table regions. Defaults to [`TableModel::Tatr`].
    pub table_model: TableModel,
    /// Hardware acceleration for ONNX models (layout detection + table structure).
    ///
    /// When set, controls which execution provider (CPU, CUDA, CoreML, TensorRT)
    /// is used for inference. Defaults to `None` (auto-select per platform).
    pub acceleration: Option<AccelerationConfig>,
}

/// Configuration for an LLM provider/model via liter-llm.
///
/// Each feature (VLM OCR, VLM embeddings, structured extraction) carries
/// its own `LlmConfig`, allowing different providers per feature.
///
/// # Example
///
/// ```toml
/// [structured_extraction.llm]
/// model = "openai/gpt-4o"
/// api_key = "sk-..."  # or use KREUZBERG_LLM_API_KEY env var
/// ```
#[frb(mirror(LlmConfig))]
pub struct LlmConfig {
    /// Provider/model string using liter-llm routing format.
    ///
    /// Examples: `"openai/gpt-4o"`, `"anthropic/claude-sonnet-4-20250514"`,
    /// `"groq/llama-3.1-70b-versatile"`.
    pub model: String,
    /// API key for the provider. When `None`, liter-llm falls back to
    /// the provider's standard environment variable (e.g., `OPENAI_API_KEY`).
    pub api_key: Option<String>,
    /// Custom base URL override for the provider endpoint.
    pub base_url: Option<String>,
    /// Request timeout in seconds (default: 60).
    pub timeout_secs: Option<i64>,
    /// Maximum retry attempts (default: 3).
    pub max_retries: Option<i64>,
    /// Sampling temperature for generation tasks.
    pub temperature: Option<f64>,
    /// Maximum tokens to generate.
    pub max_tokens: Option<i64>,
}

/// Configuration for LLM-based structured data extraction.
///
/// Sends extracted document content to a VLM with a JSON schema,
/// returning structured data that conforms to the schema.
///
/// # Example
///
/// ```toml
/// [structured_extraction]
/// schema_name = "invoice_data"
/// strict = true
///
/// [structured_extraction.schema]
/// type = "object"
/// properties.vendor = { type = "string" }
/// properties.total = { type = "number" }
/// required = ["vendor", "total"]
///
/// [structured_extraction.llm]
/// model = "openai/gpt-4o"
/// ```
#[frb(mirror(StructuredExtractionConfig))]
pub struct StructuredExtractionConfig {
    /// JSON Schema defining the desired output structure.
    pub schema: String,
    /// Schema name passed to the LLM's structured output mode.
    pub schema_name: String,
    /// Optional schema description for the LLM.
    pub schema_description: Option<String>,
    /// Enable strict mode — output must exactly match the schema.
    pub strict: bool,
    /// Custom Jinja2 extraction prompt template. When `None`, a default template is used.
    ///
    /// Available template variables:
    /// - `{{ content }}` — The extracted document text.
    /// - `{{ schema }}` — The JSON schema as a formatted string.
    /// - `{{ schema_name }}` — The schema name.
    /// - `{{ schema_description }}` — The schema description (may be empty).
    pub prompt: Option<String>,
    /// LLM configuration for the extraction.
    pub llm: LlmConfig,
}

/// Quality thresholds for OCR fallback decisions and pipeline quality gating.
///
/// All fields default to the values that match the previous hardcoded behavior,
/// so `OcrQualityThresholds::default()` preserves existing semantics exactly.
#[frb(mirror(OcrQualityThresholds))]
pub struct OcrQualityThresholds {
    /// Minimum total non-whitespace characters to consider text substantive.
    pub min_total_non_whitespace: i64,
    /// Minimum non-whitespace characters per page on average.
    pub min_non_whitespace_per_page: f64,
    /// Minimum character count for a word to be "meaningful".
    pub min_meaningful_word_len: i64,
    /// Minimum count of meaningful words before text is accepted.
    pub min_meaningful_words: i64,
    /// Minimum alphanumeric ratio (non-whitespace chars that are alphanumeric).
    pub min_alnum_ratio: f64,
    /// Minimum Unicode replacement characters (U+FFFD) to trigger OCR fallback.
    pub min_garbage_chars: i64,
    /// Maximum fraction of short (1-2 char) words before text is considered fragmented.
    pub max_fragmented_word_ratio: f64,
    /// Critical fragmentation threshold — triggers OCR regardless of meaningful words.
    /// Normal English text has ~20-30% short words. 80%+ is definitive garbage.
    pub critical_fragmented_word_ratio: f64,
    /// Minimum average word length. Below this with enough words indicates garbled extraction.
    pub min_avg_word_length: f64,
    /// Minimum word count before average word length check applies.
    pub min_words_for_avg_length_check: i64,
    /// Minimum consecutive word repetition ratio to detect column scrambling.
    pub min_consecutive_repeat_ratio: f64,
    /// Minimum word count before consecutive repetition check is applied.
    pub min_words_for_repeat_check: i64,
    /// Minimum character count for "substantive markdown" OCR skip gate.
    pub substantive_min_chars: i64,
    /// Minimum character count for "non-text content" OCR skip gate.
    pub non_text_min_chars: i64,
    /// Alphanumeric+whitespace ratio threshold for skip decisions.
    pub alnum_ws_ratio_threshold: f64,
    /// Minimum quality score (0.0-1.0) for a pipeline stage result to be accepted.
    /// If the result from a backend scores below this, try the next backend.
    pub pipeline_min_quality: f64,
}

/// A single backend stage in the OCR pipeline.
#[frb(mirror(OcrPipelineStage))]
pub struct OcrPipelineStage {
    /// Backend name: "tesseract", "paddleocr", "easyocr", or a custom registered name.
    pub backend: String,
    /// Priority weight (higher = tried first). Stages are sorted by priority descending.
    pub priority: i64,
    /// Language override for this stage (None = use parent OcrConfig.language).
    pub language: Option<String>,
    /// Tesseract-specific config override for this stage.
    pub tesseract_config: Option<TesseractConfig>,
    /// PaddleOCR-specific config for this stage.
    pub paddle_ocr_config: Option<String>,
    /// VLM config override for this pipeline stage.
    pub vlm_config: Option<LlmConfig>,
    /// Arbitrary per-call options passed through to the backend unchanged.
    ///
    /// Backends that support runtime tuning (mode switching, preprocessing
    /// flags, inference parameters, etc.) read this value and deserialize
    /// the keys they care about. Keys unknown to the backend are silently
    /// ignored, so options from different backends can coexist in the same
    /// config without conflict.
    ///
    /// Example (custom backend):
    /// ```json
    /// { "mode": "fast", "enable_layout": true }
    /// ```
    pub backend_options: Option<String>,
}

/// Multi-backend OCR pipeline with quality-based fallback.
///
/// Backends are tried in priority order (highest first). After each backend
/// produces output, quality is evaluated. If it meets `quality_thresholds.pipeline_min_quality`,
/// the result is accepted. Otherwise the next backend is tried.
#[frb(mirror(OcrPipelineConfig))]
pub struct OcrPipelineConfig {
    /// Ordered list of backends to try. Sorted by priority (descending) at runtime.
    pub stages: Vec<OcrPipelineStage>,
    /// Quality thresholds for deciding whether to accept a result or try the next backend.
    pub quality_thresholds: OcrQualityThresholds,
}

/// OCR configuration.
#[frb(mirror(OcrConfig))]
pub struct OcrConfig {
    /// Whether OCR is enabled.
    ///
    /// Setting `enabled: false` is a shorthand for `disable_ocr: true` on the parent
    /// [`ExtractionConfig`](crate::core::config::ExtractionConfig). Images return
    /// metadata only; PDFs use native text extraction without OCR fallback.
    ///
    /// Defaults to `true`. When `false`, all other OCR settings are ignored.
    pub enabled: bool,
    /// OCR backend: tesseract, easyocr, paddleocr
    pub backend: String,
    /// Language code (e.g., "eng", "deu")
    pub language: String,
    /// Tesseract-specific configuration (optional)
    pub tesseract_config: Option<TesseractConfig>,
    /// Output format for OCR results (optional, for format conversion)
    pub output_format: Option<OutputFormat>,
    /// PaddleOCR-specific configuration (optional, JSON passthrough)
    pub paddle_ocr_config: Option<String>,
    /// Arbitrary per-call options passed through to the backend unchanged.
    ///
    /// Custom OCR backends and built-in backends that support runtime tuning
    /// can read this value and deserialize the keys they care about. Keys
    /// unknown to the backend are silently ignored.
    ///
    /// This is the recommended extension point for per-call parameters that
    /// are not covered by the typed fields above (e.g. mode switching,
    /// preprocessing flags, inference batch size).
    ///
    /// **Scope:** when `pipeline` is `None`, this value is propagated to the
    /// primary stage of the auto-constructed pipeline. When `pipeline` is
    /// explicitly set, this field has **no effect** — the caller must set
    /// `OcrPipelineStage.backend_options` directly on the relevant stage(s)
    /// instead.
    ///
    /// Example:
    /// ```json
    /// { "mode": "fast", "enable_layout": true, "timeout_ms": 5000 }
    /// ```
    pub backend_options: Option<String>,
    /// OCR element extraction configuration
    pub element_config: Option<OcrElementConfig>,
    /// Quality thresholds for the native-text-to-OCR fallback decision.
    /// When None, uses compiled defaults (matching previous hardcoded behavior).
    pub quality_thresholds: Option<OcrQualityThresholds>,
    /// Multi-backend OCR pipeline configuration. When set, enables weighted
    /// fallback across multiple OCR backends based on output quality.
    /// When None, uses the single `backend` field (same as today).
    pub pipeline: Option<OcrPipelineConfig>,
    /// Enable automatic page rotation based on orientation detection.
    ///
    /// When enabled, uses Tesseract's `DetectOrientationScript()` to detect
    /// page orientation (0/90/180/270 degrees) before OCR. If the page is
    /// rotated with high confidence, the image is corrected before recognition.
    /// This is critical for handling rotated scanned documents.
    pub auto_rotate: bool,
    /// VLM (Vision Language Model) OCR configuration.
    ///
    /// Required when `backend` is `"vlm"`. Uses liter-llm to send page
    /// images to a vision model for text extraction.
    pub vlm_config: Option<LlmConfig>,
    /// Custom Jinja2 prompt template for VLM OCR.
    ///
    /// When `None`, uses the default template. Available variables:
    /// - `{{ language }}` — The document language code (e.g., "eng", "deu").
    pub vlm_prompt: Option<String>,
    /// Hardware acceleration for ONNX Runtime models (e.g. PaddleOCR, layout detection).
    ///
    /// Not user-configurable via config files — injected at runtime from
    /// `ExtractionConfig::acceleration` before each `process_image` call.
    pub acceleration: Option<AccelerationConfig>,
    /// Caller-supplied Tesseract `traineddata` bytes per language code.
    ///
    /// Primary use case is the WASM build, which has no filesystem and cannot
    /// download tessdata at runtime. Native builds typically rely on
    /// `TessdataManager` and ignore this field. When present, the WASM
    /// Tesseract backend prefers these bytes over its compile-time-bundled
    /// English data.
    ///
    /// Skipped by serde to keep config files small — supply via the typed API
    /// at runtime.
    pub tessdata_bytes: Option<std::collections::HashMap<String, Vec<u8>>>,
}

/// Page extraction and tracking configuration.
///
/// Controls how pages are extracted, tracked, and represented in the extraction results.
/// When `None`, page tracking is disabled.
///
/// Page range tracking in chunk metadata (first_page/last_page) is automatically enabled
/// when page boundaries are available and chunking is configured.
#[frb(mirror(PageConfig))]
pub struct PageConfig {
    /// Extract pages as separate array (ExtractionResult.pages)
    pub extract_pages: bool,
    /// Insert page markers in main content string
    pub insert_page_markers: bool,
    /// Page marker format (use {page_num} placeholder)
    /// Default: "\n\n<!-- PAGE {page_num} -->\n\n"
    pub marker_format: String,
}

/// PDF-specific configuration.
#[frb(mirror(PdfConfig))]
pub struct PdfConfig {
    /// Extract images from PDF
    pub extract_images: bool,
    /// Extract tables from PDF.
    ///
    /// When `true` (default), runs pdf_oxide's native grid detector and, if it
    /// finds nothing, falls back to the heuristic text-layer reconstruction in
    /// `pdf::oxide::table::extract_tables_heuristic`. Set to `false` to skip
    /// both passes — `tables` will then be empty in the result.
    pub extract_tables: bool,
    /// List of passwords to try when opening encrypted PDFs
    pub passwords: Option<Vec<String>>,
    /// Extract PDF metadata
    pub extract_metadata: bool,
    /// Hierarchy extraction configuration (None = hierarchy extraction disabled)
    pub hierarchy: Option<HierarchyConfig>,
    /// Extract PDF annotations (text notes, highlights, links, stamps).
    /// Default: false
    pub extract_annotations: bool,
    /// Top margin fraction (0.0–1.0) of page height to exclude headers/running heads.
    /// Default: 0.06 (6%)
    pub top_margin_fraction: Option<f64>,
    /// Bottom margin fraction (0.0–1.0) of page height to exclude footers/page numbers.
    /// Default: 0.05 (5%)
    pub bottom_margin_fraction: Option<f64>,
    /// Allow single-column pseudo tables in extraction results.
    ///
    /// By default, tables with fewer than 2 columns (layout-guided) or 3 columns
    /// (heuristic) are rejected. When `true`, the minimum column count is relaxed
    /// to 1, allowing single-column structured data (glossaries, itemized lists)
    /// to be emitted as tables. Other quality filters (density, sparsity, prose
    /// detection) still apply.
    pub allow_single_column_tables: bool,
    /// Perform OCR on inline images extracted from PDF pages and attach the
    /// recognized text to each `ExtractedImage.ocr_result`. Requires Tesseract
    /// to be available; if `ExtractionConfig.ocr` is `None` the extractor
    /// falls back to `TesseractConfig::default()`. Per-image failures degrade
    /// gracefully (the image is returned without OCR text rather than failing
    /// the whole extraction). Default: `false`.
    pub ocr_inline_images: bool,
}

/// Hierarchy extraction configuration for PDF text structure analysis.
///
/// Enables extraction of document hierarchy levels (H1-H6) based on font size
/// clustering and semantic analysis. When enabled, hierarchical blocks are
/// included in page content.
#[frb(mirror(HierarchyConfig))]
pub struct HierarchyConfig {
    /// Enable hierarchy extraction
    pub enabled: bool,
    /// Number of font size clusters to use for hierarchy levels (1-7)
    ///
    /// Default: 6, which provides H1-H6 heading levels with body text.
    /// Larger values create more fine-grained hierarchy levels.
    pub k_clusters: i64,
    /// Include bounding box information in hierarchy blocks
    pub include_bbox: bool,
    /// OCR coverage threshold for smart OCR triggering (0.0-1.0)
    ///
    /// Determines when OCR should be triggered based on text block coverage.
    /// OCR is triggered when text blocks cover less than this fraction of the page.
    /// Default: 0.5 (trigger OCR if less than 50% of page has text)
    pub ocr_coverage_threshold: Option<f64>,
}

/// Post-processor configuration.
#[frb(mirror(PostProcessorConfig))]
pub struct PostProcessorConfig {
    /// Enable post-processors
    pub enabled: bool,
    /// Whitelist of processor names to run (None = all enabled)
    pub enabled_processors: Option<Vec<String>>,
    /// Blacklist of processor names to skip (None = none disabled)
    pub disabled_processors: Option<Vec<String>>,
    /// Pre-computed AHashSet for O(1) enabled processor lookup
    pub enabled_set: Option<Vec<String>>,
    /// Pre-computed AHashSet for O(1) disabled processor lookup
    pub disabled_set: Option<Vec<String>>,
}

/// Chunking configuration.
///
/// Configures text chunking for document content, including chunk size,
/// overlap, trimming behavior, and optional embeddings.
///
/// Use `..Default::default()` when constructing to allow for future field additions:
/// ```rust
/// let config = ChunkingConfig {
///     max_characters: 500,
///     ..Default::default()
/// };
/// ```
#[frb(mirror(ChunkingConfig))]
pub struct ChunkingConfig {
    /// Maximum size per chunk (in units determined by `sizing`).
    ///
    /// When `sizing` is `Characters` (default), this is the max character count.
    /// When using token-based sizing, this is the max token count.
    ///
    /// Default: 1000
    pub max_characters: i64,
    /// Overlap between chunks (in units determined by `sizing`).
    ///
    /// Default: 200
    pub overlap: i64,
    /// Whether to trim whitespace from chunk boundaries.
    ///
    /// Default: true
    pub trim: bool,
    /// Type of chunker to use (Text or Markdown).
    ///
    /// Default: Text
    pub chunker_type: ChunkerType,
    /// Optional embedding configuration for chunk embeddings.
    pub embedding: Option<EmbeddingConfig>,
    /// Use a preset configuration (overrides individual settings if provided).
    pub preset: Option<String>,
    /// How to measure chunk size.
    ///
    /// Default: `Characters` (Unicode character count).
    /// Enable `chunking-tiktoken` or `chunking-tokenizers` features for token-based sizing.
    pub sizing: ChunkSizing,
    /// When `true` and `chunker_type` is `Markdown`, prepend the heading hierarchy
    /// path (e.g. `"# Title > ## Section\n\n"`) to each chunk's content string.
    ///
    /// This is useful for RAG pipelines where each chunk needs self-contained
    /// context about its position in the document structure.
    ///
    /// Default: `false`
    pub prepend_heading_context: bool,
    /// Optional cosine similarity threshold for semantic topic boundary detection.
    ///
    /// Only used when `chunker_type` is `Semantic` and an `EmbeddingConfig` is
    /// provided. You almost never need to set this. When omitted, defaults to
    /// `0.75` which works well for most documents. Lower values detect more
    /// topic boundaries (more, smaller chunks); higher values detect fewer.
    /// Range: `0.0..=1.0`.
    pub topic_threshold: Option<f64>,
}

/// Embedding configuration for text chunks.
///
/// Configures embedding generation using ONNX models via the vendored embedding engine.
/// Requires the `embeddings` feature to be enabled.
#[frb(mirror(EmbeddingConfig))]
pub struct EmbeddingConfig {
    /// The embedding model to use (defaults to "balanced" preset if not specified)
    pub model: EmbeddingModelType,
    /// Whether to normalize embedding vectors (recommended for cosine similarity)
    pub normalize: bool,
    /// Batch size for embedding generation
    pub batch_size: i64,
    /// Show model download progress
    pub show_download_progress: bool,
    /// Custom cache directory for model files
    ///
    /// Defaults to `~/.cache/kreuzberg/embeddings/` if not specified.
    /// Allows full customization of model download location.
    pub cache_dir: Option<String>,
    /// Hardware acceleration for the embedding ONNX model.
    ///
    /// When set, controls which execution provider (CPU, CUDA, CoreML, TensorRT)
    /// is used for inference. Defaults to `None` (auto-select per platform).
    pub acceleration: Option<AccelerationConfig>,
    /// Maximum wall-clock duration (in seconds) for a single `embed()` call when
    /// using [`EmbeddingModelType::Plugin`].
    ///
    /// Applies only to the in-process plugin path — protects against hung
    /// host-language backends (e.g. a Python callback deadlocked on the GIL,
    /// a model stuck on CUDA OOM retries, etc.). On timeout, the dispatcher
    /// returns `Plugin` instead of blocking forever.
    ///
    /// `None` disables the timeout. The default (60 seconds) is conservative
    /// for common in-process inference; increase for large batches on slow
    /// hardware.
    pub max_embed_duration_secs: Option<i64>,
}

/// Configuration for tree-sitter language pack integration.
///
/// Controls grammar download behavior and code analysis options.
///
/// # Example (TOML)
///
/// ```toml
/// [tree_sitter]
/// languages = ["python", "rust"]
/// groups = ["web"]
///
/// [tree_sitter.process]
/// structure = true
/// comments = true
/// docstrings = true
/// ```
#[frb(mirror(TreeSitterConfig))]
pub struct TreeSitterConfig {
    /// Enable code intelligence processing (default: true).
    ///
    /// When `false`, tree-sitter analysis is completely skipped even if
    /// the config section is present.
    pub enabled: bool,
    /// Custom cache directory for downloaded grammars.
    ///
    /// When `None`, uses the default: `~/.cache/tree-sitter-language-pack/v{version}/libs/`.
    pub cache_dir: Option<String>,
    /// Languages to pre-download on init (e.g., `["python", "rust"]`).
    pub languages: Option<Vec<String>>,
    /// Language groups to pre-download (e.g., `["web", "systems", "scripting"]`).
    pub groups: Option<Vec<String>>,
    /// Processing options for code analysis.
    pub process: TreeSitterProcessConfig,
}

/// Processing options for tree-sitter code analysis.
///
/// Controls which analysis features are enabled when extracting code files.
#[frb(mirror(TreeSitterProcessConfig))]
pub struct TreeSitterProcessConfig {
    /// Extract structural items (functions, classes, structs, etc.). Default: true.
    pub structure: bool,
    /// Extract import statements. Default: true.
    pub imports: bool,
    /// Extract export statements. Default: true.
    pub exports: bool,
    /// Extract comments. Default: false.
    pub comments: bool,
    /// Extract docstrings. Default: false.
    pub docstrings: bool,
    /// Extract symbol definitions. Default: false.
    pub symbols: bool,
    /// Include parse diagnostics. Default: false.
    pub diagnostics: bool,
    /// Maximum chunk size in bytes. `None` disables chunking.
    pub chunk_max_size: Option<i64>,
    /// Content rendering mode for code extraction.
    pub content_mode: CodeContentMode,
}

/// A supported document format entry.
///
/// Represents a file extension and its corresponding MIME type that Kreuzberg can process.
#[frb(mirror(SupportedFormat))]
pub struct SupportedFormat {
    /// File extension (without leading dot), e.g., "pdf", "docx"
    pub extension: String,
    /// MIME type string, e.g., "application/pdf"
    pub mime_type: String,
}

/// API server configuration.
///
/// This struct holds all configuration options for the Kreuzberg API server,
/// including host/port settings, CORS configuration, and upload limits.
///
/// # Defaults
///
/// - `host`: "127.0.0.1" (localhost only)
/// - `port`: 8000
/// - `cors_origins`: empty vector (allows all origins)
/// - `max_request_body_bytes`: 104_857_600 (100 MB)
/// - `max_multipart_field_bytes`: 104_857_600 (100 MB)
#[frb(mirror(ServerConfig))]
pub struct ServerConfig {
    /// Server host address (e.g., "127.0.0.1", "0.0.0.0")
    pub host: String,
    /// Server port number
    pub port: i64,
    /// CORS allowed origins. Empty vector means allow all origins.
    ///
    /// If this is an empty vector, the server will accept requests from any origin.
    /// If populated with specific origins (e.g., `"https://example.com"`), only
    /// those origins will be allowed.
    pub cors_origins: Vec<String>,
    /// Maximum size of request body in bytes (default: 100 MB)
    pub max_request_body_bytes: i64,
    /// Maximum size of multipart fields in bytes (default: 100 MB)
    pub max_multipart_field_bytes: i64,
}

#[frb(mirror(StructuredDataResult))]
pub struct StructuredDataResult {
    pub content: String,
    pub format: String,
    pub metadata: std::collections::HashMap<String, String>,
    pub text_fields: Vec<String>,
}

/// Application properties from docProps/app.xml for DOCX
///
/// Contains Word-specific document statistics and metadata.
#[frb(mirror(DocxAppProperties))]
pub struct DocxAppProperties {
    /// Application name (e.g., "Microsoft Office Word")
    pub application: Option<String>,
    /// Application version
    pub app_version: Option<String>,
    /// Template filename
    pub template: Option<String>,
    /// Total editing time in minutes
    pub total_time: Option<i64>,
    /// Number of pages
    pub pages: Option<i64>,
    /// Number of words
    pub words: Option<i64>,
    /// Number of characters (excluding spaces)
    pub characters: Option<i64>,
    /// Number of characters (including spaces)
    pub characters_with_spaces: Option<i64>,
    /// Number of lines
    pub lines: Option<i64>,
    /// Number of paragraphs
    pub paragraphs: Option<i64>,
    /// Company name
    pub company: Option<String>,
    /// Document security level
    pub doc_security: Option<i64>,
    /// Scale crop flag
    pub scale_crop: Option<bool>,
    /// Links up to date flag
    pub links_up_to_date: Option<bool>,
    /// Shared document flag
    pub shared_doc: Option<bool>,
    /// Hyperlinks changed flag
    pub hyperlinks_changed: Option<bool>,
}

/// Application properties from docProps/app.xml for XLSX
///
/// Contains Excel-specific document metadata.
#[frb(mirror(XlsxAppProperties))]
pub struct XlsxAppProperties {
    /// Application name (e.g., "Microsoft Excel")
    pub application: Option<String>,
    /// Application version
    pub app_version: Option<String>,
    /// Document security level
    pub doc_security: Option<i64>,
    /// Scale crop flag
    pub scale_crop: Option<bool>,
    /// Links up to date flag
    pub links_up_to_date: Option<bool>,
    /// Shared document flag
    pub shared_doc: Option<bool>,
    /// Hyperlinks changed flag
    pub hyperlinks_changed: Option<bool>,
    /// Company name
    pub company: Option<String>,
    /// Worksheet names
    pub worksheet_names: Vec<String>,
}

/// Application properties from docProps/app.xml for PPTX
///
/// Contains PowerPoint-specific document metadata.
#[frb(mirror(PptxAppProperties))]
pub struct PptxAppProperties {
    /// Application name (e.g., "Microsoft Office PowerPoint")
    pub application: Option<String>,
    /// Application version
    pub app_version: Option<String>,
    /// Total editing time in minutes
    pub total_time: Option<i64>,
    /// Company name
    pub company: Option<String>,
    /// Document security level
    pub doc_security: Option<i64>,
    /// Scale crop flag
    pub scale_crop: Option<bool>,
    /// Links up to date flag
    pub links_up_to_date: Option<bool>,
    /// Shared document flag
    pub shared_doc: Option<bool>,
    /// Hyperlinks changed flag
    pub hyperlinks_changed: Option<bool>,
    /// Number of slides
    pub slides: Option<i64>,
    /// Number of notes
    pub notes: Option<i64>,
    /// Number of hidden slides
    pub hidden_slides: Option<i64>,
    /// Number of multimedia clips
    pub multimedia_clips: Option<i64>,
    /// Presentation format (e.g., "Widescreen", "Standard")
    pub presentation_format: Option<String>,
    /// Slide titles
    pub slide_titles: Vec<String>,
}

/// Dublin Core metadata from docProps/core.xml
///
/// Contains standard metadata fields defined by the Dublin Core standard
/// and Office-specific extensions.
#[frb(mirror(CoreProperties))]
pub struct CoreProperties {
    /// Document title
    pub title: Option<String>,
    /// Document subject/topic
    pub subject: Option<String>,
    /// Document creator/author
    pub creator: Option<String>,
    /// Keywords or tags
    pub keywords: Option<String>,
    /// Document description/abstract
    pub description: Option<String>,
    /// User who last modified the document
    pub last_modified_by: Option<String>,
    /// Revision number
    pub revision: Option<String>,
    /// Creation timestamp (ISO 8601)
    pub created: Option<String>,
    /// Last modification timestamp (ISO 8601)
    pub modified: Option<String>,
    /// Document category
    pub category: Option<String>,
    /// Content status (Draft, Final, etc.)
    pub content_status: Option<String>,
    /// Document language
    pub language: Option<String>,
    /// Unique identifier
    pub identifier: Option<String>,
    /// Document version
    pub version: Option<String>,
    /// Last print timestamp (ISO 8601)
    pub last_printed: Option<String>,
}

/// Configuration for security limits across extractors.
///
/// All limits are intentionally conservative to prevent DoS attacks
/// while still supporting legitimate documents.
#[frb(mirror(SecurityLimits))]
pub struct SecurityLimits {
    /// Maximum uncompressed size for archives (500 MB)
    pub max_archive_size: i64,
    /// Maximum compression ratio before flagging as potential bomb (100:1)
    pub max_compression_ratio: i64,
    /// Maximum number of files in archive (10,000)
    pub max_files_in_archive: i64,
    /// Maximum nesting depth for structures (100)
    pub max_nesting_depth: i64,
    /// Maximum length of any single XML entity / attribute / token (1 MiB).
    /// This is a per-token cap, NOT a total cap — billion-laughs class
    /// attacks where a single entity expands to hundreds of MB are caught
    /// here, while normal long text content (a paragraph, a CDATA block) is
    /// caught by `max_content_size` instead.
    pub max_entity_length: i64,
    /// Maximum string growth per document (100 MB)
    pub max_content_size: i64,
    /// Maximum iterations per operation
    pub max_iterations: i64,
    /// Maximum XML depth (100 levels)
    pub max_xml_depth: i64,
    /// Maximum cells per table (100,000)
    pub max_table_cells: i64,
}

#[frb(mirror(TokenReductionConfig))]
pub struct TokenReductionConfig {
    pub level: ReductionLevel,
    pub language_hint: Option<String>,
    pub preserve_markdown: bool,
    pub preserve_code: bool,
    pub semantic_threshold: f64,
    pub enable_parallel: bool,
    pub use_simd: bool,
    pub custom_stopwords: Option<std::collections::HashMap<String, Vec<String>>>,
    pub preserve_patterns: Vec<String>,
    pub target_reduction: Option<f64>,
    pub enable_semantic_clustering: bool,
}

/// A PDF annotation extracted from a document page.
#[frb(mirror(PdfAnnotation))]
pub struct PdfAnnotation {
    /// The type of annotation.
    pub annotation_type: PdfAnnotationType,
    /// Text content of the annotation (e.g., comment text, link URL).
    pub content: Option<String>,
    /// Page number where the annotation appears (1-indexed).
    pub page_number: i64,
    /// Bounding box of the annotation on the page.
    pub bounding_box: Option<BoundingBox>,
}

/// Comprehensive Djot document structure with semantic preservation.
///
/// This type captures the full richness of Djot markup, including:
/// - Block-level structures (headings, lists, blockquotes, code blocks, etc.)
/// - Inline formatting (emphasis, strong, highlight, subscript, superscript, etc.)
/// - Attributes (classes, IDs, key-value pairs)
/// - Links, images, footnotes
/// - Math expressions (inline and display)
/// - Tables with full structure
///
/// Available when the `djot` feature is enabled.
#[frb(mirror(DjotContent))]
pub struct DjotContent {
    /// Plain text representation for backwards compatibility
    pub plain_text: String,
    /// Structured block-level content
    pub blocks: Vec<FormattedBlock>,
    /// Metadata from YAML frontmatter
    pub metadata: Metadata,
    /// Extracted tables as structured data
    pub tables: Vec<Table>,
    /// Extracted images with metadata
    pub images: Vec<DjotImage>,
    /// Extracted links with URLs
    pub links: Vec<DjotLink>,
    /// Footnote definitions
    pub footnotes: Vec<Footnote>,
    /// Attributes mapped by element identifier (if present)
    pub attributes: Vec<String>,
}

/// Block-level element in a Djot document.
///
/// Represents structural elements like headings, paragraphs, lists, code blocks, etc.
#[frb(mirror(FormattedBlock))]
pub struct FormattedBlock {
    /// Type of block element
    pub block_type: BlockType,
    /// Heading level (1-6) for headings, or nesting level for lists
    pub level: Option<i64>,
    /// Inline content within the block
    pub inline_content: Vec<InlineElement>,
    /// Element attributes (classes, IDs, key-value pairs)
    pub attributes: Option<String>,
    /// Language identifier for code blocks
    pub language: Option<String>,
    /// Raw code content for code blocks
    pub code: Option<String>,
    /// Nested blocks for containers (blockquotes, list items, divs)
    pub children: Vec<FormattedBlock>,
}

/// Inline element within a block.
///
/// Represents text with formatting, links, images, etc.
#[frb(mirror(InlineElement))]
pub struct InlineElement {
    /// Type of inline element
    pub element_type: InlineType,
    /// Text content
    pub content: String,
    /// Element attributes
    pub attributes: Option<String>,
    /// Additional metadata (e.g., href for links, src/alt for images)
    pub metadata: Option<std::collections::HashMap<String, String>>,
}

/// Image element in Djot.
#[frb(mirror(DjotImage))]
pub struct DjotImage {
    /// Image source URL or path
    pub src: String,
    /// Alternative text
    pub alt: String,
    /// Optional title
    pub title: Option<String>,
    /// Element attributes
    pub attributes: Option<String>,
}

/// Link element in Djot.
#[frb(mirror(DjotLink))]
pub struct DjotLink {
    /// Link URL
    pub url: String,
    /// Link text content
    pub text: String,
    /// Optional title
    pub title: Option<String>,
    /// Element attributes
    pub attributes: Option<String>,
}

/// Footnote in Djot.
#[frb(mirror(Footnote))]
pub struct Footnote {
    /// Footnote label
    pub label: String,
    /// Footnote content blocks
    pub content: Vec<FormattedBlock>,
}

/// Top-level structured document representation.
///
/// A flat array of nodes with index-based parent/child references forming a tree.
/// Root-level nodes have `parent: None`. Use `body_roots()` and `furniture_roots()`
/// to iterate over top-level content by layer.
///
/// # Validation
///
/// Call `validate()` after construction to verify all node indices are in bounds
/// and parent-child relationships are bidirectionally consistent.
#[frb(mirror(DocumentStructure))]
pub struct DocumentStructure {
    /// All nodes in document/reading order.
    pub nodes: Vec<DocumentNode>,
    /// Origin format identifier (e.g. "docx", "pptx", "html", "pdf").
    ///
    /// Allows renderers to apply format-aware heuristics when converting
    /// the document tree to output formats.
    pub source_format: Option<String>,
    /// Resolved relationships between nodes (footnote refs, citations, anchor links, etc.).
    ///
    /// Populated during derivation from the internal document representation.
    /// Empty when no relationships are detected.
    pub relationships: Vec<DocumentRelationship>,
    /// Sorted, deduplicated list of node type names present in this document.
    ///
    /// Each value is the snake_case `node_type` tag of the corresponding
    /// [`NodeContent`] variant (e.g. `"paragraph"`, `"heading"`, `"table"`, …).
    ///
    /// Computed from [`nodes`] via [`DocumentStructure::finalize_node_types`].
    /// Empty until that method is called (internal construction paths call it
    /// at the end of derivation).
    pub node_types: Vec<String>,
}

/// A resolved relationship between two nodes in the document tree.
#[frb(mirror(DocumentRelationship))]
pub struct DocumentRelationship {
    /// Source node index (the referencing node).
    pub source: i64,
    /// Target node index (the referenced node).
    pub target: i64,
    /// Semantic kind of the relationship.
    pub kind: RelationshipKind,
}

/// A single node in the document tree.
///
/// Each node has deterministic `id`, typed `content`, optional `parent`/`children`
/// for tree structure, and metadata like page number, bounding box, and content layer.
#[frb(mirror(DocumentNode))]
pub struct DocumentNode {
    /// Deterministic identifier (hash of content + position).
    pub id: String,
    /// Node content — tagged enum, type-specific data only.
    pub content: NodeContent,
    /// Parent node index (`None` = root-level node).
    pub parent: Option<i64>,
    /// Child node indices in reading order.
    pub children: Vec<i64>,
    /// Content layer classification.
    pub content_layer: ContentLayer,
    /// Page number where this node starts (1-indexed).
    pub page: Option<i64>,
    /// Page number where this node ends (for multi-page tables/sections).
    pub page_end: Option<i64>,
    /// Bounding box in document coordinates.
    pub bbox: Option<BoundingBox>,
    /// Inline annotations (formatting, links) on this node's text content.
    ///
    /// Only meaningful for text-carrying nodes; empty for containers.
    pub annotations: Vec<TextAnnotation>,
    /// Format-specific key-value attributes.
    ///
    /// Extensible bag for miscellaneous data without a dedicated typed field: CSS classes,
    /// LaTeX environment names, Excel cell formulas, slide layout names, etc.
    pub attributes: Option<std::collections::HashMap<String, String>>,
}

/// Structured table grid with cell-level metadata.
///
/// Stores row/column dimensions and a flat list of cells with position info.
#[frb(mirror(TableGrid))]
pub struct TableGrid {
    /// Number of rows in the table.
    pub rows: i64,
    /// Number of columns in the table.
    pub cols: i64,
    /// All cells in row-major order.
    pub cells: Vec<GridCell>,
}

/// Individual grid cell with position and span metadata.
#[frb(mirror(GridCell))]
pub struct GridCell {
    /// Cell text content.
    pub content: String,
    /// Zero-indexed row position.
    pub row: i64,
    /// Zero-indexed column position.
    pub col: i64,
    /// Number of rows this cell spans.
    pub row_span: i64,
    /// Number of columns this cell spans.
    pub col_span: i64,
    /// Whether this is a header cell.
    pub is_header: bool,
    /// Bounding box for this cell (if available).
    pub bbox: Option<BoundingBox>,
}

/// Inline text annotation — byte-range based formatting and links.
///
/// Annotations reference byte offsets into the node's text content,
/// enabling precise identification of formatted regions.
#[frb(mirror(TextAnnotation))]
pub struct TextAnnotation {
    /// Start byte offset in the node's text content (inclusive).
    pub start: i64,
    /// End byte offset in the node's text content (exclusive).
    pub end: i64,
    /// Annotation type.
    pub kind: AnnotationKind,
}

/// General extraction result used by the core extraction API.
///
/// This is the main result type returned by all extraction functions.
#[frb(mirror(ExtractionResult))]
pub struct ExtractionResult {
    pub content: String,
    pub mime_type: String,
    pub metadata: Metadata,
    /// Extraction strategy used to produce the returned text.
    ///
    /// Populated when the extractor can reliably distinguish native text extraction,
    /// OCR-only extraction, or mixed native/OCR output.
    pub extraction_method: Option<ExtractionMethod>,
    pub tables: Vec<Table>,
    pub detected_languages: Option<Vec<String>>,
    /// Text chunks when chunking is enabled.
    ///
    /// When chunking configuration is provided, the content is split into
    /// overlapping chunks for efficient processing. Each chunk contains the text,
    /// optional embeddings (if enabled), and metadata about its position.
    pub chunks: Option<Vec<Chunk>>,
    /// Extracted images from the document.
    ///
    /// When image extraction is enabled via `ImageExtractionConfig`, this field
    /// contains all images found in the document with their raw data and metadata.
    /// Each image may optionally contain a nested `ocr_result` if OCR was performed.
    pub images: Option<Vec<ExtractedImage>>,
    /// Per-page content when page extraction is enabled.
    ///
    /// When page extraction is configured, the document is split into per-page content
    /// with tables and images mapped to their respective pages.
    pub pages: Option<Vec<PageContent>>,
    /// Semantic elements when element-based result format is enabled.
    ///
    /// When result_format is set to ElementBased, this field contains semantic
    /// elements with type classification, unique identifiers, and metadata for
    /// Unstructured-compatible element-based processing.
    pub elements: Option<Vec<Element>>,
    /// Rich Djot content structure (when extracting Djot documents).
    ///
    /// When extracting Djot documents with structured extraction enabled,
    /// this field contains the full semantic structure including:
    /// - Block-level elements with nesting
    /// - Inline formatting with attributes
    /// - Links, images, footnotes
    /// - Math expressions
    /// - Complete attribute information
    ///
    /// The `content` field still contains plain text for backward compatibility.
    ///
    /// Always `None` for non-Djot documents.
    pub djot_content: Option<DjotContent>,
    /// OCR elements with full spatial and confidence metadata.
    ///
    /// When OCR is performed with element extraction enabled, this field contains
    /// the structured representation of detected text including:
    /// - Bounding geometry (rectangles or quadrilaterals)
    /// - Confidence scores (detection and recognition)
    /// - Rotation information
    /// - Hierarchical relationships (Tesseract only)
    ///
    /// This field preserves all metadata that would otherwise be lost when
    /// converting to plain text or markdown output formats.
    ///
    /// Only populated when `OcrElementConfig.include_elements` is true.
    pub ocr_elements: Option<Vec<OcrElement>>,
    /// Structured document tree (when document structure extraction is enabled).
    ///
    /// When `include_document_structure` is true in `ExtractionConfig`, this field
    /// contains the full hierarchical representation of the document including:
    /// - Heading-driven section nesting
    /// - Table grids with cell-level metadata
    /// - Content layer classification (body, header, footer, footnote)
    /// - Inline text annotations (formatting, links)
    /// - Bounding boxes and page numbers
    ///
    /// Independent of `result_format` — can be combined with Unified or ElementBased.
    pub document: Option<DocumentStructure>,
    /// Extracted keywords when keyword extraction is enabled.
    ///
    /// When keyword extraction (RAKE or YAKE) is configured, this field contains
    /// the extracted keywords with scores, algorithm info, and position data.
    /// Previously stored in `metadata.additional["keywords"]`.
    pub extracted_keywords: Option<Vec<Keyword>>,
    /// Document quality score from quality analysis.
    ///
    /// A value between 0.0 and 1.0 indicating the overall text quality.
    /// Previously stored in `metadata.additional["quality_score"]`.
    pub quality_score: Option<f64>,
    /// Non-fatal warnings collected during processing pipeline stages.
    ///
    /// Captures errors from optional pipeline features (embedding, chunking,
    /// language detection, output formatting) that don't prevent extraction
    /// but may indicate degraded results.
    /// Previously stored as individual keys in `metadata.additional`.
    pub processing_warnings: Vec<ProcessingWarning>,
    /// PDF annotations extracted from the document.
    ///
    /// When annotation extraction is enabled via `PdfConfig::extract_annotations`,
    /// this field contains text notes, highlights, links, stamps, and other
    /// annotations found in PDF documents.
    pub annotations: Option<Vec<PdfAnnotation>>,
    /// Nested extraction results from archive contents.
    ///
    /// When extracting archives, each processable file inside produces its own
    /// full extraction result. Set to `None` for non-archive formats.
    /// Use `max_archive_depth` in config to control recursion depth.
    pub children: Option<Vec<ArchiveEntry>>,
    /// URIs/links discovered during document extraction.
    ///
    /// Contains hyperlinks, image references, citations, email addresses, and
    /// other URI-like references found in the document. Always extracted when
    /// present in the source document.
    pub uris: Option<Vec<ExtractedUri>>,
    /// Tracked changes embedded in the source document.
    ///
    /// Populated by per-format extractors that understand change-tracking
    /// metadata (DOCX `w:ins`/`w:del`/`w:rPrChange`, ODT `text:change-*`,
    /// …). Every extractor defaults to `None` until its format-specific
    /// implementation is added. Extractors that do populate this field follow
    /// the "accepted-changes" convention: inserted text is present in
    /// `content`, deleted text is absent — the revision list is the separate
    /// audit trail.
    pub revisions: Option<Vec<DocumentRevision>>,
    /// Structured extraction output from LLM-based JSON schema extraction.
    ///
    /// When `structured_extraction` is configured in `ExtractionConfig`, the
    /// extracted document content is sent to a VLM with the provided JSON schema.
    /// The response is parsed and stored here as a JSON value matching the schema.
    pub structured_output: Option<String>,
    /// Code intelligence results from tree-sitter analysis.
    ///
    /// Populated when extracting source code files with the `tree-sitter` feature.
    /// Contains metrics, structural analysis, imports/exports, comments,
    /// docstrings, symbols, diagnostics, and optionally chunked code segments.
    ///
    /// Stored as an opaque JSON value so that all language bindings (Go, Java,
    /// C#, …) can deserialize it as a raw JSON object rather than a typed struct.
    /// The underlying type is `tree_sitter_language_pack::ProcessResult`.
    pub code_intelligence: Option<String>,
    /// LLM token usage and cost data for all LLM calls made during this extraction.
    ///
    /// Contains one entry per LLM call. Multiple entries are produced when
    /// VLM OCR, structured extraction, or LLM embeddings run during
    /// the same extraction.
    ///
    /// `None` when no LLM was used.
    pub llm_usage: Option<Vec<LlmUsage>>,
    /// Pre-rendered content in the requested output format.
    ///
    /// Populated during `derive_extraction_result` before tree derivation consumes
    /// element data. `apply_output_format` swaps this into `content` at the end
    /// of the pipeline, after post-processors have operated on plain text.
    pub formatted_content: Option<String>,
    /// Structured hOCR document for the OCR+layout pipeline.
    ///
    /// When tesseract produces hOCR output, the parsed `InternalDocument` carries
    /// paragraph structure with bounding boxes and confidence scores. The layout
    /// classification step enriches these elements before final rendering.
    pub ocr_internal_document: Option<String>,
}

/// A single file extracted from an archive.
///
/// When archives (ZIP, TAR, 7Z, GZIP) are extracted with recursive extraction
/// enabled, each processable file produces its own full `ExtractionResult`.
#[frb(mirror(ArchiveEntry))]
pub struct ArchiveEntry {
    /// Archive-relative file path (e.g. "folder/document.pdf").
    pub path: String,
    /// Detected MIME type of the file.
    pub mime_type: String,
    /// Full extraction result for this file.
    pub result: ExtractionResult,
}

/// A non-fatal warning from a processing pipeline stage.
///
/// Captures errors from optional features that don't prevent extraction
/// but may indicate degraded results.
#[frb(mirror(ProcessingWarning))]
pub struct ProcessingWarning {
    /// The pipeline stage or feature that produced this warning
    /// (e.g., "embedding", "chunking", "language_detection", "output_format").
    pub source: String,
    /// Human-readable description of what went wrong.
    pub message: String,
}

/// Token usage and cost data for a single LLM call made during extraction.
///
/// Populated when VLM OCR, structured extraction, or LLM-based embeddings
/// are used. Multiple entries may be present when multiple LLM calls occur
/// within one extraction (e.g. VLM OCR + structured extraction).
#[frb(mirror(LlmUsage))]
pub struct LlmUsage {
    /// The LLM model identifier (e.g. "openai/gpt-4o", "anthropic/claude-sonnet-4-20250514").
    pub model: String,
    /// The pipeline stage that triggered this LLM call
    /// (e.g. "vlm_ocr", "structured_extraction", "embeddings").
    pub source: String,
    /// Number of input/prompt tokens consumed.
    pub input_tokens: Option<i64>,
    /// Number of output/completion tokens generated.
    pub output_tokens: Option<i64>,
    /// Total tokens (input + output).
    pub total_tokens: Option<i64>,
    /// Estimated cost in USD based on the provider's published pricing.
    pub estimated_cost: Option<f64>,
    /// Why the model stopped generating (e.g. "stop", "length", "content_filter").
    pub finish_reason: Option<String>,
}

/// A text chunk with optional embedding and metadata.
///
/// Chunks are created when chunking is enabled in `ExtractionConfig`. Each chunk
/// contains the text content, optional embedding vector (if embedding generation
/// is configured), and metadata about its position in the document.
#[frb(mirror(Chunk))]
pub struct Chunk {
    /// The text content of this chunk.
    pub content: String,
    /// Semantic structural classification of this chunk.
    ///
    /// Assigned by the heuristic classifier based on content patterns and
    /// heading context. Defaults to `ChunkType::Unknown` when no rule matches.
    pub chunk_type: ChunkType,
    /// Optional embedding vector for this chunk.
    ///
    /// Only populated when `EmbeddingConfig` is provided in chunking configuration.
    /// The dimensionality depends on the chosen embedding model.
    pub embedding: Option<Vec<f64>>,
    /// Metadata about this chunk's position and properties.
    pub metadata: ChunkMetadata,
}

/// Heading context for a chunk within a Markdown document.
///
/// Contains the heading hierarchy from document root to this chunk's section.
#[frb(mirror(HeadingContext))]
pub struct HeadingContext {
    /// The heading hierarchy from document root to this chunk's section.
    /// Index 0 is the outermost (h1), last element is the most specific.
    pub headings: Vec<HeadingLevel>,
}

/// A single heading in the hierarchy.
#[frb(mirror(HeadingLevel))]
pub struct HeadingLevel {
    /// Heading depth (1 = h1, 2 = h2, etc.)
    pub level: i64,
    /// The text content of the heading.
    pub text: String,
}

/// Metadata about a chunk's position in the original document.
#[frb(mirror(ChunkMetadata))]
pub struct ChunkMetadata {
    /// Byte offset where this chunk starts in the original text (UTF-8 valid boundary).
    pub byte_start: i64,
    /// Byte offset where this chunk ends in the original text (UTF-8 valid boundary).
    pub byte_end: i64,
    /// Number of tokens in this chunk (if available).
    ///
    /// This is calculated by the embedding model's tokenizer if embeddings are enabled.
    pub token_count: Option<i64>,
    /// Zero-based index of this chunk in the document.
    pub chunk_index: i64,
    /// Total number of chunks in the document.
    pub total_chunks: i64,
    /// First page number this chunk spans (1-indexed).
    ///
    /// Only populated when page tracking is enabled in extraction configuration.
    pub first_page: Option<i64>,
    /// Last page number this chunk spans (1-indexed, equal to first_page for single-page chunks).
    ///
    /// Only populated when page tracking is enabled in extraction configuration.
    pub last_page: Option<i64>,
    /// Heading context when using Markdown chunker.
    ///
    /// Contains the heading hierarchy this chunk falls under.
    /// Only populated when `ChunkerType::Markdown` is used.
    pub heading_context: Option<HeadingContext>,
    /// Indices into `ExtractionResult.images` for images on pages covered by this chunk.
    ///
    /// Contains zero-based indices into the top-level `images` collection for every
    /// image whose `page_number` falls within `[first_page, last_page]`.
    /// Empty when image extraction is disabled or the chunk spans no pages with images.
    pub image_indices: Vec<i64>,
}

/// Extracted image from a document.
///
/// Contains raw image data, metadata, and optional nested OCR results.
/// Raw bytes allow cross-language compatibility - users can convert to
/// PIL.Image (Python), Sharp (Node.js), or other formats as needed.
#[frb(mirror(ExtractedImage))]
pub struct ExtractedImage {
    /// Raw image data (PNG, JPEG, WebP, etc. bytes).
    /// Uses `bytes::Bytes` for cheap cloning of large buffers.
    pub data: Vec<u8>,
    /// Image format (e.g., "jpeg", "png", "webp")
    /// Uses Cow<'static, str> to avoid allocation for static literals.
    pub format: String,
    /// Zero-indexed position of this image in the document/page
    pub image_index: i64,
    /// Page/slide number where image was found (1-indexed)
    pub page_number: Option<i64>,
    /// Image width in pixels
    pub width: Option<i64>,
    /// Image height in pixels
    pub height: Option<i64>,
    /// Colorspace information (e.g., "RGB", "CMYK", "Gray")
    pub colorspace: Option<String>,
    /// Bits per color component (e.g., 8, 16)
    pub bits_per_component: Option<i64>,
    /// Whether this image is a mask image
    pub is_mask: bool,
    /// Optional description of the image
    pub description: Option<String>,
    /// Nested OCR extraction result (if image was OCRed)
    ///
    /// When OCR is performed on this image, the result is embedded here
    /// rather than in a separate collection, making the relationship explicit.
    pub ocr_result: Option<ExtractionResult>,
    /// Bounding box of the image on the page (PDF coordinates: x0=left, y0=bottom, x1=right, y1=top).
    /// Only populated for PDF-extracted images when position data is available from the PDF extractor.
    pub bounding_box: Option<BoundingBox>,
    /// Original source path of the image within the document archive (e.g., "media/image1.png" in DOCX).
    /// Used for rendering image references when the binary data is not extracted.
    pub source_path: Option<String>,
    /// Heuristic classification of what this image likely depicts.
    /// `None` if classification was disabled or inconclusive.
    pub image_kind: Option<ImageKind>,
    /// Confidence score for `image_kind`, in the range 0.0 to 1.0.
    pub kind_confidence: Option<f64>,
    /// Identifier shared across images that form a single logical figure
    /// (e.g. all raster tiles of one technical drawing). `None` for singletons.
    pub cluster_id: Option<i64>,
}

/// Bounding box coordinates for element positioning.
#[frb(mirror(BoundingBox))]
pub struct BoundingBox {
    /// Left x-coordinate
    pub x0: f64,
    /// Bottom y-coordinate
    pub y0: f64,
    /// Right x-coordinate
    pub x1: f64,
    /// Top y-coordinate
    pub y1: f64,
}

/// Metadata for a semantic element.
#[frb(mirror(ElementMetadata))]
pub struct ElementMetadata {
    /// Page number (1-indexed)
    pub page_number: Option<i64>,
    /// Source filename or document name
    pub filename: Option<String>,
    /// Bounding box coordinates if available
    pub coordinates: Option<BoundingBox>,
    /// Position index in the element sequence
    pub element_index: Option<i64>,
    /// Additional custom metadata
    pub additional: std::collections::HashMap<String, String>,
}

/// Semantic element extracted from document.
///
/// Represents a logical unit of content with semantic classification,
/// unique identifier, and metadata for tracking origin and position.
#[frb(mirror(Element))]
pub struct Element {
    /// Unique element identifier
    pub element_id: String,
    /// Semantic type of this element
    pub element_type: ElementType,
    /// Text content of the element
    pub text: String,
    /// Metadata about the element
    pub metadata: ElementMetadata,
}

/// Excel workbook representation.
///
/// Contains all sheets from an Excel file (.xlsx, .xls, etc.) with
/// extracted content and metadata.
#[frb(mirror(ExcelWorkbook))]
pub struct ExcelWorkbook {
    /// All sheets in the workbook
    pub sheets: Vec<ExcelSheet>,
    /// Workbook-level metadata (author, creation date, etc.)
    pub metadata: std::collections::HashMap<String, String>,
    /// Collaborative-edit revision headers from `xl/revisions/revisionHeaders.xml`.
    ///
    /// Populated for legacy shared-workbook `.xlsx` files that contain the
    /// `xl/revisions/` directory. Each `<header>` element maps to one
    /// `DocumentRevision { kind: FormatChange }` carrying the header's `guid`
    /// (→ `revision_id`), `userName` (→ `author`), and `dateTime` (→ `timestamp`).
    /// `anchor` and `delta` are `None`/empty for v1 (per-cell log parsing is a
    /// follow-up). `None` when `xl/revisions/revisionHeaders.xml` is absent.
    pub revisions: Option<Vec<DocumentRevision>>,
}

/// Single Excel worksheet.
///
/// Represents one sheet from an Excel workbook with its content
/// converted to Markdown format and dimensional statistics.
#[frb(mirror(ExcelSheet))]
pub struct ExcelSheet {
    /// Sheet name as it appears in Excel
    pub name: String,
    /// Sheet content converted to Markdown tables
    pub markdown: String,
    /// Number of rows
    pub row_count: i64,
    /// Number of columns
    pub col_count: i64,
    /// Total number of non-empty cells
    pub cell_count: i64,
    /// Pre-extracted table cells (2D vector of cell values)
    /// Populated during markdown generation to avoid re-parsing markdown.
    /// None for empty sheets.
    pub table_cells: Option<Vec<Vec<String>>>,
}

/// XML extraction result.
///
/// Contains extracted text content from XML files along with
/// structural statistics about the XML document.
#[frb(mirror(XmlExtractionResult))]
pub struct XmlExtractionResult {
    /// Extracted text content (XML structure filtered out)
    pub content: String,
    /// Total number of XML elements processed
    pub element_count: i64,
    /// List of unique element names found (sorted)
    pub unique_elements: Vec<String>,
}

/// Plain text and Markdown extraction result.
///
/// Contains the extracted text along with statistics and,
/// for Markdown files, structural elements like headers and links.
#[frb(mirror(TextExtractionResult))]
pub struct TextExtractionResult {
    /// Extracted text content
    pub content: String,
    /// Number of lines
    pub line_count: i64,
    /// Number of words
    pub word_count: i64,
    /// Number of characters
    pub character_count: i64,
    /// Markdown headers (text only, Markdown files only)
    pub headers: Option<Vec<String>>,
    /// Markdown links as (text, URL) tuples (Markdown files only)
    pub links: Option<Vec<Vec<String>>>,
    /// Code blocks as (language, code) tuples (Markdown files only)
    pub code_blocks: Option<Vec<Vec<String>>>,
}

/// PowerPoint (PPTX) extraction result.
///
/// Contains extracted slide content, metadata, and embedded images/tables.
#[frb(mirror(PptxExtractionResult))]
pub struct PptxExtractionResult {
    /// Extracted text content from all slides
    pub content: String,
    /// Presentation metadata
    pub metadata: PptxMetadata,
    /// Total number of slides
    pub slide_count: i64,
    /// Total number of embedded images
    pub image_count: i64,
    /// Total number of tables
    pub table_count: i64,
    /// Extracted images from the presentation
    pub images: Vec<ExtractedImage>,
    /// Slide structure with boundaries (when page tracking is enabled)
    pub page_structure: Option<PageStructure>,
    /// Per-slide content (when page tracking is enabled)
    pub page_contents: Option<Vec<PageContent>>,
    /// Structured document representation
    pub document: Option<DocumentStructure>,
    /// Hyperlinks discovered in slides as (url, optional_label) pairs.
    pub hyperlinks: Vec<String>,
    /// Office metadata extracted from docProps/core.xml and docProps/app.xml.
    ///
    /// Contains keys like "title", "author", "created_by", "subject", "keywords",
    /// "modified_by", "created_at", "modified_at", etc.
    pub office_metadata: std::collections::HashMap<String, String>,
    /// Slide comments as revisions.
    ///
    /// Each `<p:cm>` element in `ppt/comments/comment{N}.xml` becomes a
    /// `DocumentRevision { kind: Comment }` with author (resolved from
    /// `ppt/commentAuthors.xml`), ISO-8601 timestamp, and
    /// `RevisionAnchor::Slide { index }`. `None` when no comment XML parts exist.
    pub revisions: Option<Vec<DocumentRevision>>,
}

/// Email extraction result.
///
/// Complete representation of an extracted email message (.eml or .msg)
/// including headers, body content, and attachments.
#[frb(mirror(EmailExtractionResult))]
pub struct EmailExtractionResult {
    /// Email subject line
    pub subject: Option<String>,
    /// Sender email address
    pub from_email: Option<String>,
    /// Primary recipient email addresses
    pub to_emails: Vec<String>,
    /// CC recipient email addresses
    pub cc_emails: Vec<String>,
    /// BCC recipient email addresses
    pub bcc_emails: Vec<String>,
    /// Email date/timestamp
    pub date: Option<String>,
    /// Message-ID header value
    pub message_id: Option<String>,
    /// Plain text version of the email body
    pub plain_text: Option<String>,
    /// HTML version of the email body
    pub html_content: Option<String>,
    /// Cleaned/processed text content. Aliased as `cleaned_text` for back-compat.
    pub content: String,
    /// List of email attachments
    pub attachments: Vec<EmailAttachment>,
    /// Additional email headers and metadata
    pub metadata: std::collections::HashMap<String, String>,
}

/// Email attachment representation.
///
/// Contains metadata and optionally the content of an email attachment.
#[frb(mirror(EmailAttachment))]
pub struct EmailAttachment {
    /// Attachment name (from Content-Disposition header)
    pub name: Option<String>,
    /// Filename of the attachment
    pub filename: Option<String>,
    /// MIME type of the attachment
    pub mime_type: Option<String>,
    /// Size in bytes
    pub size: Option<i64>,
    /// Whether this attachment is an image
    pub is_image: bool,
    /// Attachment data (if extracted).
    /// Uses `bytes::Bytes` for cheap cloning of large buffers.
    pub data: Option<Vec<u8>>,
}

/// OCR extraction result.
///
/// Result of performing OCR on an image or scanned document,
/// including recognized text and detected tables.
#[frb(mirror(OcrExtractionResult))]
pub struct OcrExtractionResult {
    /// Recognized text content
    pub content: String,
    /// Original MIME type of the processed image
    pub mime_type: String,
    /// OCR processing metadata (confidence scores, language, etc.)
    pub metadata: std::collections::HashMap<String, String>,
    /// Tables detected and extracted via OCR
    pub tables: Vec<OcrTable>,
    /// Structured OCR elements with bounding boxes and confidence scores.
    /// Available when TSV output is requested or table detection is enabled.
    pub ocr_elements: Option<Vec<OcrElement>>,
    /// Structured document produced from hOCR parsing.
    /// Carries paragraph structure, bounding boxes, and confidence scores
    /// that the flattened `content` string discards.
    pub internal_document: Option<String>,
}

/// Table detected via OCR.
///
/// Represents a table structure recognized during OCR processing.
#[frb(mirror(OcrTable))]
pub struct OcrTable {
    /// Table cells as a 2D vector (rows × columns)
    pub cells: Vec<Vec<String>>,
    /// Markdown representation of the table
    pub markdown: String,
    /// Page number where the table was found (1-indexed)
    pub page_number: i64,
    /// Bounding box of the table in pixel coordinates (from OCR word positions).
    pub bounding_box: Option<OcrTableBoundingBox>,
}

/// Bounding box for an OCR-detected table in pixel coordinates.
#[frb(mirror(OcrTableBoundingBox))]
pub struct OcrTableBoundingBox {
    /// Left x-coordinate (pixels)
    pub left: i64,
    /// Top y-coordinate (pixels)
    pub top: i64,
    /// Right x-coordinate (pixels)
    pub right: i64,
    /// Bottom y-coordinate (pixels)
    pub bottom: i64,
}

/// Image preprocessing configuration for OCR.
///
/// These settings control how images are preprocessed before OCR to improve
/// text recognition quality. Different preprocessing strategies work better
/// for different document types.
#[frb(mirror(ImagePreprocessingConfig))]
pub struct ImagePreprocessingConfig {
    /// Target DPI for the image (300 is standard, 600 for small text).
    pub target_dpi: i64,
    /// Auto-detect and correct image rotation.
    pub auto_rotate: bool,
    /// Correct skew (tilted images).
    pub deskew: bool,
    /// Remove noise from the image.
    pub denoise: bool,
    /// Enhance contrast for better text visibility.
    pub contrast_enhance: bool,
    /// Binarization method: "otsu", "sauvola", "adaptive".
    pub binarization_method: String,
    /// Invert colors (white text on black → black on white).
    pub invert_colors: bool,
}

/// Tesseract OCR configuration.
///
/// Provides fine-grained control over Tesseract OCR engine parameters.
/// Most users can use the defaults, but these settings allow optimization
/// for specific document types (invoices, handwriting, etc.).
#[frb(mirror(TesseractConfig))]
pub struct TesseractConfig {
    /// Language code (e.g., "eng", "deu", "fra")
    pub language: String,
    /// Page Segmentation Mode (0-13).
    ///
    /// Common values:
    /// - 3: Fully automatic page segmentation (native default)
    /// - 6: Assume a single uniform block of text (WASM default — avoids layout-analysis hang)
    /// - 11: Sparse text with no particular order
    pub psm: i64,
    /// Output format ("text" or "markdown")
    pub output_format: String,
    /// OCR Engine Mode (0-3).
    ///
    /// - 0: Legacy engine only
    /// - 1: Neural nets (LSTM) only (usually best)
    /// - 2: Legacy + LSTM
    /// - 3: Default (based on what's available)
    pub oem: i64,
    /// Minimum confidence threshold (0.0-100.0).
    ///
    /// Words with confidence below this threshold may be rejected or flagged.
    pub min_confidence: f64,
    /// Image preprocessing configuration.
    ///
    /// Controls how images are preprocessed before OCR. Can significantly
    /// improve quality for scanned documents or low-quality images.
    pub preprocessing: Option<ImagePreprocessingConfig>,
    /// Enable automatic table detection and reconstruction
    pub enable_table_detection: bool,
    /// Minimum confidence threshold for table detection (0.0-1.0)
    pub table_min_confidence: f64,
    /// Column threshold for table detection (pixels)
    pub table_column_threshold: i64,
    /// Row threshold ratio for table detection (0.0-1.0)
    pub table_row_threshold_ratio: f64,
    /// Enable OCR result caching
    pub use_cache: bool,
    /// Use pre-adapted templates for character classification
    pub classify_use_pre_adapted_templates: bool,
    /// Enable N-gram language model
    pub language_model_ngram_on: bool,
    /// Don't reject good words during block-level processing
    pub tessedit_dont_blkrej_good_wds: bool,
    /// Don't reject good words during row-level processing
    pub tessedit_dont_rowrej_good_wds: bool,
    /// Enable dictionary correction
    pub tessedit_enable_dict_correction: bool,
    /// Whitelist of allowed characters (empty = all allowed)
    pub tessedit_char_whitelist: String,
    /// Blacklist of forbidden characters (empty = none forbidden)
    pub tessedit_char_blacklist: String,
    /// Use primary language params model
    pub tessedit_use_primary_params_model: bool,
    /// Variable-width space detection
    pub textord_space_size_is_variable: bool,
    /// Use adaptive thresholding method
    pub thresholding_method: bool,
}

/// Image preprocessing metadata.
///
/// Tracks the transformations applied to an image during OCR preprocessing,
/// including DPI normalization, resizing, and resampling.
#[frb(mirror(ImagePreprocessingMetadata))]
pub struct ImagePreprocessingMetadata {
    /// Original image dimensions (width, height) in pixels
    pub original_dimensions: Vec<i64>,
    /// Original image DPI (horizontal, vertical)
    pub original_dpi: Vec<f64>,
    /// Target DPI from configuration
    pub target_dpi: i64,
    /// Scaling factor applied to the image
    pub scale_factor: f64,
    /// Whether DPI was auto-adjusted based on content
    pub auto_adjusted: bool,
    /// Final DPI after processing
    pub final_dpi: i64,
    /// New dimensions after resizing (if resized)
    pub new_dimensions: Option<Vec<i64>>,
    /// Resampling algorithm used ("LANCZOS3", "CATMULLROM", etc.)
    pub resample_method: String,
    /// Whether dimensions were clamped to max_image_dimension
    pub dimension_clamped: bool,
    /// Calculated optimal DPI (if auto_adjust_dpi enabled)
    pub calculated_dpi: Option<i64>,
    /// Whether resize was skipped (dimensions already optimal)
    pub skipped_resize: bool,
    /// Error message if resize failed
    pub resize_error: Option<String>,
}

/// Extraction result metadata.
///
/// Contains common fields applicable to all formats, format-specific metadata
/// via a discriminated union, and additional custom fields from postprocessors.
#[frb(mirror(Metadata))]
pub struct Metadata {
    /// Document title
    pub title: Option<String>,
    /// Document subject or description
    pub subject: Option<String>,
    /// Primary author(s) - always Vec for consistency
    pub authors: Option<Vec<String>>,
    /// Keywords/tags - always Vec for consistency
    pub keywords: Option<Vec<String>>,
    /// Primary language (ISO 639 code)
    pub language: Option<String>,
    /// Creation timestamp (ISO 8601 format)
    pub created_at: Option<String>,
    /// Last modification timestamp (ISO 8601 format)
    pub modified_at: Option<String>,
    /// User who created the document
    pub created_by: Option<String>,
    /// User who last modified the document
    pub modified_by: Option<String>,
    /// Page/slide/sheet structure with boundaries
    pub pages: Option<PageStructure>,
    /// Format-specific metadata (discriminated union)
    ///
    /// Contains detailed metadata specific to the document format.
    /// Serialized as a nested `"format"` object with a `format_type` discriminator field.
    pub format: Option<FormatMetadata>,
    /// Image preprocessing metadata (when OCR preprocessing was applied)
    pub image_preprocessing: Option<ImagePreprocessingMetadata>,
    /// JSON schema (for structured data extraction)
    pub json_schema: Option<String>,
    /// Error metadata (for batch operations)
    pub error: Option<ErrorMetadata>,
    /// Extraction duration in milliseconds (for benchmarking).
    ///
    /// This field is populated by batch extraction to provide per-file timing
    /// information. It's `None` for single-file extraction (which uses external timing).
    pub extraction_duration_ms: Option<i64>,
    /// Document category (from frontmatter or classification).
    pub category: Option<String>,
    /// Document tags (from frontmatter).
    pub tags: Option<Vec<String>>,
    /// Document version string (from frontmatter).
    pub document_version: Option<String>,
    /// Abstract or summary text (from frontmatter).
    pub abstract_text: Option<String>,
    /// Output format identifier (e.g., "markdown", "html", "text").
    ///
    /// Set by the output format pipeline stage when format conversion is applied.
    /// Previously stored in `metadata.additional["output_format"]`.
    pub output_format: Option<String>,
    /// Whether OCR was used during extraction.
    ///
    /// Set to `true` whenever the extraction pipeline ran an OCR backend
    /// (Tesseract, PaddleOCR, VLM, etc.) and used that output as the primary
    /// or fallback text. `false` means native text extraction was used exclusively.
    pub ocr_used: bool,
    /// Additional custom fields from postprocessors.
    ///
    /// Serialized as a nested `"additional"` object (not flattened at root level).
    /// Uses `Cow<'static, str>` keys so static string keys avoid allocation.
    pub additional: std::collections::HashMap<String, String>,
}

/// Excel/spreadsheet format metadata.
///
/// Identifies the document as a spreadsheet source via the `FormatMetadata::Excel`
/// discriminant. Sheet count and sheet names are stored inside this struct.
#[frb(mirror(ExcelMetadata))]
pub struct ExcelMetadata {
    /// Number of sheets in the workbook.
    pub sheet_count: Option<i64>,
    /// Names of all sheets in the workbook.
    pub sheet_names: Option<Vec<String>>,
}

/// Email metadata extracted from .eml and .msg files.
///
/// Includes sender/recipient information, message ID, and attachment list.
#[frb(mirror(EmailMetadata))]
pub struct EmailMetadata {
    /// Sender's email address
    pub from_email: Option<String>,
    /// Sender's display name
    pub from_name: Option<String>,
    /// Primary recipients
    pub to_emails: Vec<String>,
    /// CC recipients
    pub cc_emails: Vec<String>,
    /// BCC recipients
    pub bcc_emails: Vec<String>,
    /// Message-ID header value
    pub message_id: Option<String>,
    /// List of attachment filenames
    pub attachments: Vec<String>,
}

/// Archive (ZIP/TAR/7Z) metadata.
///
/// Extracted from compressed archive files containing file lists and size information.
#[frb(mirror(ArchiveMetadata))]
pub struct ArchiveMetadata {
    /// Archive format ("ZIP", "TAR", "7Z", etc.)
    pub format: String,
    /// Total number of files in the archive
    pub file_count: i64,
    /// List of file paths within the archive
    pub file_list: Vec<String>,
    /// Total uncompressed size in bytes
    pub total_size: i64,
    /// Compressed size in bytes (if available)
    pub compressed_size: Option<i64>,
}

/// Image metadata extracted from image files.
///
/// Includes dimensions, format, and EXIF data.
#[frb(mirror(ImageMetadata))]
pub struct ImageMetadata {
    /// Image width in pixels
    pub width: i64,
    /// Image height in pixels
    pub height: i64,
    /// Image format (e.g., "PNG", "JPEG", "TIFF")
    pub format: String,
    /// EXIF metadata tags
    pub exif: std::collections::HashMap<String, String>,
}

/// XML metadata extracted during XML parsing.
///
/// Provides statistics about XML document structure.
#[frb(mirror(XmlMetadata))]
pub struct XmlMetadata {
    /// Total number of XML elements processed
    pub element_count: i64,
    /// List of unique element tag names (sorted)
    pub unique_elements: Vec<String>,
}

/// Text/Markdown metadata.
///
/// Extracted from plain text and Markdown files. Includes word counts and,
/// for Markdown, structural elements like headers and links.
#[frb(mirror(TextMetadata))]
pub struct TextMetadata {
    /// Number of lines in the document
    pub line_count: i64,
    /// Number of words
    pub word_count: i64,
    /// Number of characters
    pub character_count: i64,
    /// Markdown headers (headings text only, for Markdown files)
    pub headers: Option<Vec<String>>,
    /// Markdown links as (text, url) tuples (for Markdown files)
    pub links: Option<Vec<Vec<String>>>,
    /// Code blocks as (language, code) tuples (for Markdown files)
    pub code_blocks: Option<Vec<Vec<String>>>,
}

/// Header/heading element metadata.
#[frb(mirror(HeaderMetadata))]
pub struct HeaderMetadata {
    /// Header level: 1 (h1) through 6 (h6)
    pub level: i64,
    /// Normalized text content of the header
    pub text: String,
    /// HTML id attribute if present
    pub id: Option<String>,
    /// Document tree depth at the header element
    pub depth: i64,
    /// Byte offset in original HTML document
    pub html_offset: i64,
}

/// Link element metadata.
#[frb(mirror(LinkMetadata))]
pub struct LinkMetadata {
    /// The href URL value
    pub href: String,
    /// Link text content (normalized)
    pub text: String,
    /// Optional title attribute
    pub title: Option<String>,
    /// Link type classification
    pub link_type: LinkType,
    /// Rel attribute values
    pub rel: Vec<String>,
    /// Additional attributes as key-value pairs
    pub attributes: Vec<Vec<String>>,
}

/// Image element metadata.
#[frb(mirror(ImageMetadataType))]
pub struct ImageMetadataType {
    /// Image source (URL, data URI, or SVG content)
    pub src: String,
    /// Alternative text from alt attribute
    pub alt: Option<String>,
    /// Title attribute
    pub title: Option<String>,
    /// Image dimensions as (width, height) if available
    pub dimensions: Option<Vec<i64>>,
    /// Image type classification
    pub image_type: ImageType,
    /// Additional attributes as key-value pairs
    pub attributes: Vec<Vec<String>>,
}

/// Structured data (Schema.org, microdata, RDFa) block.
#[frb(mirror(StructuredData))]
pub struct StructuredData {
    /// Type of structured data
    pub data_type: StructuredDataType,
    /// Raw JSON string representation
    pub raw_json: String,
    /// Schema type if detectable (e.g., "Article", "Event", "Product")
    pub schema_type: Option<String>,
}

/// HTML metadata extracted from HTML documents.
///
/// Includes document-level metadata, Open Graph data, Twitter Card metadata,
/// and extracted structural elements (headers, links, images, structured data).
#[frb(mirror(HtmlMetadata))]
pub struct HtmlMetadata {
    /// Document title from `<title>` tag
    pub title: Option<String>,
    /// Document description from `<meta name="description">` tag
    pub description: Option<String>,
    /// Document keywords from `<meta name="keywords">` tag, split on commas
    pub keywords: Vec<String>,
    /// Document author from `<meta name="author">` tag
    pub author: Option<String>,
    /// Canonical URL from `<link rel="canonical">` tag
    pub canonical_url: Option<String>,
    /// Base URL from `<base href="">` tag for resolving relative URLs
    pub base_href: Option<String>,
    /// Document language from `lang` attribute
    pub language: Option<String>,
    /// Document text direction from `dir` attribute
    pub text_direction: Option<TextDirection>,
    /// Open Graph metadata (og:* properties) for social media
    /// Keys like "title", "description", "image", "url", etc.
    pub open_graph: std::collections::HashMap<String, String>,
    /// Twitter Card metadata (twitter:* properties)
    /// Keys like "card", "site", "creator", "title", "description", "image", etc.
    pub twitter_card: std::collections::HashMap<String, String>,
    /// Additional meta tags not covered by specific fields
    /// Keys are meta name/property attributes, values are content
    pub meta_tags: std::collections::HashMap<String, String>,
    /// Extracted header elements with hierarchy
    pub headers: Vec<HeaderMetadata>,
    /// Extracted hyperlinks with type classification
    pub links: Vec<LinkMetadata>,
    /// Extracted images with source and dimensions
    pub images: Vec<ImageMetadataType>,
    /// Extracted structured data blocks
    pub structured_data: Vec<StructuredData>,
}

/// OCR processing metadata.
///
/// Captures information about OCR processing configuration and results.
#[frb(mirror(OcrMetadata))]
pub struct OcrMetadata {
    /// OCR language code(s) used
    pub language: String,
    /// Tesseract Page Segmentation Mode (PSM)
    pub psm: i64,
    /// Output format (e.g., "text", "hocr")
    pub output_format: String,
    /// Number of tables detected
    pub table_count: i64,
    pub table_rows: Option<i64>,
    pub table_cols: Option<i64>,
}

/// Error metadata (for batch operations).
#[frb(mirror(ErrorMetadata))]
pub struct ErrorMetadata {
    pub error_type: String,
    pub message: String,
}

/// PowerPoint presentation metadata.
///
/// Extracted from PPTX files containing slide counts and presentation details.
#[frb(mirror(PptxMetadata))]
pub struct PptxMetadata {
    /// Total number of slides in the presentation
    pub slide_count: i64,
    /// Names of slides (if available)
    pub slide_names: Vec<String>,
    /// Number of embedded images
    pub image_count: Option<i64>,
    /// Number of tables
    pub table_count: Option<i64>,
}

/// Word document metadata.
///
/// Extracted from DOCX files using shared Office Open XML metadata extraction.
/// Integrates with `office_metadata` module for core/app/custom properties.
#[frb(mirror(DocxMetadata))]
pub struct DocxMetadata {
    /// Core properties from docProps/core.xml (Dublin Core metadata)
    ///
    /// Contains title, creator, subject, keywords, dates, etc.
    /// Shared format across DOCX/PPTX/XLSX documents.
    pub core_properties: Option<CoreProperties>,
    /// Application properties from docProps/app.xml (Word-specific statistics)
    ///
    /// Contains word count, page count, paragraph count, editing time, etc.
    /// DOCX-specific variant of Office application properties.
    pub app_properties: Option<DocxAppProperties>,
    /// Custom properties from docProps/custom.xml (user-defined properties)
    ///
    /// Contains key-value pairs defined by users or applications.
    /// Values can be strings, numbers, booleans, or dates.
    pub custom_properties: Option<std::collections::HashMap<String, String>>,
}

/// CSV/TSV file metadata.
#[frb(mirror(CsvMetadata))]
pub struct CsvMetadata {
    pub row_count: i64,
    pub column_count: i64,
    pub delimiter: Option<String>,
    pub has_header: bool,
    pub column_types: Option<Vec<String>>,
}

/// BibTeX bibliography metadata.
#[frb(mirror(BibtexMetadata))]
pub struct BibtexMetadata {
    /// Number of entries in the bibliography.
    pub entry_count: i64,
    pub citation_keys: Vec<String>,
    pub authors: Vec<String>,
    pub year_range: Option<YearRange>,
    pub entry_types: Option<std::collections::HashMap<String, i64>>,
}

/// Citation file metadata (RIS, PubMed, EndNote).
#[frb(mirror(CitationMetadata))]
pub struct CitationMetadata {
    pub citation_count: i64,
    pub format: Option<String>,
    pub authors: Vec<String>,
    pub year_range: Option<YearRange>,
    pub dois: Vec<String>,
    pub keywords: Vec<String>,
}

/// Year range for bibliographic metadata.
#[frb(mirror(YearRange))]
pub struct YearRange {
    pub min: Option<i64>,
    pub max: Option<i64>,
    pub years: Vec<i64>,
}

/// FictionBook (FB2) metadata.
#[frb(mirror(FictionBookMetadata))]
pub struct FictionBookMetadata {
    pub genres: Vec<String>,
    pub sequences: Vec<String>,
    pub annotation: Option<String>,
}

/// dBASE (DBF) file metadata.
#[frb(mirror(DbfMetadata))]
pub struct DbfMetadata {
    pub record_count: i64,
    pub field_count: i64,
    pub fields: Vec<DbfFieldInfo>,
}

/// dBASE field information.
#[frb(mirror(DbfFieldInfo))]
pub struct DbfFieldInfo {
    pub name: String,
    pub field_type: String,
}

/// JATS (Journal Article Tag Suite) metadata.
#[frb(mirror(JatsMetadata))]
pub struct JatsMetadata {
    pub copyright: Option<String>,
    pub license: Option<String>,
    pub history_dates: std::collections::HashMap<String, String>,
    pub contributor_roles: Vec<ContributorRole>,
}

/// JATS contributor with role.
#[frb(mirror(ContributorRole))]
pub struct ContributorRole {
    pub name: String,
    pub role: Option<String>,
}

/// EPUB metadata (Dublin Core extensions).
#[frb(mirror(EpubMetadata))]
pub struct EpubMetadata {
    pub coverage: Option<String>,
    pub dc_format: Option<String>,
    pub relation: Option<String>,
    pub source: Option<String>,
    pub dc_type: Option<String>,
    pub cover_image: Option<String>,
}

/// Outlook PST archive metadata.
#[frb(mirror(PstMetadata))]
pub struct PstMetadata {
    pub message_count: i64,
}

/// Confidence scores for an OCR element.
///
/// Separates detection confidence (how confident that text exists at this location)
/// from recognition confidence (how confident about the actual text content).
#[frb(mirror(OcrConfidence))]
pub struct OcrConfidence {
    /// Detection confidence: how confident the OCR engine is that text exists here.
    ///
    /// PaddleOCR provides this as `box_score`, Tesseract doesn't have a direct equivalent.
    /// Range: 0.0 to 1.0 (or None if not available).
    pub detection: Option<f64>,
    /// Recognition confidence: how confident about the text content.
    ///
    /// Range: 0.0 to 1.0.
    pub recognition: f64,
}

/// Rotation information for an OCR element.
#[frb(mirror(OcrRotation))]
pub struct OcrRotation {
    /// Rotation angle in degrees (0, 90, 180, 270 for PaddleOCR).
    pub angle_degrees: f64,
    /// Confidence score for the rotation detection.
    pub confidence: Option<f64>,
}

/// A unified OCR element representing detected text with full metadata.
///
/// This is the primary type for structured OCR output, preserving all information
/// from both Tesseract and PaddleOCR backends.
#[frb(mirror(OcrElement))]
pub struct OcrElement {
    /// The recognized text content.
    pub text: String,
    /// Bounding geometry (rectangle or quadrilateral).
    pub geometry: OcrBoundingGeometry,
    /// Confidence scores for detection and recognition.
    pub confidence: OcrConfidence,
    /// Hierarchical level (word, line, block, page).
    pub level: OcrElementLevel,
    /// Rotation information (if detected).
    pub rotation: Option<OcrRotation>,
    /// Page number (1-indexed).
    pub page_number: i64,
    /// Parent element ID for hierarchical relationships.
    ///
    /// Only used for Tesseract output which has word -> line -> block hierarchy.
    pub parent_id: Option<String>,
    /// Backend-specific metadata that doesn't fit the unified schema.
    pub backend_metadata: std::collections::HashMap<String, String>,
}

/// Configuration for OCR element extraction.
///
/// Controls how OCR elements are extracted and filtered.
#[frb(mirror(OcrElementConfig))]
pub struct OcrElementConfig {
    /// Whether to include OCR elements in the extraction result.
    ///
    /// When true, the `ocr_elements` field in `ExtractionResult` will be populated.
    pub include_elements: bool,
    /// Minimum hierarchical level to include.
    ///
    /// Elements below this level (e.g., words when min_level is Line) will be excluded.
    pub min_level: OcrElementLevel,
    /// Minimum recognition confidence threshold (0.0-1.0).
    ///
    /// Elements with confidence below this threshold will be filtered out.
    pub min_confidence: f64,
    /// Whether to build hierarchical relationships between elements.
    ///
    /// When true, `parent_id` fields will be populated based on spatial containment.
    /// Only meaningful for Tesseract output.
    pub build_hierarchy: bool,
}

/// Unified page structure for documents.
///
/// Supports different page types (PDF pages, PPTX slides, Excel sheets)
/// with character offset boundaries for chunk-to-page mapping.
#[frb(mirror(PageStructure))]
pub struct PageStructure {
    /// Total number of pages/slides/sheets
    pub total_count: i64,
    /// Type of paginated unit
    pub unit_type: PageUnitType,
    /// Character offset boundaries for each page
    ///
    /// Maps character ranges in the extracted content to page numbers.
    /// Used for chunk page range calculation.
    pub boundaries: Option<Vec<PageBoundary>>,
    /// Detailed per-page metadata (optional, only when needed)
    pub pages: Option<Vec<PageInfo>>,
}

/// Byte offset boundary for a page.
///
/// Tracks where a specific page's content starts and ends in the main content string,
/// enabling mapping from byte positions to page numbers. Offsets are guaranteed to be
/// at valid UTF-8 character boundaries when using standard String methods (push_str, push, etc.).
#[frb(mirror(PageBoundary))]
pub struct PageBoundary {
    /// Byte offset where this page starts in the content string (UTF-8 valid boundary, inclusive)
    pub byte_start: i64,
    /// Byte offset where this page ends in the content string (UTF-8 valid boundary, exclusive)
    pub byte_end: i64,
    /// Page number (1-indexed)
    pub page_number: i64,
}

/// Metadata for individual page/slide/sheet.
///
/// Captures per-page information including dimensions, content counts,
/// and visibility state (for presentations).
#[frb(mirror(PageInfo))]
pub struct PageInfo {
    /// Page number (1-indexed)
    pub number: i64,
    /// Page title (usually for presentations)
    pub title: Option<String>,
    /// Dimensions in points (PDF) or pixels (images): (width, height)
    pub dimensions: Option<Vec<f64>>,
    /// Number of images on this page
    pub image_count: Option<i64>,
    /// Number of tables on this page
    pub table_count: Option<i64>,
    /// Whether this page is hidden (e.g., in presentations)
    pub hidden: Option<bool>,
    /// Whether this page is blank (no meaningful text, no images, no tables)
    ///
    /// A page is considered blank if it has fewer than 3 non-whitespace characters
    /// and contains no tables or images. This is useful for filtering out empty pages
    /// in scanned documents or PDFs with blank separator pages.
    pub is_blank: Option<bool>,
    /// Whether this page contains non-trivial vector graphics (paths, shapes, curves)
    ///
    /// Indicates the presence of vector-drawn content such as charts, diagrams,
    /// or geometric shapes (e.g., from Adobe InDesign, LaTeX TikZ). These are
    /// invisible to `ExtractionResult.images` since they are not embedded as raster
    /// XObjects. Set to `true` when path count exceeds a heuristic threshold,
    /// signaling that downstream consumers may want to rasterize the page to
    /// capture this content.
    ///
    /// Only populated for PDFs; `None` for other document types.
    pub has_vector_graphics: bool,
}

/// Content for a single page/slide.
///
/// When page extraction is enabled, documents are split into per-page content
/// with associated tables and images mapped to each page.
///
/// # Performance
///
/// Uses Arc-wrapped tables and images for memory efficiency:
/// - `Vec<Arc<Table>>` enables zero-copy sharing of table data
/// - `Vec<Arc<ExtractedImage>>` enables zero-copy sharing of image data
/// - Maintains exact JSON compatibility via custom Serialize/Deserialize
///
/// This reduces memory overhead for documents with shared tables/images
/// by avoiding redundant copies during serialization.
#[frb(mirror(PageContent))]
pub struct PageContent {
    /// Page number (1-indexed)
    pub page_number: i64,
    /// Text content for this page
    pub content: String,
    /// Tables found on this page (uses Arc for memory efficiency)
    ///
    /// Serializes as Vec<Table> for JSON compatibility while maintaining
    /// Arc semantics in-memory for zero-copy sharing.
    pub tables: Vec<Table>,
    /// Indices into `ExtractionResult.images` for images found on this page.
    ///
    /// Each value is a zero-based index into the top-level `images` collection.
    /// Only populated when `extract_images = true` in the extraction config.
    pub image_indices: Vec<i64>,
    /// Hierarchy information for the page (when hierarchy extraction is enabled)
    ///
    /// Contains text hierarchy levels (H1-H6) extracted from the page content.
    pub hierarchy: Option<PageHierarchy>,
    /// Whether this page is blank (no meaningful text content)
    ///
    /// Determined during extraction based on text content analysis.
    /// A page is blank if it has fewer than 3 non-whitespace characters
    /// and contains no tables or images.
    pub is_blank: Option<bool>,
    /// Layout detection regions for this page (when layout detection is enabled).
    ///
    /// Contains detected layout regions with class, confidence, bounding box,
    /// and area fraction. Only populated when layout detection is configured.
    pub layout_regions: Option<Vec<LayoutRegion>>,
    /// Speaker notes for this slide (PPTX only).
    ///
    /// Contains the text from the slide's notes pane (`ppt/notesSlides/notesSlide{N}.xml`).
    /// Only populated when the source is a PPTX file and notes are present.
    pub speaker_notes: Option<String>,
    /// Section name this slide belongs to (PPTX only).
    ///
    /// PowerPoint sections group slides into logical chapters (`<p:sectionLst>` in
    /// `ppt/presentation.xml`). Only populated when the source is a PPTX file and
    /// the slide belongs to a named section.
    pub section_name: Option<String>,
    /// Sheet name for this page (XLSX/ODS only).
    ///
    /// Each spreadsheet sheet maps to one `PageContent` entry. This field carries the
    /// sheet's display name as it appears in the workbook. `None` for all non-spreadsheet
    /// formats and for sheets with an empty name.
    pub sheet_name: Option<String>,
}

/// A detected layout region on a page.
///
/// When layout detection is enabled, each page may have layout regions
/// identifying different content types (text, pictures, tables, etc.)
/// with confidence scores and spatial positions.
#[frb(mirror(LayoutRegion))]
pub struct LayoutRegion {
    /// Layout class name (e.g. "picture", "table", "text", "section_header").
    pub class_name: String,
    /// Confidence score from the layout detection model (0.0 to 1.0).
    pub confidence: f64,
    /// Bounding box in document coordinate space.
    pub bounding_box: BoundingBox,
    /// Fraction of the page area covered by this region (0.0 to 1.0).
    pub area_fraction: f64,
}

/// Page hierarchy structure containing heading levels and block information.
///
/// Used when PDF text hierarchy extraction is enabled. Contains hierarchical
/// blocks with heading levels (H1-H6) for semantic document structure.
#[frb(mirror(PageHierarchy))]
pub struct PageHierarchy {
    /// Number of hierarchy blocks on this page
    pub block_count: i64,
    /// Hierarchical blocks with heading levels
    pub blocks: Vec<HierarchicalBlock>,
}

/// A text block with hierarchy level assignment.
///
/// Represents a block of text with semantic heading information extracted from
/// font size clustering and hierarchical analysis.
#[frb(mirror(HierarchicalBlock))]
pub struct HierarchicalBlock {
    /// The text content of this block
    pub text: String,
    /// The font size of the text in this block
    pub font_size: f64,
    /// The hierarchy level of this block (H1-H6 or Body)
    ///
    /// Levels correspond to HTML heading tags:
    /// - "h1": Top-level heading
    /// - "h2": Secondary heading
    /// - "h3": Tertiary heading
    /// - "h4": Quaternary heading
    /// - "h5": Quinary heading
    /// - "h6": Senary heading
    /// - "body": Body text (no heading level)
    pub level: String,
    /// Bounding box information for the block
    ///
    /// Contains coordinates as (left, top, right, bottom) in PDF units.
    pub bbox: Option<Vec<f64>>,
}

/// A single changed cell within a table.
///
/// Defined here (rather than only in `crate::diff`) so `RevisionDelta` can
/// reference it unconditionally, without requiring the `diff` Cargo feature.
/// `crate::diff` re-exports this type verbatim.
#[frb(mirror(CellChange))]
pub struct CellChange {
    /// Zero-based row index.
    pub row: i64,
    /// Zero-based column index.
    pub col: i64,
    /// Value before the change.
    pub from: String,
    /// Value after the change.
    pub to: String,
}

/// A single tracked change embedded in a document.
///
/// Populated by per-format extractors that understand change-tracking metadata
/// (DOCX `w:ins`/`w:del`/`w:rPrChange`, ODT `text:change-*`, …). Every
/// extractor defaults to `ExtractionResult.revisions = None` until a
/// format-specific implementation is added.
#[frb(mirror(DocumentRevision))]
pub struct DocumentRevision {
    /// Format-specific revision identifier.
    ///
    /// For DOCX this is the `w:id` attribute value on the change element
    /// (e.g. `"42"`). When the attribute is absent a synthetic fallback is
    /// generated (`"docx-ins-0"`, `"docx-del-3"`, …).
    pub revision_id: String,
    /// Display name of the author who made this change, when available.
    pub author: Option<String>,
    /// ISO-8601 timestamp of the change, when available.
    ///
    /// Stored as a plain string so this type remains FFI-friendly and
    /// unconditionally available without the `chrono` optional dep.
    /// DOCX populates this from the `w:date` attribute (e.g.
    /// `"2024-03-15T10:30:00Z"`).
    pub timestamp: Option<String>,
    /// Semantic kind of this revision.
    pub kind: RevisionKind,
    /// Best-effort document location for this revision.
    ///
    /// Resolution is format-dependent and may be `None` when the location
    /// cannot be determined (e.g. changes inside table cells before
    /// table-cell anchor support is added).
    pub anchor: Option<RevisionAnchor>,
    /// The content changes that make up this revision.
    pub delta: RevisionDelta,
}

/// The content changes that make up a single revision.
///
/// For insertions and deletions the `content` field carries the added/removed
/// lines as `DiffLine::Added` / `DiffLine::Removed` entries. For format
/// changes, `content` is empty — the property diff is left as a TODO for a
/// later enrichment pass.
#[frb(mirror(RevisionDelta))]
pub struct RevisionDelta {
    /// Line-level content changes for this revision.
    pub content: Vec<DiffLine>,
    /// Cell-level table changes for this revision.
    pub table_changes: Vec<CellChange>,
}

/// Extracted table structure.
///
/// Represents a table detected and extracted from a document (PDF, image, etc.).
/// Tables are converted to both structured cell data and Markdown format.
#[frb(mirror(Table))]
pub struct Table {
    /// Table cells as a 2D vector (rows × columns)
    pub cells: Vec<Vec<String>>,
    /// Markdown representation of the table
    pub markdown: String,
    /// Page number where the table was found (1-indexed)
    pub page_number: i64,
    /// Bounding box of the table on the page (PDF coordinates: x0=left, y0=bottom, x1=right, y1=top).
    /// Only populated for PDF-extracted tables when position data is available.
    pub bounding_box: Option<BoundingBox>,
}

/// Individual table cell with content and optional styling.
///
/// Future extension point for rich table support with cell-level metadata.
#[frb(mirror(TableCell))]
pub struct TableCell {
    /// Cell content as text
    pub content: String,
    /// Row span (number of rows this cell spans)
    pub row_span: i64,
    /// Column span (number of columns this cell spans)
    pub col_span: i64,
    /// Whether this is a header cell
    pub is_header: bool,
}

/// A URI extracted from a document.
///
/// Represents any link, reference, or resource pointer found during extraction.
/// The `kind` field classifies the URI semantically, while `label` carries
/// optional human-readable display text.
#[frb(mirror(ExtractedUri))]
pub struct ExtractedUri {
    /// The URL or path string.
    pub url: String,
    /// Optional display text / label for the link.
    pub label: Option<String>,
    /// Optional page number where the URI was found (1-indexed).
    pub page: Option<i64>,
    /// Semantic classification of the URI.
    pub kind: UriKind,
}

/// MIME type detection response.
#[frb(mirror(DetectResponse))]
pub struct DetectResponse {
    /// Detected MIME type
    pub mime_type: String,
    /// Original filename (if provided)
    pub filename: Option<String>,
}

/// Options controlling how two `ExtractionResult` values are compared.
#[frb(mirror(DiffOptions))]
pub struct DiffOptions {
    /// Include metadata changes in the diff. Default: `true`.
    pub include_metadata: bool,
    /// Include embedded-children changes in the diff. Default: `true`.
    pub include_embedded: bool,
    /// Truncate content to this many characters before diffing.
    ///
    /// Useful for very large documents where only the first N characters matter.
    /// `None` means no truncation.
    pub max_content_chars: Option<i64>,
}

/// The complete diff between two `ExtractionResult` values.
#[frb(mirror(ExtractionDiff))]
pub struct ExtractionDiff {
    /// Unified-diff hunks for the `content` field.
    ///
    /// Empty when the content is identical.
    pub content_diff: Vec<DiffHunk>,
    /// Tables present in `b` but not in `a` (by index position, excess right-side tables).
    pub tables_added: Vec<Table>,
    /// Tables present in `a` but not in `b` (by index position, excess left-side tables).
    pub tables_removed: Vec<Table>,
    /// Cell-level changes for table pairs that share the same index and dimensions.
    pub tables_changed: Vec<TableDiff>,
    /// Metadata difference, encoded as a JSON object with three top-level keys:
    /// `added` (keys present in `b` but not `a`), `removed` (keys present in `a`
    /// but not `b`), and `changed` (keys whose values differ — each entry is
    /// `{ "from": <value-in-a>, "to": <value-in-b> }`).
    ///
    /// This is NOT RFC 6902 JSON Patch — we deliberately chose a flatter shape
    /// to avoid pulling in a json-patch crate. If you need RFC 6902 semantics
    /// (with JSON Pointer paths) feed `a.metadata` and `b.metadata` to your
    /// preferred json-patch impl directly.
    pub metadata_changed: String,
    /// Changes to embedded archive children.
    pub embedded_changes: EmbeddedChanges,
}

/// A single contiguous hunk in a unified diff.
#[frb(mirror(DiffHunk))]
pub struct DiffHunk {
    /// Starting line number in the old content (0-indexed).
    pub from_line: i64,
    /// Number of lines from the old content in this hunk.
    pub from_count: i64,
    /// Starting line number in the new content (0-indexed).
    pub to_line: i64,
    /// Number of lines from the new content in this hunk.
    pub to_count: i64,
    /// Lines that make up this hunk.
    pub lines: Vec<DiffLine>,
}

/// Cell-level changes for a pair of tables that share the same index.
#[frb(mirror(TableDiff))]
pub struct TableDiff {
    /// Zero-based index of the table in both `a.tables` and `b.tables`.
    pub from_index: i64,
    /// Zero-based index in `b.tables` (equal to `from_index` for same-dimension tables).
    pub to_index: i64,
    /// Cell-level changes within the table.
    pub cell_changes: Vec<CellChange>,
}

/// Changes to embedded archive children between two results.
#[frb(mirror(EmbeddedChanges))]
pub struct EmbeddedChanges {
    /// Children present in `b` but not in `a` (matched by `path`).
    pub added: Vec<ArchiveEntry>,
    /// Children present in `a` but not in `b` (matched by `path`).
    pub removed: Vec<ArchiveEntry>,
    /// Children present in both but with differing content (matched by `path`).
    ///
    /// Each entry holds the diff of the nested `ExtractionResult`.
    pub changed: Vec<EmbeddedDiff>,
}

/// Diff for a single embedded archive entry that appears in both results.
#[frb(mirror(EmbeddedDiff))]
pub struct EmbeddedDiff {
    /// Archive-relative path identifying this entry.
    pub path: String,
    /// The recursive diff of the entry's extraction result.
    pub diff: ExtractionDiff,
}

/// Preset configurations for common RAG use cases.
///
/// Each preset combines chunk size, overlap, and embedding model
/// to provide an optimized configuration for specific scenarios.
///
/// All string fields are owned `String` for FFI compatibility — instances
/// are safe to clone and pass across language boundaries.
#[frb(mirror(EmbeddingPreset))]
pub struct EmbeddingPreset {
    pub name: String,
    pub chunk_size: i64,
    pub overlap: i64,
    /// HuggingFace repository name for the model.
    pub model_repo: String,
    /// Pooling strategy: "cls" or "mean".
    pub pooling: String,
    /// Path to the ONNX model file within the repo.
    pub model_file: String,
    pub dimensions: i64,
    pub description: String,
}

/// YAKE-specific parameters.
#[frb(mirror(YakeParams))]
pub struct YakeParams {
    /// Window size for co-occurrence analysis (default: 2).
    ///
    /// Controls the context window for computing co-occurrence statistics.
    pub window_size: i64,
}

/// RAKE-specific parameters.
#[frb(mirror(RakeParams))]
pub struct RakeParams {
    /// Minimum word length to consider (default: 1).
    pub min_word_length: i64,
    /// Maximum words in a keyword phrase (default: 3).
    pub max_words_per_phrase: i64,
}

/// Keyword extraction configuration.
#[frb(mirror(KeywordConfig))]
pub struct KeywordConfig {
    /// Algorithm to use for extraction.
    pub algorithm: KeywordAlgorithm,
    /// Maximum number of keywords to extract (default: 10).
    pub max_keywords: i64,
    /// Minimum score threshold (0.0-1.0, default: 0.0).
    ///
    /// Keywords with scores below this threshold are filtered out.
    /// Note: Score ranges differ between algorithms.
    pub min_score: f64,
    /// N-gram range for keyword extraction (min, max).
    ///
    /// (1, 1) = unigrams only
    /// (1, 2) = unigrams and bigrams
    /// (1, 3) = unigrams, bigrams, and trigrams (default)
    pub ngram_range: Vec<i64>,
    /// Language code for stopword filtering (e.g., "en", "de", "fr").
    ///
    /// If None, no stopword filtering is applied.
    pub language: Option<String>,
    /// YAKE-specific tuning parameters.
    pub yake_params: Option<YakeParams>,
    /// RAKE-specific tuning parameters.
    pub rake_params: Option<RakeParams>,
}

/// Extracted keyword with metadata.
#[frb(mirror(Keyword))]
pub struct Keyword {
    /// The keyword text.
    pub text: String,
    /// Relevance score (higher is better, algorithm-specific range).
    pub score: f64,
    /// Algorithm that extracted this keyword.
    pub algorithm: KeywordAlgorithm,
    /// Optional positions where keyword appears in text (character offsets).
    pub positions: Option<Vec<i64>>,
}

/// Configuration for PaddleOCR backend.
///
/// Configures PaddleOCR text detection and recognition with multi-language support.
/// Uses a builder pattern for convenient configuration.
///
/// # Examples
///
/// ```no_run
/// use kreuzberg::PaddleOcrConfig;
///
/// // Create with default English configuration
/// let config = PaddleOcrConfig::new("en");
///
/// // Create with custom cache directory
/// let config = PaddleOcrConfig::new("ch")
///     .with_cache_dir("/path/to/cache".into());
///
/// // Enable table detection
/// let config = PaddleOcrConfig::new("en")
///     .with_table_detection(true);
/// ```
#[frb(mirror(PaddleOcrConfig))]
pub struct PaddleOcrConfig {
    /// Language code (e.g., "en", "ch", "jpn", "kor", "deu", "fra")
    pub language: String,
    /// Optional custom cache directory for model files
    pub cache_dir: Option<String>,
    /// Enable angle classification for rotated text (default: false).
    /// Can misfire on short text regions, rotating crops incorrectly before recognition.
    pub use_angle_cls: bool,
    /// Enable table structure detection (default: false)
    pub enable_table_detection: bool,
    /// Database threshold for text detection (default: 0.3)
    /// Range: 0.0-1.0, higher values require more confident detections
    pub det_db_thresh: f64,
    /// Box threshold for text bounding box refinement (default: 0.5)
    /// Range: 0.0-1.0
    pub det_db_box_thresh: f64,
    /// Unclip ratio for expanding text bounding boxes (default: 1.6)
    /// Controls the expansion of detected text regions
    pub det_db_unclip_ratio: f64,
    /// Maximum side length for detection image (default: 960)
    /// Larger images may be resized to this limit for faster inference
    pub det_limit_side_len: i64,
    /// Batch size for recognition inference (default: 6)
    /// Number of text regions to process simultaneously
    pub rec_batch_num: i64,
    /// Padding in pixels added around the image before detection (default: 10).
    /// Large values can include surrounding content like table gridlines.
    pub padding: i64,
    /// Minimum recognition confidence score for text lines (default: 0.5).
    /// Text regions with recognition confidence below this threshold are discarded.
    /// Matches PaddleOCR Python's `drop_score` parameter.
    /// Range: 0.0-1.0
    pub drop_score: f64,
    /// Model tier controlling detection/recognition model size and accuracy trade-off.
    /// - `"mobile"` (default): Lightweight models (~4.5MB detection, ~16.5MB recognition), fast download and inference
    /// - `"server"`: Large, high-accuracy models (~88MB detection, ~84MB recognition), best for GPU or complex documents
    pub model_tier: String,
}

/// Combined paths to all models needed for OCR (backward compatibility).
#[frb(mirror(ModelPaths))]
pub struct ModelPaths {
    /// Path to the detection model directory.
    pub det_model: String,
    /// Path to the classification model directory.
    pub cls_model: String,
    /// Path to the recognition model directory.
    pub rec_model: String,
    /// Path to the character dictionary file.
    pub dict_file: String,
}

/// Document orientation detection result.
#[frb(mirror(OrientationResult))]
pub struct OrientationResult {
    /// Detected orientation in degrees (0, 90, 180, or 270).
    pub degrees: i64,
    /// Confidence score (0.0-1.0).
    pub confidence: f64,
}

/// Bounding box in original image coordinates (x1, y1) top-left, (x2, y2) bottom-right.
#[frb(mirror(BBox))]
pub struct BBox {
    pub x1: f64,
    pub y1: f64,
    pub x2: f64,
    pub y2: f64,
}

/// A single layout detection result.
#[frb(mirror(LayoutDetection))]
pub struct LayoutDetection {
    pub class_name: LayoutClass,
    pub confidence: f64,
    pub bbox: BBox,
}

/// Pre-computed table markdown for a table detection region.
///
/// Produced by the TATR-based table structure recognizer and surfaced as part of
/// layout-aware OCR results.  The struct lives here (under `layout-types`, pure-Rust)
/// so that consumers who do not enable `layout-detection` (ORT) can still reference
/// the type in their own code.
#[frb(mirror(RecognizedTable))]
pub struct RecognizedTable {
    /// Detection bbox that this table corresponds to (for matching).
    pub detection_bbox: BBox,
    /// Table cells as a 2D vector (rows × columns).
    pub cells: Vec<Vec<String>>,
    /// Rendered markdown table.
    pub markdown: String,
}

/// Page-level detection result containing all detections and page metadata.
#[frb(mirror(DetectionResult))]
pub struct DetectionResult {
    pub page_width: i64,
    pub page_height: i64,
    pub detections: Vec<LayoutDetection>,
}

/// Embedded file descriptor extracted from the PDF name tree.
#[frb(mirror(EmbeddedFile))]
pub struct EmbeddedFile {
    /// The filename as stored in the PDF name tree.
    pub name: String,
    /// Raw file bytes from the embedded stream (already decompressed by lopdf).
    pub data: Vec<u8>,
    /// Compressed byte count of the original stream (before decompression).
    ///
    /// Used by callers to compute the decompression ratio and detect zip-bomb-style
    /// attacks that embed a tiny compressed stream expanding to gigabytes of data.
    pub compressed_size: i64,
    /// MIME type if specified in the filespec, otherwise `None`.
    pub mime_type: Option<String>,
}

/// PDF-specific metadata.
///
/// Contains metadata fields specific to PDF documents that are not in the common
/// `Metadata` structure. Common fields like title, authors, keywords, and dates
/// are at the `Metadata` level.
#[frb(mirror(PdfMetadata))]
pub struct PdfMetadata {
    /// PDF version (e.g., "1.7", "2.0")
    pub pdf_version: Option<String>,
    /// PDF producer (application that created the PDF)
    pub producer: Option<String>,
    /// Whether the PDF is encrypted/password-protected
    pub is_encrypted: Option<bool>,
    /// First page width in points (1/72 inch)
    pub width: Option<i64>,
    /// First page height in points (1/72 inch)
    pub height: Option<i64>,
    /// Total number of pages in the PDF document
    pub page_count: Option<i64>,
}

/// ONNX Runtime execution provider type.
///
/// Determines which hardware backend is used for model inference.
/// `Auto` (default) selects the best available provider per platform.
#[frb(mirror(ExecutionProviderType))]
pub enum ExecutionProviderType {
    /// Auto-select: CoreML on macOS, CUDA on Linux, CPU elsewhere.
    Auto,
    /// CPU execution provider (always available).
    Cpu,
    /// Apple CoreML (macOS/iOS Neural Engine + GPU).
    CoreMl,
    /// NVIDIA CUDA GPU acceleration.
    Cuda,
    /// NVIDIA TensorRT (optimized CUDA inference).
    TensorRt,
}

/// Output format for extraction results.
///
/// Controls the format of the `content` field in `ExtractionResult`.
/// When set to `Markdown`, `Djot`, or `Html`, the output uses that format.
/// `Plain` returns the raw extracted text.
/// `Structured` returns JSON with full OCR element data including bounding
/// boxes and confidence scores.
#[frb(mirror(OutputFormat))]
pub enum OutputFormat {
    /// Plain text content only (default)
    Plain,
    /// Markdown format
    Markdown,
    /// Djot markup format
    Djot,
    /// HTML format
    Html,
    /// JSON tree format with heading-driven sections.
    Json,
    /// Structured JSON format with full OCR element metadata.
    Structured,
    /// Custom renderer registered via the RendererRegistry.
    /// The string is the renderer name (e.g., "docx", "latex").
    Custom { field0: String },
}

/// Built-in HTML theme selection.
#[frb(mirror(HtmlTheme))]
pub enum HtmlTheme {
    /// Sensible defaults: system font stack, neutral colours, readable line
    /// measure. CSS custom properties (`--kb-*`) are all defined so user CSS
    /// can override individual values.
    Default,
    /// GitHub Markdown-inspired palette and spacing.
    GitHub,
    /// Dark background, light text.
    Dark,
    /// Minimal light theme with generous whitespace.
    Light,
    /// No built-in stylesheet emitted. CSS custom properties are still defined
    /// on `:root` so user stylesheets can reference `var(--kb-*)` tokens.
    Unstyled,
}

/// Which table structure recognition model to use.
///
/// Controls the model used for table cell detection within layout-detected
/// table regions. Wire format is snake_case in all serializers (JSON, TOML,
/// YAML).
#[frb(mirror(TableModel))]
pub enum TableModel {
    /// TATR (Table Transformer) -- default, 30MB, DETR-based row/column detection.
    Tatr,
    /// SLANeXT wired variant -- 365MB, optimized for bordered tables.
    SlanetWired,
    /// SLANeXT wireless variant -- 365MB, optimized for borderless tables.
    SlanetWireless,
    /// SLANet-plus -- 7.78MB, lightweight general-purpose.
    SlanetPlus,
    /// Classifier-routed SLANeXT: auto-select wired/wireless per table.
    /// Uses PP-LCNet classifier (6.78MB) + both SLANeXT variants (730MB total).
    SlanetAuto,
    /// Disable table structure model inference entirely; use heuristic path only.
    Disabled,
}

/// Type of text chunker to use.
///
/// # Variants
///
/// * `Text` - Generic text splitter, splits on whitespace and punctuation
/// * `Markdown` - Markdown-aware splitter, preserves formatting and structure
/// * `Yaml` - YAML-aware splitter, creates one chunk per top-level key
/// * `Semantic` - Topic-aware chunker. With an `EmbeddingConfig`, splits at
///   embedding-based topic shifts tuned by `topic_threshold` (default 0.75,
///   lower = more splits). Without an embedding, falls back to a
///   structural-boundary heuristic (ALL-CAPS headers, numbered sections,
///   blank-line paragraphs) and merges groups into chunks capped at
///   `max_characters` (default 1000). `topic_threshold` has no effect in the
///   fallback path. For best results, pair with an embedding model.
#[frb(mirror(ChunkerType))]
pub enum ChunkerType {
    Text,
    Markdown,
    Yaml,
    Semantic,
}

/// How chunk size is measured.
///
/// Defaults to `Characters` (Unicode character count). When using token-based sizing,
/// chunks are sized by token count according to the specified tokenizer.
///
/// Token-based sizing uses HuggingFace tokenizers loaded at runtime. Any tokenizer
/// available on HuggingFace Hub can be used, including OpenAI-compatible tokenizers
/// (e.g., `Xenova/gpt-4o`, `Xenova/cl100k_base`).
#[frb(mirror(ChunkSizing))]
pub enum ChunkSizing {
    /// Size measured in Unicode characters (default).
    Characters,
    /// Size measured in tokens from a HuggingFace tokenizer.
    Tokenizer {
        /// HuggingFace model ID or path, e.g. "Xenova/gpt-4o", "bert-base-uncased".
        model: String,
        /// Optional cache directory override for tokenizer files.
        /// Defaults to hf-hub's standard cache (`~/.cache/huggingface/`).
        /// Can also be set via `KREUZBERG_TOKENIZER_CACHE_DIR` environment variable.
        cache_dir: String,
    },
}

/// Embedding model types supported by Kreuzberg.
#[frb(mirror(EmbeddingModelType))]
pub enum EmbeddingModelType {
    /// Use a preset model configuration (recommended)
    Preset { name: String },
    /// Use a custom ONNX model from HuggingFace
    Custom { model_id: String, dimensions: i64 },
    /// Provider-hosted embedding model via liter-llm.
    ///
    /// Uses the model specified in the nested `LlmConfig` (e.g.,
    /// `"openai/text-embedding-3-small"`).
    Llm { llm: LlmConfig },
    /// In-process embedding backend registered via the plugin system.
    ///
    /// The caller registers an [`EmbeddingBackend`](crate::plugins::EmbeddingBackend) once
    /// (e.g. a wrapper around an already-loaded `llama-cpp-python`, `sentence-transformers`,
    /// or tuned ONNX model), then references it by name in config. Kreuzberg calls back
    /// into the registered backend during chunking and standalone embed requests —
    /// no HuggingFace download, no ONNX Runtime requirement, no HTTP sidecar.
    ///
    /// When this variant is selected, only the following [`EmbeddingConfig`] fields
    /// apply: `normalize` (post-call L2 normalization) and `max_embed_duration_secs`
    /// (dispatcher timeout). Model-loading fields (`batch_size`, `cache_dir`,
    /// `show_download_progress`, `acceleration`) are ignored — the host owns the
    /// model lifecycle.
    ///
    /// Semantic chunking falls back to [`ChunkingConfig::max_characters`] when this variant
    /// is used, since there is no preset to look a chunk-size ceiling up against — size your
    /// context window via `max_characters` directly.
    ///
    /// See `register_embedding_backend`.
    Plugin { name: String },
}

/// Content rendering mode for code extraction.
///
/// Controls how extracted code content is represented in the `content` field
/// of `ExtractionResult`.
#[frb(mirror(CodeContentMode))]
pub enum CodeContentMode {
    /// Use TSLP semantic chunks as content (default).
    Chunks,
    /// Use raw source code as content.
    Raw,
    /// Emit function/class headings + docstrings (no code bodies).
    Structure,
}

/// Type of list detection.
#[frb(mirror(ListType))]
pub enum ListType {
    /// Bullet points (-, *, •, etc.)
    Bullet,
    /// Numbered lists (1., 2., etc.)
    Numbered,
    /// Lettered lists (a., b., A., B., etc.)
    Lettered,
    /// Indented items
    Indented,
}

/// OCR backend types.
#[frb(mirror(OcrBackendType))]
pub enum OcrBackendType {
    /// Tesseract OCR (native Rust binding)
    Tesseract,
    /// EasyOCR (Python-based, via FFI)
    EasyOCR,
    /// PaddleOCR (Python-based, via FFI)
    PaddleOCR,
    /// Custom/third-party OCR backend
    Custom,
}

/// Processing stages for post-processors.
///
/// Post-processors are executed in stage order (Early → Middle → Late).
/// Use stages to control the order of post-processing operations.
#[frb(mirror(ProcessingStage))]
pub enum ProcessingStage {
    /// Early stage - foundational processing.
    ///
    /// Use for:
    /// - Language detection
    /// - Character encoding normalization
    /// - Entity extraction (NER)
    /// - Text quality scoring
    Early,
    /// Middle stage - content transformation.
    ///
    /// Use for:
    /// - Keyword extraction
    /// - Token reduction
    /// - Text summarization
    /// - Semantic analysis
    Middle,
    /// Late stage - final enrichment.
    ///
    /// Use for:
    /// - Custom user hooks
    /// - Analytics/logging
    /// - Final validation
    /// - Output formatting
    Late,
}

#[frb(mirror(ReductionLevel))]
pub enum ReductionLevel {
    Off,
    Light,
    Moderate,
    Aggressive,
    Maximum,
}

/// Type of PDF annotation.
#[frb(mirror(PdfAnnotationType))]
pub enum PdfAnnotationType {
    /// Sticky note / text annotation
    Text,
    /// Highlighted text region
    Highlight,
    /// Hyperlink annotation
    Link,
    /// Rubber stamp annotation
    Stamp,
    /// Underline text markup
    Underline,
    /// Strikeout text markup
    StrikeOut,
    /// Any other annotation type
    Other,
}

/// Types of block-level elements in Djot.
#[frb(mirror(BlockType))]
pub enum BlockType {
    Paragraph,
    Heading,
    Blockquote,
    CodeBlock,
    ListItem,
    OrderedList,
    BulletList,
    TaskList,
    DefinitionList,
    DefinitionTerm,
    DefinitionDescription,
    Div,
    Section,
    ThematicBreak,
    RawBlock,
    MathDisplay,
}

/// Types of inline elements in Djot.
#[frb(mirror(InlineType))]
pub enum InlineType {
    Text,
    Strong,
    Emphasis,
    Highlight,
    Subscript,
    Superscript,
    Insert,
    Delete,
    Code,
    Link,
    Image,
    Span,
    Math,
    RawInline,
    FootnoteRef,
    Symbol,
}

/// Semantic kind of a relationship between document elements.
#[frb(mirror(RelationshipKind))]
pub enum RelationshipKind {
    /// Footnote marker -> footnote definition.
    FootnoteReference,
    /// Citation marker -> bibliography entry.
    CitationReference,
    /// Internal anchor link (`#id`) -> target heading/element.
    InternalLink,
    /// Caption paragraph -> figure/table it describes.
    Caption,
    /// Label -> labeled element (HTML `<label for>`, LaTeX `\label{}`).
    Label,
    /// TOC entry -> target section.
    TocEntry,
    /// Cross-reference (LaTeX `\ref{}`, DOCX cross-reference field).
    CrossReference,
}

/// Content layer classification for document nodes.
///
/// Replaces separate body/furniture arrays with per-node granularity.
#[frb(mirror(ContentLayer))]
pub enum ContentLayer {
    /// Main document body content.
    Body,
    /// Page/section header (running header).
    Header,
    /// Page/section footer (running footer).
    Footer,
    /// Footnote content.
    Footnote,
}

/// Tagged enum for node content. Each variant carries only type-specific data.
///
/// Uses `#[serde(tag = "node_type")]` to avoid "type" keyword collision in
/// Go/Java/TypeScript bindings.
#[frb(mirror(NodeContent))]
pub enum NodeContent {
    /// Document title.
    Title { text: String },
    /// Section heading with level (1-6).
    Heading { level: i64, text: String },
    /// Body text paragraph.
    Paragraph { text: String },
    /// List container — children are `ListItem` nodes.
    List { ordered: bool },
    /// Individual list item.
    ListItem { text: String },
    /// Table with structured cell grid.
    Table { grid: TableGrid },
    /// Image reference.
    Image {
        description: String,
        image_index: i64,
        /// Source URL or path of the image (from `<img src="...">` or `![](src)`).
        src: String,
    },
    /// Code block.
    Code { text: String, language: String },
    /// Block quote — container, children carry the quoted content.
    Quote,
    /// Mathematical formula / equation.
    Formula { text: String },
    /// Footnote reference content.
    Footnote { text: String },
    /// Logical grouping container (section, key-value area).
    ///
    /// `heading_level` + `heading_text` capture the section heading directly
    /// rather than relying on a first-child positional convention.
    Group {
        label: String,
        heading_level: i64,
        heading_text: String,
    },
    /// Page break marker.
    PageBreak,
    /// Presentation slide container — children are the slide's content nodes.
    Slide {
        /// 1-indexed slide number.
        number: i64,
        title: String,
    },
    /// Definition list container — children are `DefinitionItem` nodes.
    DefinitionList,
    /// Individual definition list entry with term and definition.
    DefinitionItem { term: String, definition: String },
    /// Citation or bibliographic reference.
    Citation { key: String, text: String },
    /// Admonition / callout container (note, warning, tip, etc.).
    ///
    /// Children carry the admonition body content.
    Admonition {
        /// Kind of admonition (e.g. "note", "warning", "tip", "danger").
        kind: String,
        title: String,
    },
    /// Raw block preserved verbatim from the source format.
    ///
    /// Used for content that cannot be mapped to a semantic node type
    /// (e.g. JSX in MDX, raw LaTeX in markdown, embedded HTML).
    RawBlock {
        /// Source format identifier (e.g. "html", "latex", "jsx").
        format: String,
        content: String,
    },
    /// Structured metadata block (email headers, YAML frontmatter, etc.).
    MetadataBlock { entries: Vec<Vec<String>> },
}

/// Types of inline text annotations.
#[frb(mirror(AnnotationKind))]
pub enum AnnotationKind {
    Bold,
    Italic,
    Underline,
    Strikethrough,
    Code,
    Subscript,
    Superscript,
    Link {
        url: String,
        title: String,
    },
    /// Highlighted text (PDF highlights, HTML `<mark>`).
    Highlight,
    /// Text color (CSS-compatible value, e.g. "#ff0000", "red").
    Color {
        value: String,
    },
    /// Font size with units (e.g. "12pt", "1.2em", "16px").
    FontSize {
        value: String,
    },
    /// Extensible annotation for format-specific styling.
    Custom {
        name: String,
        value: String,
    },
}

/// How the extracted text was produced.
#[frb(mirror(ExtractionMethod))]
pub enum ExtractionMethod {
    Native,
    Ocr,
    Mixed,
}

/// Semantic structural classification of a text chunk.
///
/// Assigned by the heuristic classifier in `chunking::classifier`.
/// Defaults to `Unknown` when no rule matches.
/// Designed to be extended in future versions without breaking changes.
#[frb(mirror(ChunkType))]
pub enum ChunkType {
    /// Section heading or document title.
    Heading,
    /// Party list: names, addresses, and signatories.
    PartyList,
    /// Definition clause ("X means…", "X shall mean…").
    Definitions,
    /// Operative clause containing legal/contractual action verbs.
    OperativeClause,
    /// Signature block with signatures, names, and dates.
    SignatureBlock,
    /// Schedule, annex, appendix, or exhibit section.
    Schedule,
    /// Table-like content with aligned columns or repeated patterns.
    TableLike,
    /// Mathematical formula or equation.
    Formula,
    /// Code block or preformatted content.
    CodeBlock,
    /// Embedded or referenced image content.
    Image,
    /// Organizational chart or hierarchy diagram.
    OrgChart,
    /// Diagram, figure, or visual illustration.
    Diagram,
    /// Unclassified or mixed content.
    Unknown,
}

/// Heuristic classification of what an image likely depicts.
#[frb(mirror(ImageKind))]
pub enum ImageKind {
    /// Photographic image (natural scene, photograph)
    Photograph,
    /// Technical or schematic diagram
    Diagram,
    /// Chart, graph, or plot
    Chart,
    /// Freehand or technical drawing
    Drawing,
    /// Text-heavy image (scanned text, document)
    TextBlock,
    /// Decorative element or border
    Decoration,
    /// Logo or brand mark
    Logo,
    /// Small icon
    Icon,
    /// Fragment of a larger tiled image (tile of a technical drawing)
    TileFragment,
    /// Mask or transparency map
    Mask,
    /// Full-page render produced during OCR preprocessing; used as a citation thumbnail.
    PageRaster,
    /// Could not classify with reasonable confidence
    Unknown,
}

/// Result-shape selection for extraction results.
///
/// Distinct from `OutputFormat` (which controls rendering — Plain, Markdown,
/// HTML, etc.). `ResultFormat` controls the *shape* of the result: a unified content
/// blob vs. an element-based decomposition.
#[frb(mirror(ResultFormat))]
pub enum ResultFormat {
    /// Unified format with all content in `content` field
    Unified,
    /// Element-based format with semantic element extraction
    ElementBased,
}

/// Semantic element type classification.
///
/// Categorizes text content into semantic units for downstream processing.
/// Supports the element types commonly found in Unstructured documents.
#[frb(mirror(ElementType))]
pub enum ElementType {
    /// Document title
    Title,
    /// Main narrative text body
    NarrativeText,
    /// Section heading
    Heading,
    /// List item (bullet, numbered, etc.)
    ListItem,
    /// Table element
    Table,
    /// Image element
    Image,
    /// Page break marker
    PageBreak,
    /// Code block
    CodeBlock,
    /// Block quote
    BlockQuote,
    /// Footer text
    Footer,
    /// Header text
    Header,
}

/// Format-specific metadata (discriminated union).
///
/// Only one format type can exist per extraction result. This provides
/// type-safe, clean metadata without nested optionals.
#[frb(mirror(FormatMetadata))]
pub enum FormatMetadata {
    Pdf { field0: PdfMetadata },
    Docx { field0: DocxMetadata },
    Excel { field0: ExcelMetadata },
    Email { field0: EmailMetadata },
    Pptx { field0: PptxMetadata },
    Archive { field0: ArchiveMetadata },
    Image { field0: ImageMetadata },
    Xml { field0: XmlMetadata },
    Text { field0: TextMetadata },
    Html { field0: HtmlMetadata },
    Ocr { field0: OcrMetadata },
    Csv { field0: CsvMetadata },
    Bibtex { field0: BibtexMetadata },
    Citation { field0: CitationMetadata },
    FictionBook { field0: FictionBookMetadata },
    Dbf { field0: DbfMetadata },
    Jats { field0: JatsMetadata },
    Epub { field0: EpubMetadata },
    Pst { field0: PstMetadata },
    Code { field0: String },
}

/// Text direction enumeration for HTML documents.
#[frb(mirror(TextDirection))]
pub enum TextDirection {
    /// Left-to-right text direction
    LeftToRight,
    /// Right-to-left text direction
    RightToLeft,
    /// Automatic text direction detection
    Auto,
}

/// Link type classification.
#[frb(mirror(LinkType))]
pub enum LinkType {
    /// Anchor link (#section)
    Anchor,
    /// Internal link (same domain)
    Internal,
    /// External link (different domain)
    External,
    /// Email link (mailto:)
    Email,
    /// Phone link (tel:)
    Phone,
    /// Other link type
    Other,
}

/// Image type classification.
#[frb(mirror(ImageType))]
pub enum ImageType {
    /// Data URI image
    DataUri,
    /// Inline SVG
    InlineSvg,
    /// External image URL
    External,
    /// Relative path image
    Relative,
}

/// Structured data type classification.
#[frb(mirror(StructuredDataType))]
pub enum StructuredDataType {
    /// JSON-LD structured data
    JsonLd,
    /// Microdata
    Microdata,
    /// RDFa
    RDFa,
}

/// Bounding geometry for an OCR element.
///
/// Supports both axis-aligned rectangles (from Tesseract) and 4-point quadrilaterals
/// (from PaddleOCR and rotated text detection).
#[frb(mirror(OcrBoundingGeometry))]
pub enum OcrBoundingGeometry {
    /// Axis-aligned bounding box (typical for Tesseract output).
    Rectangle {
        /// Left x-coordinate in pixels
        left: i64,
        /// Top y-coordinate in pixels
        top: i64,
        /// Width in pixels
        width: i64,
        /// Height in pixels
        height: i64,
    },
    /// 4-point quadrilateral for rotated/skewed text (PaddleOCR).
    ///
    /// Points are in clockwise order starting from top-left:
    /// `[top_left, top_right, bottom_right, bottom_left]`
    Quadrilateral {
        /// Four corner points as `[(x, y), ...]` in clockwise order
        points: String,
    },
}

/// Hierarchical level of an OCR element.
///
/// Maps to Tesseract's page segmentation hierarchy and provides
/// equivalent semantics for PaddleOCR.
#[frb(mirror(OcrElementLevel))]
pub enum OcrElementLevel {
    /// Individual word
    Word,
    /// Line of text (default for PaddleOCR)
    Line,
    /// Paragraph or text block
    Block,
    /// Page-level element
    Page,
}

/// Type of paginated unit in a document.
///
/// Distinguishes between different types of "pages" (PDF pages, presentation slides, spreadsheet sheets).
#[frb(mirror(PageUnitType))]
pub enum PageUnitType {
    /// Standard document pages (PDF, DOCX, images)
    Page,
    /// Presentation slides (PPTX, ODP)
    Slide,
    /// Spreadsheet sheets (XLSX, ODS)
    Sheet,
}

/// A single line in a unified-diff hunk.
///
/// Defined here (rather than only in `crate::diff`) so `RevisionDelta` can
/// reference it unconditionally, without requiring the `diff` Cargo feature.
/// `crate::diff` re-exports this type verbatim.
#[frb(mirror(DiffLine))]
pub enum DiffLine {
    /// Unchanged context line.
    Context { field0: String },
    /// Line added in the "after" version.
    Added { field0: String },
    /// Line removed from the "before" version.
    Removed { field0: String },
}

/// Semantic classification of a tracked change.
#[frb(mirror(RevisionKind))]
pub enum RevisionKind {
    /// Text or content was inserted.
    Insertion,
    /// Text or content was deleted.
    Deletion,
    /// Run-level formatting (font, size, colour, …) was changed.
    FormatChange,
    /// A reviewer comment or annotation.
    Comment,
}

/// Best-effort document location for a revision.
#[frb(mirror(RevisionAnchor))]
pub enum RevisionAnchor {
    /// Body paragraph, identified by its zero-based index in the document flow.
    Paragraph {
        /// Zero-based index of the paragraph in document order.
        index: i64,
    },
    /// Cell inside a table.
    TableCell {
        /// Zero-based row index within the table.
        row: i64,
        /// Zero-based column index within the table.
        col: i64,
        /// Zero-based index of the table in document order.
        table_index: i64,
    },
    /// Page, identified by its zero-based index.
    Page {
        /// Zero-based page index.
        index: i64,
    },
    /// Presentation slide, identified by its zero-based index.
    Slide {
        /// Zero-based slide index.
        index: i64,
    },
    /// Spreadsheet cell or range, identified by sheet index and optional name.
    Sheet {
        /// Zero-based sheet index.
        index: i64,
        /// Sheet display name when available.
        name: String,
    },
}

/// Semantic classification of an extracted URI.
#[frb(mirror(UriKind))]
pub enum UriKind {
    /// A clickable hyperlink (web URL, file link).
    Hyperlink,
    /// An image or media resource reference.
    Image,
    /// An internal anchor or cross-reference target.
    Anchor,
    /// A citation or bibliographic reference (DOI, academic ref).
    Citation,
    /// A general reference (e.g. `\ref{}` in LaTeX, `:ref:` in RST).
    Reference,
    /// An email address (`mailto:` link or bare email).
    Email,
}

/// Keyword algorithm selection.
#[frb(mirror(KeywordAlgorithm))]
pub enum KeywordAlgorithm {
    /// YAKE (Yet Another Keyword Extractor) - statistical approach
    Yake,
    /// RAKE (Rapid Automatic Keyword Extraction) - co-occurrence based
    Rake,
}

/// Page Segmentation Mode for Tesseract OCR
#[frb(mirror(PSMMode))]
pub enum PSMMode {
    OsdOnly,
    AutoOsd,
    AutoOnly,
    Auto,
    SingleColumn,
    SingleBlockVertical,
    SingleBlock,
    SingleLine,
    SingleWord,
    CircleWord,
    SingleChar,
}

/// Supported languages in PaddleOCR.
///
/// Maps user-friendly language codes to paddle-ocr-rs language identifiers.
#[frb(mirror(PaddleLanguage))]
pub enum PaddleLanguage {
    /// English
    English,
    /// Simplified Chinese
    Chinese,
    /// Japanese
    Japanese,
    /// Korean
    Korean,
    /// German
    German,
    /// French
    French,
    /// Latin script (covers most European languages)
    Latin,
    /// Cyrillic (Russian and related)
    Cyrillic,
    /// Traditional Chinese
    TraditionalChinese,
    /// Thai
    Thai,
    /// Greek
    Greek,
    /// East Slavic (Russian, Ukrainian, Belarusian)
    EastSlavic,
    /// Arabic (Arabic, Persian, Urdu)
    Arabic,
    /// Devanagari (Hindi, Marathi, Sanskrit, Nepali)
    Devanagari,
    /// Tamil
    Tamil,
    /// Telugu
    Telugu,
}

/// The 17 canonical document layout classes.
///
/// All model backends (RT-DETR, YOLO, etc.) map their native class IDs
/// to this shared set. Models with fewer classes (DocLayNet: 11, PubLayNet: 5)
/// map to the closest equivalent.
///
/// Wire format is snake_case in all serializers (JSON, TOML, YAML).
#[frb(mirror(LayoutClass))]
pub enum LayoutClass {
    Caption,
    Footnote,
    Formula,
    ListItem,
    PageFooter,
    PageHeader,
    Picture,
    SectionHeader,
    Table,
    Text,
    Title,
    DocumentIndex,
    Code,
    CheckboxSelected,
    CheckboxUnselected,
    Form,
    KeyValueRegion,
}

/// Main error type for all Kreuzberg operations.
///
/// All errors in Kreuzberg use this enum, which preserves error chains
/// and provides context for debugging.
///
/// # Variants
///
/// - `Io` - File system and I/O errors (always bubble up)
/// - `Parsing` - Document parsing errors (corrupt files, unsupported features)
/// - `Ocr` - OCR processing errors
/// - `Validation` - Input validation errors (invalid paths, config, parameters)
/// - `Cache` - Cache operation errors (non-fatal, can be ignored)
/// - `ImageProcessing` - Image manipulation errors
/// - `Serialization` - JSON/MessagePack serialization errors
/// - `MissingDependency` - Missing optional dependencies (tesseract, etc.)
/// - `Plugin` - Plugin-specific errors
/// - `LockPoisoned` - Mutex/RwLock poisoning (should not happen in normal operation)
/// - `UnsupportedFormat` - Unsupported MIME type or file format
/// - `Other` - Catch-all for uncommon errors
#[frb(mirror(KreuzbergError))]
pub enum KreuzbergError {
    Io { field0: String },
    Parsing { message: String },
    Ocr { message: String },
    Validation { message: String },
    Cache { message: String },
    ImageProcessing { message: String },
    Serialization { message: String },
    MissingDependency { field0: String },
    Plugin { message: String, plugin_name: String },
    LockPoisoned { field0: String },
    UnsupportedFormat { field0: String },
    Embedding { message: String },
    Timeout { elapsed_ms: i64, limit_ms: i64 },
    Cancelled,
    Security { message: String },
    Other { field0: String },
}

// From<SourceT> conversions for bridge return types.

impl From<kreuzberg::CacheStats> for CacheStats {
    fn from(v: kreuzberg::CacheStats) -> Self {
        CacheStats {
            total_files: v.total_files as _,
            total_size_mb: v.total_size_mb as _,
            available_space_mb: v.available_space_mb as _,
            oldest_file_age_days: v.oldest_file_age_days as _,
            newest_file_age_days: v.newest_file_age_days as _,
        }
    }
}

impl From<kreuzberg::AccelerationConfig> for AccelerationConfig {
    fn from(v: kreuzberg::AccelerationConfig) -> Self {
        AccelerationConfig {
            provider: ExecutionProviderType::from(v.provider),
            device_id: v.device_id as _,
        }
    }
}

impl From<kreuzberg::ContentFilterConfig> for ContentFilterConfig {
    fn from(v: kreuzberg::ContentFilterConfig) -> Self {
        ContentFilterConfig {
            include_headers: v.include_headers as _,
            include_footers: v.include_footers as _,
            strip_repeating_text: v.strip_repeating_text as _,
            include_watermarks: v.include_watermarks as _,
        }
    }
}

impl From<kreuzberg::EmailConfig> for EmailConfig {
    fn from(v: kreuzberg::EmailConfig) -> Self {
        EmailConfig {
            msg_fallback_codepage: v.msg_fallback_codepage.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::ExtractionConfig> for ExtractionConfig {
    fn from(v: kreuzberg::ExtractionConfig) -> Self {
        ExtractionConfig {
            use_cache: v.use_cache as _,
            enable_quality_processing: v.enable_quality_processing as _,
            ocr: v.ocr.map(OcrConfig::from),
            force_ocr: v.force_ocr as _,
            force_ocr_pages: v.force_ocr_pages.map(|vec| vec.into_iter().map(|x| x as _).collect()),
            disable_ocr: v.disable_ocr as _,
            chunking: v.chunking.map(ChunkingConfig::from),
            content_filter: v.content_filter.map(ContentFilterConfig::from),
            images: v.images.map(ImageExtractionConfig::from),
            pdf_options: v.pdf_options.map(PdfConfig::from),
            token_reduction: v.token_reduction.map(TokenReductionOptions::from),
            language_detection: v.language_detection.map(LanguageDetectionConfig::from),
            pages: v.pages.map(PageConfig::from),
            keywords: v.keywords.map(KeywordConfig::from),
            postprocessor: v.postprocessor.map(PostProcessorConfig::from),
            html_options: Default::default(),
            html_output: v.html_output.map(HtmlOutputConfig::from),
            extraction_timeout_secs: v.extraction_timeout_secs.map(|x| x as _),
            max_concurrent_extractions: v.max_concurrent_extractions.map(|x| x as _),
            result_format: ResultFormat::from(v.result_format),
            security_limits: v.security_limits.map(SecurityLimits::from),
            max_embedded_file_bytes: v.max_embedded_file_bytes.map(|x| x as _),
            output_format: OutputFormat::from(v.output_format),
            layout: v.layout.map(LayoutDetectionConfig::from),
            use_layout_for_markdown: v.use_layout_for_markdown as _,
            include_document_structure: v.include_document_structure as _,
            acceleration: v.acceleration.map(AccelerationConfig::from),
            cache_namespace: v.cache_namespace.map(|s| s.into()),
            cache_ttl_secs: v.cache_ttl_secs.map(|x| x as _),
            email: v.email.map(EmailConfig::from),
            concurrency: Default::default(),
            max_archive_depth: v.max_archive_depth as _,
            tree_sitter: v.tree_sitter.map(TreeSitterConfig::from),
            structured_extraction: v.structured_extraction.map(StructuredExtractionConfig::from),
            cancel_token: Default::default(),
        }
    }
}

impl From<kreuzberg::FileExtractionConfig> for FileExtractionConfig {
    fn from(v: kreuzberg::FileExtractionConfig) -> Self {
        FileExtractionConfig {
            enable_quality_processing: v.enable_quality_processing.map(|x| x as _),
            ocr: v.ocr.map(OcrConfig::from),
            force_ocr: v.force_ocr.map(|x| x as _),
            force_ocr_pages: v.force_ocr_pages.map(|vec| vec.into_iter().map(|x| x as _).collect()),
            disable_ocr: v.disable_ocr.map(|x| x as _),
            chunking: v.chunking.map(ChunkingConfig::from),
            content_filter: v.content_filter.map(ContentFilterConfig::from),
            images: v.images.map(ImageExtractionConfig::from),
            pdf_options: v.pdf_options.map(PdfConfig::from),
            token_reduction: v.token_reduction.map(TokenReductionOptions::from),
            language_detection: v.language_detection.map(LanguageDetectionConfig::from),
            pages: v.pages.map(PageConfig::from),
            keywords: v.keywords.map(KeywordConfig::from),
            postprocessor: v.postprocessor.map(PostProcessorConfig::from),
            html_options: Default::default(),
            result_format: v.result_format.map(ResultFormat::from),
            output_format: v.output_format.map(OutputFormat::from),
            include_document_structure: v.include_document_structure.map(|x| x as _),
            layout: v.layout.map(LayoutDetectionConfig::from),
            timeout_secs: v.timeout_secs.map(|x| x as _),
            tree_sitter: v.tree_sitter.map(TreeSitterConfig::from),
            structured_extraction: v.structured_extraction.map(StructuredExtractionConfig::from),
        }
    }
}

impl From<kreuzberg::BatchBytesItem> for BatchBytesItem {
    fn from(v: kreuzberg::BatchBytesItem) -> Self {
        BatchBytesItem {
            content: v.content.into(),
            mime_type: v.mime_type.into(),
            config: v.config.map(FileExtractionConfig::from),
        }
    }
}

impl From<kreuzberg::BatchFileItem> for BatchFileItem {
    fn from(v: kreuzberg::BatchFileItem) -> Self {
        BatchFileItem {
            path: v.path.to_string_lossy().into_owned(),
            config: v.config.map(FileExtractionConfig::from),
        }
    }
}

impl From<kreuzberg::ImageExtractionConfig> for ImageExtractionConfig {
    fn from(v: kreuzberg::ImageExtractionConfig) -> Self {
        ImageExtractionConfig {
            extract_images: v.extract_images as _,
            target_dpi: v.target_dpi as _,
            max_image_dimension: v.max_image_dimension as _,
            inject_placeholders: v.inject_placeholders as _,
            auto_adjust_dpi: v.auto_adjust_dpi as _,
            min_dpi: v.min_dpi as _,
            max_dpi: v.max_dpi as _,
            max_images_per_page: v.max_images_per_page.map(|x| x as _),
            classify: v.classify as _,
            include_page_rasters: v.include_page_rasters as _,
            run_ocr_on_images: v.run_ocr_on_images as _,
            ocr_text_only: v.ocr_text_only as _,
            append_ocr_text: v.append_ocr_text as _,
        }
    }
}

impl From<kreuzberg::TokenReductionOptions> for TokenReductionOptions {
    fn from(v: kreuzberg::TokenReductionOptions) -> Self {
        TokenReductionOptions {
            mode: v.mode.into(),
            preserve_important_words: v.preserve_important_words as _,
        }
    }
}

impl From<kreuzberg::LanguageDetectionConfig> for LanguageDetectionConfig {
    fn from(v: kreuzberg::LanguageDetectionConfig) -> Self {
        LanguageDetectionConfig {
            enabled: v.enabled as _,
            min_confidence: v.min_confidence as _,
            detect_multiple: v.detect_multiple as _,
        }
    }
}

impl From<kreuzberg::HtmlOutputConfig> for HtmlOutputConfig {
    fn from(v: kreuzberg::HtmlOutputConfig) -> Self {
        HtmlOutputConfig {
            css: v.css.map(|s| s.into()),
            css_file: v.css_file.map(|p| p.to_string_lossy().into_owned()),
            theme: HtmlTheme::from(v.theme),
            class_prefix: v.class_prefix.into(),
            embed_css: v.embed_css as _,
        }
    }
}

impl From<kreuzberg::LayoutDetectionConfig> for LayoutDetectionConfig {
    fn from(v: kreuzberg::LayoutDetectionConfig) -> Self {
        LayoutDetectionConfig {
            confidence_threshold: v.confidence_threshold.map(|x| x as _),
            apply_heuristics: v.apply_heuristics as _,
            table_model: TableModel::from(v.table_model),
            acceleration: v.acceleration.map(AccelerationConfig::from),
        }
    }
}

impl From<kreuzberg::LlmConfig> for LlmConfig {
    fn from(v: kreuzberg::LlmConfig) -> Self {
        LlmConfig {
            model: v.model.into(),
            api_key: v.api_key.map(|s| s.into()),
            base_url: v.base_url.map(|s| s.into()),
            timeout_secs: v.timeout_secs.map(|x| x as _),
            max_retries: v.max_retries.map(|x| x as _),
            temperature: v.temperature.map(|x| x as _),
            max_tokens: v.max_tokens.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::StructuredExtractionConfig> for StructuredExtractionConfig {
    fn from(v: kreuzberg::StructuredExtractionConfig) -> Self {
        StructuredExtractionConfig {
            schema: serde_json::to_string(&v.schema).unwrap_or_default(),
            schema_name: v.schema_name.into(),
            schema_description: v.schema_description.map(|s| s.into()),
            strict: v.strict as _,
            prompt: v.prompt.map(|s| s.into()),
            llm: LlmConfig::from(v.llm),
        }
    }
}

impl From<kreuzberg::OcrQualityThresholds> for OcrQualityThresholds {
    fn from(v: kreuzberg::OcrQualityThresholds) -> Self {
        OcrQualityThresholds {
            min_total_non_whitespace: v.min_total_non_whitespace as _,
            min_non_whitespace_per_page: v.min_non_whitespace_per_page as _,
            min_meaningful_word_len: v.min_meaningful_word_len as _,
            min_meaningful_words: v.min_meaningful_words as _,
            min_alnum_ratio: v.min_alnum_ratio as _,
            min_garbage_chars: v.min_garbage_chars as _,
            max_fragmented_word_ratio: v.max_fragmented_word_ratio as _,
            critical_fragmented_word_ratio: v.critical_fragmented_word_ratio as _,
            min_avg_word_length: v.min_avg_word_length as _,
            min_words_for_avg_length_check: v.min_words_for_avg_length_check as _,
            min_consecutive_repeat_ratio: v.min_consecutive_repeat_ratio as _,
            min_words_for_repeat_check: v.min_words_for_repeat_check as _,
            substantive_min_chars: v.substantive_min_chars as _,
            non_text_min_chars: v.non_text_min_chars as _,
            alnum_ws_ratio_threshold: v.alnum_ws_ratio_threshold as _,
            pipeline_min_quality: v.pipeline_min_quality as _,
        }
    }
}

impl From<kreuzberg::OcrPipelineStage> for OcrPipelineStage {
    fn from(v: kreuzberg::OcrPipelineStage) -> Self {
        OcrPipelineStage {
            backend: v.backend.into(),
            priority: v.priority as _,
            language: v.language.map(|s| s.into()),
            tesseract_config: v.tesseract_config.map(TesseractConfig::from),
            paddle_ocr_config: v
                .paddle_ocr_config
                .map(|j| serde_json::to_string(&j).unwrap_or_default()),
            vlm_config: v.vlm_config.map(LlmConfig::from),
            backend_options: v.backend_options.map(|j| serde_json::to_string(&j).unwrap_or_default()),
        }
    }
}

impl From<kreuzberg::OcrPipelineConfig> for OcrPipelineConfig {
    fn from(v: kreuzberg::OcrPipelineConfig) -> Self {
        OcrPipelineConfig {
            stages: v.stages.into_iter().map(OcrPipelineStage::from).collect(),
            quality_thresholds: OcrQualityThresholds::from(v.quality_thresholds),
        }
    }
}

impl From<kreuzberg::OcrConfig> for OcrConfig {
    fn from(v: kreuzberg::OcrConfig) -> Self {
        OcrConfig {
            enabled: v.enabled as _,
            backend: v.backend.into(),
            language: v.language.into(),
            tesseract_config: v.tesseract_config.map(TesseractConfig::from),
            output_format: v.output_format.map(OutputFormat::from),
            paddle_ocr_config: v
                .paddle_ocr_config
                .map(|j| serde_json::to_string(&j).unwrap_or_default()),
            backend_options: v.backend_options.map(|j| serde_json::to_string(&j).unwrap_or_default()),
            element_config: v.element_config.map(OcrElementConfig::from),
            quality_thresholds: v.quality_thresholds.map(OcrQualityThresholds::from),
            pipeline: v.pipeline.map(OcrPipelineConfig::from),
            auto_rotate: v.auto_rotate as _,
            vlm_config: v.vlm_config.map(LlmConfig::from),
            vlm_prompt: v.vlm_prompt.map(|s| s.into()),
            acceleration: v.acceleration.map(AccelerationConfig::from),
            tessdata_bytes: v
                .tessdata_bytes
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<kreuzberg::PageConfig> for PageConfig {
    fn from(v: kreuzberg::PageConfig) -> Self {
        PageConfig {
            extract_pages: v.extract_pages as _,
            insert_page_markers: v.insert_page_markers as _,
            marker_format: v.marker_format.into(),
        }
    }
}

impl From<kreuzberg::PdfConfig> for PdfConfig {
    fn from(v: kreuzberg::PdfConfig) -> Self {
        PdfConfig {
            extract_images: v.extract_images as _,
            extract_tables: v.extract_tables as _,
            passwords: v.passwords.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            extract_metadata: v.extract_metadata as _,
            hierarchy: v.hierarchy.map(HierarchyConfig::from),
            extract_annotations: v.extract_annotations as _,
            top_margin_fraction: v.top_margin_fraction.map(|x| x as _),
            bottom_margin_fraction: v.bottom_margin_fraction.map(|x| x as _),
            allow_single_column_tables: v.allow_single_column_tables as _,
            ocr_inline_images: v.ocr_inline_images as _,
        }
    }
}

impl From<kreuzberg::HierarchyConfig> for HierarchyConfig {
    fn from(v: kreuzberg::HierarchyConfig) -> Self {
        HierarchyConfig {
            enabled: v.enabled as _,
            k_clusters: v.k_clusters as _,
            include_bbox: v.include_bbox as _,
            ocr_coverage_threshold: v.ocr_coverage_threshold.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::PostProcessorConfig> for PostProcessorConfig {
    fn from(v: kreuzberg::PostProcessorConfig) -> Self {
        PostProcessorConfig {
            enabled: v.enabled as _,
            enabled_processors: v
                .enabled_processors
                .map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            disabled_processors: v
                .disabled_processors
                .map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            enabled_set: v.enabled_set.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            disabled_set: v.disabled_set.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
        }
    }
}

impl From<kreuzberg::ChunkingConfig> for ChunkingConfig {
    fn from(v: kreuzberg::ChunkingConfig) -> Self {
        ChunkingConfig {
            max_characters: v.max_characters as _,
            overlap: v.overlap as _,
            trim: v.trim as _,
            chunker_type: ChunkerType::from(v.chunker_type),
            embedding: v.embedding.map(EmbeddingConfig::from),
            preset: v.preset.map(|s| s.into()),
            sizing: ChunkSizing::from(v.sizing),
            prepend_heading_context: v.prepend_heading_context as _,
            topic_threshold: v.topic_threshold.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::EmbeddingConfig> for EmbeddingConfig {
    fn from(v: kreuzberg::EmbeddingConfig) -> Self {
        EmbeddingConfig {
            model: EmbeddingModelType::from(v.model),
            normalize: v.normalize as _,
            batch_size: v.batch_size as _,
            show_download_progress: v.show_download_progress as _,
            cache_dir: v.cache_dir.map(|p| p.to_string_lossy().into_owned()),
            acceleration: v.acceleration.map(AccelerationConfig::from),
            max_embed_duration_secs: v.max_embed_duration_secs.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::TreeSitterConfig> for TreeSitterConfig {
    fn from(v: kreuzberg::TreeSitterConfig) -> Self {
        TreeSitterConfig {
            enabled: v.enabled as _,
            cache_dir: v.cache_dir.map(|p| p.to_string_lossy().into_owned()),
            languages: v.languages.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            groups: v.groups.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            process: TreeSitterProcessConfig::from(v.process),
        }
    }
}

impl From<kreuzberg::TreeSitterProcessConfig> for TreeSitterProcessConfig {
    fn from(v: kreuzberg::TreeSitterProcessConfig) -> Self {
        TreeSitterProcessConfig {
            structure: v.structure as _,
            imports: v.imports as _,
            exports: v.exports as _,
            comments: v.comments as _,
            docstrings: v.docstrings as _,
            symbols: v.symbols as _,
            diagnostics: v.diagnostics as _,
            chunk_max_size: v.chunk_max_size.map(|x| x as _),
            content_mode: CodeContentMode::from(v.content_mode),
        }
    }
}

impl From<kreuzberg::SupportedFormat> for SupportedFormat {
    fn from(v: kreuzberg::SupportedFormat) -> Self {
        SupportedFormat {
            extension: v.extension.into(),
            mime_type: v.mime_type.into(),
        }
    }
}

impl From<kreuzberg::ServerConfig> for ServerConfig {
    fn from(v: kreuzberg::ServerConfig) -> Self {
        ServerConfig {
            host: v.host.into(),
            port: v.port as _,
            cors_origins: v.cors_origins.into_iter().map(|s| s.into()).collect(),
            max_request_body_bytes: v.max_request_body_bytes as _,
            max_multipart_field_bytes: v.max_multipart_field_bytes as _,
        }
    }
}

impl From<kreuzberg::extraction::structured::StructuredDataResult> for StructuredDataResult {
    fn from(v: kreuzberg::extraction::structured::StructuredDataResult) -> Self {
        StructuredDataResult {
            content: v.content.into(),
            format: v.format.into(),
            metadata: v.metadata.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            text_fields: v.text_fields.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::DocxAppProperties> for DocxAppProperties {
    fn from(v: kreuzberg::DocxAppProperties) -> Self {
        DocxAppProperties {
            application: v.application.map(|s| s.into()),
            app_version: v.app_version.map(|s| s.into()),
            template: v.template.map(|s| s.into()),
            total_time: v.total_time.map(|x| x as _),
            pages: v.pages.map(|x| x as _),
            words: v.words.map(|x| x as _),
            characters: v.characters.map(|x| x as _),
            characters_with_spaces: v.characters_with_spaces.map(|x| x as _),
            lines: v.lines.map(|x| x as _),
            paragraphs: v.paragraphs.map(|x| x as _),
            company: v.company.map(|s| s.into()),
            doc_security: v.doc_security.map(|x| x as _),
            scale_crop: v.scale_crop.map(|x| x as _),
            links_up_to_date: v.links_up_to_date.map(|x| x as _),
            shared_doc: v.shared_doc.map(|x| x as _),
            hyperlinks_changed: v.hyperlinks_changed.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties> for XlsxAppProperties {
    fn from(v: kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties) -> Self {
        XlsxAppProperties {
            application: v.application.map(|s| s.into()),
            app_version: v.app_version.map(|s| s.into()),
            doc_security: v.doc_security.map(|x| x as _),
            scale_crop: v.scale_crop.map(|x| x as _),
            links_up_to_date: v.links_up_to_date.map(|x| x as _),
            shared_doc: v.shared_doc.map(|x| x as _),
            hyperlinks_changed: v.hyperlinks_changed.map(|x| x as _),
            company: v.company.map(|s| s.into()),
            worksheet_names: v.worksheet_names.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties> for PptxAppProperties {
    fn from(v: kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties) -> Self {
        PptxAppProperties {
            application: v.application.map(|s| s.into()),
            app_version: v.app_version.map(|s| s.into()),
            total_time: v.total_time.map(|x| x as _),
            company: v.company.map(|s| s.into()),
            doc_security: v.doc_security.map(|x| x as _),
            scale_crop: v.scale_crop.map(|x| x as _),
            links_up_to_date: v.links_up_to_date.map(|x| x as _),
            shared_doc: v.shared_doc.map(|x| x as _),
            hyperlinks_changed: v.hyperlinks_changed.map(|x| x as _),
            slides: v.slides.map(|x| x as _),
            notes: v.notes.map(|x| x as _),
            hidden_slides: v.hidden_slides.map(|x| x as _),
            multimedia_clips: v.multimedia_clips.map(|x| x as _),
            presentation_format: v.presentation_format.map(|s| s.into()),
            slide_titles: v.slide_titles.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::CoreProperties> for CoreProperties {
    fn from(v: kreuzberg::CoreProperties) -> Self {
        CoreProperties {
            title: v.title.map(|s| s.into()),
            subject: v.subject.map(|s| s.into()),
            creator: v.creator.map(|s| s.into()),
            keywords: v.keywords.map(|s| s.into()),
            description: v.description.map(|s| s.into()),
            last_modified_by: v.last_modified_by.map(|s| s.into()),
            revision: v.revision.map(|s| s.into()),
            created: v.created.map(|s| s.into()),
            modified: v.modified.map(|s| s.into()),
            category: v.category.map(|s| s.into()),
            content_status: v.content_status.map(|s| s.into()),
            language: v.language.map(|s| s.into()),
            identifier: v.identifier.map(|s| s.into()),
            version: v.version.map(|s| s.into()),
            last_printed: v.last_printed.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::SecurityLimits> for SecurityLimits {
    fn from(v: kreuzberg::SecurityLimits) -> Self {
        SecurityLimits {
            max_archive_size: v.max_archive_size as _,
            max_compression_ratio: v.max_compression_ratio as _,
            max_files_in_archive: v.max_files_in_archive as _,
            max_nesting_depth: v.max_nesting_depth as _,
            max_entity_length: v.max_entity_length as _,
            max_content_size: v.max_content_size as _,
            max_iterations: v.max_iterations as _,
            max_xml_depth: v.max_xml_depth as _,
            max_table_cells: v.max_table_cells as _,
        }
    }
}

impl From<kreuzberg::TokenReductionConfig> for TokenReductionConfig {
    fn from(v: kreuzberg::TokenReductionConfig) -> Self {
        TokenReductionConfig {
            level: ReductionLevel::from(v.level),
            language_hint: v.language_hint.map(|s| s.into()),
            preserve_markdown: v.preserve_markdown as _,
            preserve_code: v.preserve_code as _,
            semantic_threshold: v.semantic_threshold as _,
            enable_parallel: v.enable_parallel as _,
            use_simd: v.use_simd as _,
            custom_stopwords: v
                .custom_stopwords
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
            preserve_patterns: v.preserve_patterns.into_iter().map(|s| s.into()).collect(),
            target_reduction: v.target_reduction.map(|x| x as _),
            enable_semantic_clustering: v.enable_semantic_clustering as _,
        }
    }
}

impl From<kreuzberg::PdfAnnotation> for PdfAnnotation {
    fn from(v: kreuzberg::PdfAnnotation) -> Self {
        PdfAnnotation {
            annotation_type: PdfAnnotationType::from(v.annotation_type),
            content: v.content.map(|s| s.into()),
            page_number: v.page_number as _,
            bounding_box: v.bounding_box.map(BoundingBox::from),
        }
    }
}

impl From<kreuzberg::DjotContent> for DjotContent {
    fn from(v: kreuzberg::DjotContent) -> Self {
        DjotContent {
            plain_text: v.plain_text.into(),
            blocks: v.blocks.into_iter().map(FormattedBlock::from).collect(),
            metadata: Metadata::from(v.metadata),
            tables: v.tables.into_iter().map(Table::from).collect(),
            images: v.images.into_iter().map(DjotImage::from).collect(),
            links: v.links.into_iter().map(DjotLink::from).collect(),
            footnotes: v.footnotes.into_iter().map(Footnote::from).collect(),
            attributes: Default::default(),
        }
    }
}

impl From<kreuzberg::FormattedBlock> for FormattedBlock {
    fn from(v: kreuzberg::FormattedBlock) -> Self {
        FormattedBlock {
            block_type: BlockType::from(v.block_type),
            level: v.level.map(|x| x as _),
            inline_content: v.inline_content.into_iter().map(InlineElement::from).collect(),
            attributes: Default::default(),
            language: v.language.map(|s| s.into()),
            code: v.code.map(|s| s.into()),
            children: v.children.into_iter().map(FormattedBlock::from).collect(),
        }
    }
}

impl From<kreuzberg::InlineElement> for InlineElement {
    fn from(v: kreuzberg::InlineElement) -> Self {
        InlineElement {
            element_type: InlineType::from(v.element_type),
            content: v.content.into(),
            attributes: Default::default(),
            metadata: v
                .metadata
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<kreuzberg::DjotImage> for DjotImage {
    fn from(v: kreuzberg::DjotImage) -> Self {
        DjotImage {
            src: v.src.into(),
            alt: v.alt.into(),
            title: v.title.map(|s| s.into()),
            attributes: Default::default(),
        }
    }
}

impl From<kreuzberg::DjotLink> for DjotLink {
    fn from(v: kreuzberg::DjotLink) -> Self {
        DjotLink {
            url: v.url.into(),
            text: v.text.into(),
            title: v.title.map(|s| s.into()),
            attributes: Default::default(),
        }
    }
}

impl From<kreuzberg::Footnote> for Footnote {
    fn from(v: kreuzberg::Footnote) -> Self {
        Footnote {
            label: v.label.into(),
            content: v.content.into_iter().map(FormattedBlock::from).collect(),
        }
    }
}

impl From<kreuzberg::DocumentStructure> for DocumentStructure {
    fn from(v: kreuzberg::DocumentStructure) -> Self {
        DocumentStructure {
            nodes: v.nodes.into_iter().map(DocumentNode::from).collect(),
            source_format: v.source_format.map(|s| s.into()),
            relationships: v.relationships.into_iter().map(DocumentRelationship::from).collect(),
            node_types: v.node_types.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::DocumentRelationship> for DocumentRelationship {
    fn from(v: kreuzberg::DocumentRelationship) -> Self {
        DocumentRelationship {
            source: v.source.0 as _,
            target: v.target.0 as _,
            kind: RelationshipKind::from(v.kind),
        }
    }
}

impl From<kreuzberg::DocumentNode> for DocumentNode {
    fn from(v: kreuzberg::DocumentNode) -> Self {
        DocumentNode {
            id: Default::default(),
            content: NodeContent::from(v.content),
            parent: v.parent.map(|x| x.0 as _),
            children: v.children.into_iter().map(|x| x.0 as _).collect(),
            content_layer: ContentLayer::from(v.content_layer),
            page: v.page.map(|x| x as _),
            page_end: v.page_end.map(|x| x as _),
            bbox: v.bbox.map(BoundingBox::from),
            annotations: v.annotations.into_iter().map(TextAnnotation::from).collect(),
            attributes: v
                .attributes
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<kreuzberg::TableGrid> for TableGrid {
    fn from(v: kreuzberg::TableGrid) -> Self {
        TableGrid {
            rows: v.rows as _,
            cols: v.cols as _,
            cells: v.cells.into_iter().map(GridCell::from).collect(),
        }
    }
}

impl From<kreuzberg::GridCell> for GridCell {
    fn from(v: kreuzberg::GridCell) -> Self {
        GridCell {
            content: v.content.into(),
            row: v.row as _,
            col: v.col as _,
            row_span: v.row_span as _,
            col_span: v.col_span as _,
            is_header: v.is_header as _,
            bbox: v.bbox.map(BoundingBox::from),
        }
    }
}

impl From<kreuzberg::TextAnnotation> for TextAnnotation {
    fn from(v: kreuzberg::TextAnnotation) -> Self {
        TextAnnotation {
            start: v.start as _,
            end: v.end as _,
            kind: AnnotationKind::from(v.kind),
        }
    }
}

impl From<kreuzberg::ExtractionResult> for ExtractionResult {
    fn from(v: kreuzberg::ExtractionResult) -> Self {
        ExtractionResult {
            content: v.content.into(),
            mime_type: v.mime_type.into(),
            metadata: Metadata::from(v.metadata),
            extraction_method: v.extraction_method.map(ExtractionMethod::from),
            tables: v.tables.into_iter().map(Table::from).collect(),
            detected_languages: v
                .detected_languages
                .map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            chunks: v.chunks.map(|vec| vec.into_iter().map(Chunk::from).collect()),
            images: v.images.map(|vec| vec.into_iter().map(ExtractedImage::from).collect()),
            pages: v.pages.map(|vec| vec.into_iter().map(PageContent::from).collect()),
            elements: v.elements.map(|vec| vec.into_iter().map(Element::from).collect()),
            djot_content: v.djot_content.map(DjotContent::from),
            ocr_elements: v
                .ocr_elements
                .map(|vec| vec.into_iter().map(OcrElement::from).collect()),
            document: v.document.map(DocumentStructure::from),
            extracted_keywords: v
                .extracted_keywords
                .map(|vec| vec.into_iter().map(Keyword::from).collect()),
            quality_score: v.quality_score.map(|x| x as _),
            processing_warnings: v.processing_warnings.into_iter().map(ProcessingWarning::from).collect(),
            annotations: v
                .annotations
                .map(|vec| vec.into_iter().map(PdfAnnotation::from).collect()),
            children: v.children.map(|vec| vec.into_iter().map(ArchiveEntry::from).collect()),
            uris: v.uris.map(|vec| vec.into_iter().map(ExtractedUri::from).collect()),
            revisions: v
                .revisions
                .map(|vec| vec.into_iter().map(DocumentRevision::from).collect()),
            structured_output: v
                .structured_output
                .map(|j| serde_json::to_string(&j).unwrap_or_default()),
            code_intelligence: v
                .code_intelligence
                .map(|j| serde_json::to_string(&j).unwrap_or_default()),
            llm_usage: v.llm_usage.map(|vec| vec.into_iter().map(LlmUsage::from).collect()),
            formatted_content: v.formatted_content.map(|s| s.into()),
            ocr_internal_document: Default::default(),
        }
    }
}

impl From<kreuzberg::ArchiveEntry> for ArchiveEntry {
    fn from(v: kreuzberg::ArchiveEntry) -> Self {
        ArchiveEntry {
            path: v.path.into(),
            mime_type: v.mime_type.into(),
            result: ExtractionResult::from(*v.result),
        }
    }
}

impl From<kreuzberg::ProcessingWarning> for ProcessingWarning {
    fn from(v: kreuzberg::ProcessingWarning) -> Self {
        ProcessingWarning {
            source: v.source.into(),
            message: v.message.into(),
        }
    }
}

impl From<kreuzberg::LlmUsage> for LlmUsage {
    fn from(v: kreuzberg::LlmUsage) -> Self {
        LlmUsage {
            model: v.model.into(),
            source: v.source.into(),
            input_tokens: v.input_tokens.map(|x| x as _),
            output_tokens: v.output_tokens.map(|x| x as _),
            total_tokens: v.total_tokens.map(|x| x as _),
            estimated_cost: v.estimated_cost.map(|x| x as _),
            finish_reason: v.finish_reason.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::Chunk> for Chunk {
    fn from(v: kreuzberg::Chunk) -> Self {
        Chunk {
            content: v.content.into(),
            chunk_type: ChunkType::from(v.chunk_type),
            embedding: v.embedding.map(|vec| vec.into_iter().map(|x| x as _).collect()),
            metadata: ChunkMetadata::from(v.metadata),
        }
    }
}

impl From<kreuzberg::HeadingContext> for HeadingContext {
    fn from(v: kreuzberg::HeadingContext) -> Self {
        HeadingContext {
            headings: v.headings.into_iter().map(HeadingLevel::from).collect(),
        }
    }
}

impl From<kreuzberg::HeadingLevel> for HeadingLevel {
    fn from(v: kreuzberg::HeadingLevel) -> Self {
        HeadingLevel {
            level: v.level as _,
            text: v.text.into(),
        }
    }
}

impl From<kreuzberg::ChunkMetadata> for ChunkMetadata {
    fn from(v: kreuzberg::ChunkMetadata) -> Self {
        ChunkMetadata {
            byte_start: v.byte_start as _,
            byte_end: v.byte_end as _,
            token_count: v.token_count.map(|x| x as _),
            chunk_index: v.chunk_index as _,
            total_chunks: v.total_chunks as _,
            first_page: v.first_page.map(|x| x as _),
            last_page: v.last_page.map(|x| x as _),
            heading_context: v.heading_context.map(HeadingContext::from),
            image_indices: v.image_indices.into_iter().map(|x| x as _).collect(),
        }
    }
}

impl From<kreuzberg::ExtractedImage> for ExtractedImage {
    fn from(v: kreuzberg::ExtractedImage) -> Self {
        ExtractedImage {
            data: v.data.into(),
            format: v.format.into(),
            image_index: v.image_index as _,
            page_number: v.page_number.map(|x| x as _),
            width: v.width.map(|x| x as _),
            height: v.height.map(|x| x as _),
            colorspace: v.colorspace.map(|s| s.into()),
            bits_per_component: v.bits_per_component.map(|x| x as _),
            is_mask: v.is_mask as _,
            description: v.description.map(|s| s.into()),
            ocr_result: v.ocr_result.map(|b| ExtractionResult::from(*b)),
            bounding_box: v.bounding_box.map(BoundingBox::from),
            source_path: v.source_path.map(|s| s.into()),
            image_kind: v.image_kind.map(ImageKind::from),
            kind_confidence: v.kind_confidence.map(|x| x as _),
            cluster_id: v.cluster_id.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::BoundingBox> for BoundingBox {
    fn from(v: kreuzberg::BoundingBox) -> Self {
        BoundingBox {
            x0: v.x0 as _,
            y0: v.y0 as _,
            x1: v.x1 as _,
            y1: v.y1 as _,
        }
    }
}

impl From<kreuzberg::ElementMetadata> for ElementMetadata {
    fn from(v: kreuzberg::ElementMetadata) -> Self {
        ElementMetadata {
            page_number: v.page_number.map(|x| x as _),
            filename: v.filename.map(|s| s.into()),
            coordinates: v.coordinates.map(BoundingBox::from),
            element_index: v.element_index.map(|x| x as _),
            additional: v.additional.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

impl From<kreuzberg::Element> for Element {
    fn from(v: kreuzberg::Element) -> Self {
        Element {
            element_id: Default::default(),
            element_type: ElementType::from(v.element_type),
            text: v.text.into(),
            metadata: ElementMetadata::from(v.metadata),
        }
    }
}

impl From<kreuzberg::ExcelWorkbook> for ExcelWorkbook {
    fn from(v: kreuzberg::ExcelWorkbook) -> Self {
        ExcelWorkbook {
            sheets: v.sheets.into_iter().map(ExcelSheet::from).collect(),
            metadata: v.metadata.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            revisions: v
                .revisions
                .map(|vec| vec.into_iter().map(DocumentRevision::from).collect()),
        }
    }
}

impl From<kreuzberg::ExcelSheet> for ExcelSheet {
    fn from(v: kreuzberg::ExcelSheet) -> Self {
        ExcelSheet {
            name: v.name.into(),
            markdown: v.markdown.into(),
            row_count: v.row_count as _,
            col_count: v.col_count as _,
            cell_count: v.cell_count as _,
            table_cells: v.table_cells,
        }
    }
}

impl From<kreuzberg::XmlExtractionResult> for XmlExtractionResult {
    fn from(v: kreuzberg::XmlExtractionResult) -> Self {
        XmlExtractionResult {
            content: v.content.into(),
            element_count: v.element_count as _,
            unique_elements: v.unique_elements.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::TextExtractionResult> for TextExtractionResult {
    fn from(v: kreuzberg::TextExtractionResult) -> Self {
        TextExtractionResult {
            content: v.content.into(),
            line_count: v.line_count as _,
            word_count: v.word_count as _,
            character_count: v.character_count as _,
            headers: v.headers.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            links: Default::default(),
            code_blocks: Default::default(),
        }
    }
}

impl From<kreuzberg::PptxExtractionResult> for PptxExtractionResult {
    fn from(v: kreuzberg::PptxExtractionResult) -> Self {
        PptxExtractionResult {
            content: v.content.into(),
            metadata: PptxMetadata::from(v.metadata),
            slide_count: v.slide_count as _,
            image_count: v.image_count as _,
            table_count: v.table_count as _,
            images: v.images.into_iter().map(ExtractedImage::from).collect(),
            page_structure: v.page_structure.map(PageStructure::from),
            page_contents: v
                .page_contents
                .map(|vec| vec.into_iter().map(PageContent::from).collect()),
            document: v.document.map(DocumentStructure::from),
            hyperlinks: Default::default(),
            office_metadata: v
                .office_metadata
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
            revisions: v
                .revisions
                .map(|vec| vec.into_iter().map(DocumentRevision::from).collect()),
        }
    }
}

impl From<kreuzberg::EmailExtractionResult> for EmailExtractionResult {
    fn from(v: kreuzberg::EmailExtractionResult) -> Self {
        EmailExtractionResult {
            subject: v.subject.map(|s| s.into()),
            from_email: v.from_email.map(|s| s.into()),
            to_emails: v.to_emails.into_iter().map(|s| s.into()).collect(),
            cc_emails: v.cc_emails.into_iter().map(|s| s.into()).collect(),
            bcc_emails: v.bcc_emails.into_iter().map(|s| s.into()).collect(),
            date: v.date.map(|s| s.into()),
            message_id: v.message_id.map(|s| s.into()),
            plain_text: v.plain_text.map(|s| s.into()),
            html_content: v.html_content.map(|s| s.into()),
            content: v.content.into(),
            attachments: v.attachments.into_iter().map(EmailAttachment::from).collect(),
            metadata: v.metadata.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

impl From<kreuzberg::EmailAttachment> for EmailAttachment {
    fn from(v: kreuzberg::EmailAttachment) -> Self {
        EmailAttachment {
            name: v.name.map(|s| s.into()),
            filename: v.filename.map(|s| s.into()),
            mime_type: v.mime_type.map(|s| s.into()),
            size: v.size.map(|x| x as _),
            is_image: v.is_image as _,
            data: v.data.map(|b| b.into()),
        }
    }
}

impl From<kreuzberg::OcrExtractionResult> for OcrExtractionResult {
    fn from(v: kreuzberg::OcrExtractionResult) -> Self {
        OcrExtractionResult {
            content: v.content.into(),
            mime_type: v.mime_type.into(),
            metadata: v
                .metadata
                .into_iter()
                .map(|(k, v)| (k.into(), serde_json::to_string(&v).unwrap_or_default()))
                .collect(),
            tables: v.tables.into_iter().map(OcrTable::from).collect(),
            ocr_elements: v
                .ocr_elements
                .map(|vec| vec.into_iter().map(OcrElement::from).collect()),
            internal_document: Default::default(),
        }
    }
}

impl From<kreuzberg::OcrTable> for OcrTable {
    fn from(v: kreuzberg::OcrTable) -> Self {
        OcrTable {
            cells: v.cells,
            markdown: v.markdown.into(),
            page_number: v.page_number as _,
            bounding_box: v.bounding_box.map(OcrTableBoundingBox::from),
        }
    }
}

impl From<kreuzberg::OcrTableBoundingBox> for OcrTableBoundingBox {
    fn from(v: kreuzberg::OcrTableBoundingBox) -> Self {
        OcrTableBoundingBox {
            left: v.left as _,
            top: v.top as _,
            right: v.right as _,
            bottom: v.bottom as _,
        }
    }
}

impl From<kreuzberg::ImagePreprocessingConfig> for ImagePreprocessingConfig {
    fn from(v: kreuzberg::ImagePreprocessingConfig) -> Self {
        ImagePreprocessingConfig {
            target_dpi: v.target_dpi as _,
            auto_rotate: v.auto_rotate as _,
            deskew: v.deskew as _,
            denoise: v.denoise as _,
            contrast_enhance: v.contrast_enhance as _,
            binarization_method: v.binarization_method.into(),
            invert_colors: v.invert_colors as _,
        }
    }
}

impl From<kreuzberg::TesseractConfig> for TesseractConfig {
    fn from(v: kreuzberg::TesseractConfig) -> Self {
        TesseractConfig {
            language: v.language.into(),
            psm: v.psm as _,
            output_format: v.output_format.into(),
            oem: v.oem as _,
            min_confidence: v.min_confidence as _,
            preprocessing: v.preprocessing.map(ImagePreprocessingConfig::from),
            enable_table_detection: v.enable_table_detection as _,
            table_min_confidence: v.table_min_confidence as _,
            table_column_threshold: v.table_column_threshold as _,
            table_row_threshold_ratio: v.table_row_threshold_ratio as _,
            use_cache: v.use_cache as _,
            classify_use_pre_adapted_templates: v.classify_use_pre_adapted_templates as _,
            language_model_ngram_on: v.language_model_ngram_on as _,
            tessedit_dont_blkrej_good_wds: v.tessedit_dont_blkrej_good_wds as _,
            tessedit_dont_rowrej_good_wds: v.tessedit_dont_rowrej_good_wds as _,
            tessedit_enable_dict_correction: v.tessedit_enable_dict_correction as _,
            tessedit_char_whitelist: v.tessedit_char_whitelist.into(),
            tessedit_char_blacklist: v.tessedit_char_blacklist.into(),
            tessedit_use_primary_params_model: v.tessedit_use_primary_params_model as _,
            textord_space_size_is_variable: v.textord_space_size_is_variable as _,
            thresholding_method: v.thresholding_method as _,
        }
    }
}

impl From<kreuzberg::ImagePreprocessingMetadata> for ImagePreprocessingMetadata {
    fn from(v: kreuzberg::ImagePreprocessingMetadata) -> Self {
        ImagePreprocessingMetadata {
            original_dimensions: Default::default(),
            original_dpi: Default::default(),
            target_dpi: v.target_dpi as _,
            scale_factor: v.scale_factor as _,
            auto_adjusted: v.auto_adjusted as _,
            final_dpi: v.final_dpi as _,
            new_dimensions: Default::default(),
            resample_method: v.resample_method.into(),
            dimension_clamped: v.dimension_clamped as _,
            calculated_dpi: v.calculated_dpi.map(|x| x as _),
            skipped_resize: v.skipped_resize as _,
            resize_error: v.resize_error.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::Metadata> for Metadata {
    fn from(v: kreuzberg::Metadata) -> Self {
        Metadata {
            title: v.title.map(|s| s.into()),
            subject: v.subject.map(|s| s.into()),
            authors: v.authors.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            keywords: v.keywords.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            language: v.language.map(|s| s.into()),
            created_at: v.created_at.map(|s| s.into()),
            modified_at: v.modified_at.map(|s| s.into()),
            created_by: v.created_by.map(|s| s.into()),
            modified_by: v.modified_by.map(|s| s.into()),
            pages: v.pages.map(PageStructure::from),
            format: v.format.map(FormatMetadata::from),
            image_preprocessing: v.image_preprocessing.map(ImagePreprocessingMetadata::from),
            json_schema: v.json_schema.map(|j| serde_json::to_string(&j).unwrap_or_default()),
            error: v.error.map(ErrorMetadata::from),
            extraction_duration_ms: v.extraction_duration_ms.map(|x| x as _),
            category: v.category.map(|s| s.into()),
            tags: v.tags.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            document_version: v.document_version.map(|s| s.into()),
            abstract_text: v.abstract_text.map(|s| s.into()),
            output_format: v.output_format.map(|s| s.into()),
            ocr_used: v.ocr_used as _,
            additional: v
                .additional
                .into_iter()
                .map(|(k, v)| (k.into(), serde_json::to_string(&v).unwrap_or_default()))
                .collect(),
        }
    }
}

impl From<kreuzberg::ExcelMetadata> for ExcelMetadata {
    fn from(v: kreuzberg::ExcelMetadata) -> Self {
        ExcelMetadata {
            sheet_count: v.sheet_count.map(|x| x as _),
            sheet_names: v.sheet_names.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
        }
    }
}

impl From<kreuzberg::EmailMetadata> for EmailMetadata {
    fn from(v: kreuzberg::EmailMetadata) -> Self {
        EmailMetadata {
            from_email: v.from_email.map(|s| s.into()),
            from_name: v.from_name.map(|s| s.into()),
            to_emails: v.to_emails.into_iter().map(|s| s.into()).collect(),
            cc_emails: v.cc_emails.into_iter().map(|s| s.into()).collect(),
            bcc_emails: v.bcc_emails.into_iter().map(|s| s.into()).collect(),
            message_id: v.message_id.map(|s| s.into()),
            attachments: v.attachments.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::ArchiveMetadata> for ArchiveMetadata {
    fn from(v: kreuzberg::ArchiveMetadata) -> Self {
        ArchiveMetadata {
            format: v.format.into(),
            file_count: v.file_count as _,
            file_list: v.file_list.into_iter().map(|s| s.into()).collect(),
            total_size: v.total_size as _,
            compressed_size: v.compressed_size.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::ImageMetadata> for ImageMetadata {
    fn from(v: kreuzberg::ImageMetadata) -> Self {
        ImageMetadata {
            width: v.width as _,
            height: v.height as _,
            format: v.format.into(),
            exif: v.exif.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

impl From<kreuzberg::XmlMetadata> for XmlMetadata {
    fn from(v: kreuzberg::XmlMetadata) -> Self {
        XmlMetadata {
            element_count: v.element_count as _,
            unique_elements: v.unique_elements.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::TextMetadata> for TextMetadata {
    fn from(v: kreuzberg::TextMetadata) -> Self {
        TextMetadata {
            line_count: v.line_count as _,
            word_count: v.word_count as _,
            character_count: v.character_count as _,
            headers: v.headers.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
            links: Default::default(),
            code_blocks: Default::default(),
        }
    }
}

impl From<kreuzberg::HeaderMetadata> for HeaderMetadata {
    fn from(v: kreuzberg::HeaderMetadata) -> Self {
        HeaderMetadata {
            level: v.level as _,
            text: v.text.into(),
            id: v.id.map(|s| s.into()),
            depth: v.depth as _,
            html_offset: v.html_offset as _,
        }
    }
}

impl From<kreuzberg::LinkMetadata> for LinkMetadata {
    fn from(v: kreuzberg::LinkMetadata) -> Self {
        LinkMetadata {
            href: v.href.into(),
            text: v.text.into(),
            title: v.title.map(|s| s.into()),
            link_type: LinkType::from(v.link_type),
            rel: v.rel.into_iter().map(|s| s.into()).collect(),
            attributes: Default::default(),
        }
    }
}

impl From<kreuzberg::ImageMetadataType> for ImageMetadataType {
    fn from(v: kreuzberg::ImageMetadataType) -> Self {
        ImageMetadataType {
            src: v.src.into(),
            alt: v.alt.map(|s| s.into()),
            title: v.title.map(|s| s.into()),
            dimensions: Default::default(),
            image_type: ImageType::from(v.image_type),
            attributes: Default::default(),
        }
    }
}

impl From<kreuzberg::StructuredData> for StructuredData {
    fn from(v: kreuzberg::StructuredData) -> Self {
        StructuredData {
            data_type: StructuredDataType::from(v.data_type),
            raw_json: v.raw_json.into(),
            schema_type: v.schema_type.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::HtmlMetadata> for HtmlMetadata {
    fn from(v: kreuzberg::HtmlMetadata) -> Self {
        HtmlMetadata {
            title: v.title.map(|s| s.into()),
            description: v.description.map(|s| s.into()),
            keywords: v.keywords.into_iter().map(|s| s.into()).collect(),
            author: v.author.map(|s| s.into()),
            canonical_url: v.canonical_url.map(|s| s.into()),
            base_href: v.base_href.map(|s| s.into()),
            language: v.language.map(|s| s.into()),
            text_direction: v.text_direction.map(TextDirection::from),
            open_graph: v.open_graph.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            twitter_card: v.twitter_card.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            meta_tags: v.meta_tags.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            headers: v.headers.into_iter().map(HeaderMetadata::from).collect(),
            links: v.links.into_iter().map(LinkMetadata::from).collect(),
            images: v.images.into_iter().map(ImageMetadataType::from).collect(),
            structured_data: v.structured_data.into_iter().map(StructuredData::from).collect(),
        }
    }
}

impl From<kreuzberg::OcrMetadata> for OcrMetadata {
    fn from(v: kreuzberg::OcrMetadata) -> Self {
        OcrMetadata {
            language: v.language.into(),
            psm: v.psm as _,
            output_format: v.output_format.into(),
            table_count: v.table_count as _,
            table_rows: v.table_rows.map(|x| x as _),
            table_cols: v.table_cols.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::ErrorMetadata> for ErrorMetadata {
    fn from(v: kreuzberg::ErrorMetadata) -> Self {
        ErrorMetadata {
            error_type: v.error_type.into(),
            message: v.message.into(),
        }
    }
}

impl From<kreuzberg::PptxMetadata> for PptxMetadata {
    fn from(v: kreuzberg::PptxMetadata) -> Self {
        PptxMetadata {
            slide_count: v.slide_count as _,
            slide_names: v.slide_names.into_iter().map(|s| s.into()).collect(),
            image_count: v.image_count.map(|x| x as _),
            table_count: v.table_count.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::DocxMetadata> for DocxMetadata {
    fn from(v: kreuzberg::DocxMetadata) -> Self {
        DocxMetadata {
            core_properties: v.core_properties.map(CoreProperties::from),
            app_properties: v.app_properties.map(DocxAppProperties::from),
            custom_properties: v.custom_properties.map(|m| {
                m.into_iter()
                    .map(|(k, v)| (k.into(), serde_json::to_string(&v).unwrap_or_default()))
                    .collect()
            }),
        }
    }
}

impl From<kreuzberg::CsvMetadata> for CsvMetadata {
    fn from(v: kreuzberg::CsvMetadata) -> Self {
        CsvMetadata {
            row_count: v.row_count as _,
            column_count: v.column_count as _,
            delimiter: v.delimiter.map(|s| s.into()),
            has_header: v.has_header as _,
            column_types: v.column_types.map(|vec| vec.into_iter().map(|s| s.into()).collect()),
        }
    }
}

impl From<kreuzberg::BibtexMetadata> for BibtexMetadata {
    fn from(v: kreuzberg::BibtexMetadata) -> Self {
        BibtexMetadata {
            entry_count: v.entry_count as _,
            citation_keys: v.citation_keys.into_iter().map(|s| s.into()).collect(),
            authors: v.authors.into_iter().map(|s| s.into()).collect(),
            year_range: v.year_range.map(YearRange::from),
            entry_types: v
                .entry_types
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v as _)).collect()),
        }
    }
}

impl From<kreuzberg::CitationMetadata> for CitationMetadata {
    fn from(v: kreuzberg::CitationMetadata) -> Self {
        CitationMetadata {
            citation_count: v.citation_count as _,
            format: v.format.map(|s| s.into()),
            authors: v.authors.into_iter().map(|s| s.into()).collect(),
            year_range: v.year_range.map(YearRange::from),
            dois: v.dois.into_iter().map(|s| s.into()).collect(),
            keywords: v.keywords.into_iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<kreuzberg::YearRange> for YearRange {
    fn from(v: kreuzberg::YearRange) -> Self {
        YearRange {
            min: v.min.map(|x| x as _),
            max: v.max.map(|x| x as _),
            years: v.years.into_iter().map(|x| x as _).collect(),
        }
    }
}

impl From<kreuzberg::FictionBookMetadata> for FictionBookMetadata {
    fn from(v: kreuzberg::FictionBookMetadata) -> Self {
        FictionBookMetadata {
            genres: v.genres.into_iter().map(|s| s.into()).collect(),
            sequences: v.sequences.into_iter().map(|s| s.into()).collect(),
            annotation: v.annotation.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::DbfMetadata> for DbfMetadata {
    fn from(v: kreuzberg::DbfMetadata) -> Self {
        DbfMetadata {
            record_count: v.record_count as _,
            field_count: v.field_count as _,
            fields: v.fields.into_iter().map(DbfFieldInfo::from).collect(),
        }
    }
}

impl From<kreuzberg::DbfFieldInfo> for DbfFieldInfo {
    fn from(v: kreuzberg::DbfFieldInfo) -> Self {
        DbfFieldInfo {
            name: v.name.into(),
            field_type: v.field_type.into(),
        }
    }
}

impl From<kreuzberg::JatsMetadata> for JatsMetadata {
    fn from(v: kreuzberg::JatsMetadata) -> Self {
        JatsMetadata {
            copyright: v.copyright.map(|s| s.into()),
            license: v.license.map(|s| s.into()),
            history_dates: v.history_dates.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            contributor_roles: v.contributor_roles.into_iter().map(ContributorRole::from).collect(),
        }
    }
}

impl From<kreuzberg::ContributorRole> for ContributorRole {
    fn from(v: kreuzberg::ContributorRole) -> Self {
        ContributorRole {
            name: v.name.into(),
            role: v.role.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::EpubMetadata> for EpubMetadata {
    fn from(v: kreuzberg::EpubMetadata) -> Self {
        EpubMetadata {
            coverage: v.coverage.map(|s| s.into()),
            dc_format: v.dc_format.map(|s| s.into()),
            relation: v.relation.map(|s| s.into()),
            source: v.source.map(|s| s.into()),
            dc_type: v.dc_type.map(|s| s.into()),
            cover_image: v.cover_image.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::PstMetadata> for PstMetadata {
    fn from(v: kreuzberg::PstMetadata) -> Self {
        PstMetadata {
            message_count: v.message_count as _,
        }
    }
}

impl From<kreuzberg::OcrConfidence> for OcrConfidence {
    fn from(v: kreuzberg::OcrConfidence) -> Self {
        OcrConfidence {
            detection: v.detection.map(|x| x as _),
            recognition: v.recognition as _,
        }
    }
}

impl From<kreuzberg::OcrRotation> for OcrRotation {
    fn from(v: kreuzberg::OcrRotation) -> Self {
        OcrRotation {
            angle_degrees: v.angle_degrees as _,
            confidence: v.confidence.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::OcrElement> for OcrElement {
    fn from(v: kreuzberg::OcrElement) -> Self {
        OcrElement {
            text: v.text.into(),
            geometry: OcrBoundingGeometry::from(v.geometry),
            confidence: OcrConfidence::from(v.confidence),
            level: OcrElementLevel::from(v.level),
            rotation: v.rotation.map(OcrRotation::from),
            page_number: v.page_number as _,
            parent_id: v.parent_id.map(|s| s.into()),
            backend_metadata: v
                .backend_metadata
                .into_iter()
                .map(|(k, v)| (k.into(), serde_json::to_string(&v).unwrap_or_default()))
                .collect(),
        }
    }
}

impl From<kreuzberg::OcrElementConfig> for OcrElementConfig {
    fn from(v: kreuzberg::OcrElementConfig) -> Self {
        OcrElementConfig {
            include_elements: v.include_elements as _,
            min_level: OcrElementLevel::from(v.min_level),
            min_confidence: v.min_confidence as _,
            build_hierarchy: v.build_hierarchy as _,
        }
    }
}

impl From<kreuzberg::PageStructure> for PageStructure {
    fn from(v: kreuzberg::PageStructure) -> Self {
        PageStructure {
            total_count: v.total_count as _,
            unit_type: PageUnitType::from(v.unit_type),
            boundaries: v
                .boundaries
                .map(|vec| vec.into_iter().map(PageBoundary::from).collect()),
            pages: v.pages.map(|vec| vec.into_iter().map(PageInfo::from).collect()),
        }
    }
}

impl From<kreuzberg::PageBoundary> for PageBoundary {
    fn from(v: kreuzberg::PageBoundary) -> Self {
        PageBoundary {
            byte_start: v.byte_start as _,
            byte_end: v.byte_end as _,
            page_number: v.page_number as _,
        }
    }
}

impl From<kreuzberg::PageInfo> for PageInfo {
    fn from(v: kreuzberg::PageInfo) -> Self {
        PageInfo {
            number: v.number as _,
            title: v.title.map(|s| s.into()),
            dimensions: Default::default(),
            image_count: v.image_count.map(|x| x as _),
            table_count: v.table_count.map(|x| x as _),
            hidden: v.hidden.map(|x| x as _),
            is_blank: v.is_blank.map(|x| x as _),
            has_vector_graphics: v.has_vector_graphics as _,
        }
    }
}

impl From<kreuzberg::PageContent> for PageContent {
    fn from(v: kreuzberg::PageContent) -> Self {
        PageContent {
            page_number: v.page_number as _,
            content: v.content.into(),
            tables: v.tables.into_iter().map(|a| Table::from((*a).clone())).collect(),
            image_indices: v.image_indices.into_iter().map(|x| x as _).collect(),
            hierarchy: v.hierarchy.map(PageHierarchy::from),
            is_blank: v.is_blank.map(|x| x as _),
            layout_regions: v
                .layout_regions
                .map(|vec| vec.into_iter().map(LayoutRegion::from).collect()),
            speaker_notes: v.speaker_notes.map(|s| s.into()),
            section_name: v.section_name.map(|s| s.into()),
            sheet_name: v.sheet_name.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::LayoutRegion> for LayoutRegion {
    fn from(v: kreuzberg::LayoutRegion) -> Self {
        LayoutRegion {
            class_name: v.class_name.into(),
            confidence: v.confidence as _,
            bounding_box: BoundingBox::from(v.bounding_box),
            area_fraction: v.area_fraction as _,
        }
    }
}

impl From<kreuzberg::PageHierarchy> for PageHierarchy {
    fn from(v: kreuzberg::PageHierarchy) -> Self {
        PageHierarchy {
            block_count: v.block_count as _,
            blocks: v.blocks.into_iter().map(HierarchicalBlock::from).collect(),
        }
    }
}

impl From<kreuzberg::HierarchicalBlock> for HierarchicalBlock {
    fn from(v: kreuzberg::HierarchicalBlock) -> Self {
        HierarchicalBlock {
            text: v.text.into(),
            font_size: v.font_size as _,
            level: v.level.into(),
            bbox: Default::default(),
        }
    }
}

impl From<kreuzberg::CellChange> for CellChange {
    fn from(v: kreuzberg::CellChange) -> Self {
        CellChange {
            row: v.row as _,
            col: v.col as _,
            from: v.from.into(),
            to: v.to.into(),
        }
    }
}

impl From<kreuzberg::DocumentRevision> for DocumentRevision {
    fn from(v: kreuzberg::DocumentRevision) -> Self {
        DocumentRevision {
            revision_id: v.revision_id.into(),
            author: v.author.map(|s| s.into()),
            timestamp: v.timestamp.map(|s| s.into()),
            kind: RevisionKind::from(v.kind),
            anchor: v.anchor.map(RevisionAnchor::from),
            delta: RevisionDelta::from(v.delta),
        }
    }
}

impl From<kreuzberg::RevisionDelta> for RevisionDelta {
    fn from(v: kreuzberg::RevisionDelta) -> Self {
        RevisionDelta {
            content: v.content.into_iter().map(DiffLine::from).collect(),
            table_changes: v.table_changes.into_iter().map(CellChange::from).collect(),
        }
    }
}

impl From<kreuzberg::Table> for Table {
    fn from(v: kreuzberg::Table) -> Self {
        Table {
            cells: v.cells,
            markdown: v.markdown.into(),
            page_number: v.page_number as _,
            bounding_box: v.bounding_box.map(BoundingBox::from),
        }
    }
}

impl From<kreuzberg::TableCell> for TableCell {
    fn from(v: kreuzberg::TableCell) -> Self {
        TableCell {
            content: v.content.into(),
            row_span: v.row_span as _,
            col_span: v.col_span as _,
            is_header: v.is_header as _,
        }
    }
}

impl From<kreuzberg::ExtractedUri> for ExtractedUri {
    fn from(v: kreuzberg::ExtractedUri) -> Self {
        ExtractedUri {
            url: v.url.into(),
            label: v.label.map(|s| s.into()),
            page: v.page.map(|x| x as _),
            kind: UriKind::from(v.kind),
        }
    }
}

impl From<kreuzberg::api::DetectResponse> for DetectResponse {
    fn from(v: kreuzberg::api::DetectResponse) -> Self {
        DetectResponse {
            mime_type: v.mime_type.into(),
            filename: v.filename.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::DiffOptions> for DiffOptions {
    fn from(v: kreuzberg::DiffOptions) -> Self {
        DiffOptions {
            include_metadata: v.include_metadata as _,
            include_embedded: v.include_embedded as _,
            max_content_chars: v.max_content_chars.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::ExtractionDiff> for ExtractionDiff {
    fn from(v: kreuzberg::ExtractionDiff) -> Self {
        ExtractionDiff {
            content_diff: v.content_diff.into_iter().map(DiffHunk::from).collect(),
            tables_added: v.tables_added.into_iter().map(Table::from).collect(),
            tables_removed: v.tables_removed.into_iter().map(Table::from).collect(),
            tables_changed: v.tables_changed.into_iter().map(TableDiff::from).collect(),
            metadata_changed: serde_json::to_string(&v.metadata_changed).unwrap_or_default(),
            embedded_changes: EmbeddedChanges::from(v.embedded_changes),
        }
    }
}

impl From<kreuzberg::DiffHunk> for DiffHunk {
    fn from(v: kreuzberg::DiffHunk) -> Self {
        DiffHunk {
            from_line: v.from_line as _,
            from_count: v.from_count as _,
            to_line: v.to_line as _,
            to_count: v.to_count as _,
            lines: v.lines.into_iter().map(DiffLine::from).collect(),
        }
    }
}

impl From<kreuzberg::TableDiff> for TableDiff {
    fn from(v: kreuzberg::TableDiff) -> Self {
        TableDiff {
            from_index: v.from_index as _,
            to_index: v.to_index as _,
            cell_changes: v.cell_changes.into_iter().map(CellChange::from).collect(),
        }
    }
}

impl From<kreuzberg::EmbeddedChanges> for EmbeddedChanges {
    fn from(v: kreuzberg::EmbeddedChanges) -> Self {
        EmbeddedChanges {
            added: v.added.into_iter().map(ArchiveEntry::from).collect(),
            removed: v.removed.into_iter().map(ArchiveEntry::from).collect(),
            changed: v.changed.into_iter().map(EmbeddedDiff::from).collect(),
        }
    }
}

impl From<kreuzberg::EmbeddedDiff> for EmbeddedDiff {
    fn from(v: kreuzberg::EmbeddedDiff) -> Self {
        EmbeddedDiff {
            path: v.path.into(),
            diff: ExtractionDiff::from(*v.diff),
        }
    }
}

impl From<kreuzberg::EmbeddingPreset> for EmbeddingPreset {
    fn from(v: kreuzberg::EmbeddingPreset) -> Self {
        EmbeddingPreset {
            name: v.name.into(),
            chunk_size: v.chunk_size as _,
            overlap: v.overlap as _,
            model_repo: v.model_repo.into(),
            pooling: v.pooling.into(),
            model_file: v.model_file.into(),
            dimensions: v.dimensions as _,
            description: v.description.into(),
        }
    }
}

impl From<kreuzberg::YakeParams> for YakeParams {
    fn from(v: kreuzberg::YakeParams) -> Self {
        YakeParams {
            window_size: v.window_size as _,
        }
    }
}

impl From<kreuzberg::RakeParams> for RakeParams {
    fn from(v: kreuzberg::RakeParams) -> Self {
        RakeParams {
            min_word_length: v.min_word_length as _,
            max_words_per_phrase: v.max_words_per_phrase as _,
        }
    }
}

impl From<kreuzberg::KeywordConfig> for KeywordConfig {
    fn from(v: kreuzberg::KeywordConfig) -> Self {
        KeywordConfig {
            algorithm: KeywordAlgorithm::from(v.algorithm),
            max_keywords: v.max_keywords as _,
            min_score: v.min_score as _,
            ngram_range: Default::default(),
            language: v.language.map(|s| s.into()),
            yake_params: v.yake_params.map(YakeParams::from),
            rake_params: v.rake_params.map(RakeParams::from),
        }
    }
}

impl From<kreuzberg::Keyword> for Keyword {
    fn from(v: kreuzberg::Keyword) -> Self {
        Keyword {
            text: v.text.into(),
            score: v.score as _,
            algorithm: KeywordAlgorithm::from(v.algorithm),
            positions: v.positions.map(|vec| vec.into_iter().map(|x| x as _).collect()),
        }
    }
}

impl From<kreuzberg::PaddleOcrConfig> for PaddleOcrConfig {
    fn from(v: kreuzberg::PaddleOcrConfig) -> Self {
        PaddleOcrConfig {
            language: v.language.into(),
            cache_dir: v.cache_dir.map(|p| p.to_string_lossy().into_owned()),
            use_angle_cls: v.use_angle_cls as _,
            enable_table_detection: v.enable_table_detection as _,
            det_db_thresh: v.det_db_thresh as _,
            det_db_box_thresh: v.det_db_box_thresh as _,
            det_db_unclip_ratio: v.det_db_unclip_ratio as _,
            det_limit_side_len: v.det_limit_side_len as _,
            rec_batch_num: v.rec_batch_num as _,
            padding: v.padding as _,
            drop_score: v.drop_score as _,
            model_tier: v.model_tier.into(),
        }
    }
}

impl From<kreuzberg::ModelPaths> for ModelPaths {
    fn from(v: kreuzberg::ModelPaths) -> Self {
        ModelPaths {
            det_model: v.det_model.to_string_lossy().into_owned(),
            cls_model: v.cls_model.to_string_lossy().into_owned(),
            rec_model: v.rec_model.to_string_lossy().into_owned(),
            dict_file: v.dict_file.to_string_lossy().into_owned(),
        }
    }
}

impl From<kreuzberg::OrientationResult> for OrientationResult {
    fn from(v: kreuzberg::OrientationResult) -> Self {
        OrientationResult {
            degrees: v.degrees as _,
            confidence: v.confidence as _,
        }
    }
}

impl From<kreuzberg::BBox> for BBox {
    fn from(v: kreuzberg::BBox) -> Self {
        BBox {
            x1: v.x1 as _,
            y1: v.y1 as _,
            x2: v.x2 as _,
            y2: v.y2 as _,
        }
    }
}

impl From<kreuzberg::LayoutDetection> for LayoutDetection {
    fn from(v: kreuzberg::LayoutDetection) -> Self {
        LayoutDetection {
            class_name: LayoutClass::from(v.class_name),
            confidence: v.confidence as _,
            bbox: BBox::from(v.bbox),
        }
    }
}

impl From<kreuzberg::RecognizedTable> for RecognizedTable {
    fn from(v: kreuzberg::RecognizedTable) -> Self {
        RecognizedTable {
            detection_bbox: BBox::from(v.detection_bbox),
            cells: v.cells,
            markdown: v.markdown.into(),
        }
    }
}

impl From<kreuzberg::DetectionResult> for DetectionResult {
    fn from(v: kreuzberg::DetectionResult) -> Self {
        DetectionResult {
            page_width: v.page_width as _,
            page_height: v.page_height as _,
            detections: v.detections.into_iter().map(LayoutDetection::from).collect(),
        }
    }
}

impl From<kreuzberg::pdf::embedded_files::EmbeddedFile> for EmbeddedFile {
    fn from(v: kreuzberg::pdf::embedded_files::EmbeddedFile) -> Self {
        EmbeddedFile {
            name: v.name.into(),
            data: v.data.into(),
            compressed_size: v.compressed_size as _,
            mime_type: v.mime_type.map(|s| s.into()),
        }
    }
}

impl From<kreuzberg::pdf::metadata::PdfMetadata> for PdfMetadata {
    fn from(v: kreuzberg::pdf::metadata::PdfMetadata) -> Self {
        PdfMetadata {
            pdf_version: v.pdf_version.map(|s| s.into()),
            producer: v.producer.map(|s| s.into()),
            is_encrypted: v.is_encrypted.map(|x| x as _),
            width: v.width.map(|x| x as _),
            height: v.height.map(|x| x as _),
            page_count: v.page_count.map(|x| x as _),
        }
    }
}

impl From<kreuzberg::ExecutionProviderType> for ExecutionProviderType {
    fn from(v: kreuzberg::ExecutionProviderType) -> Self {
        match v {
            kreuzberg::ExecutionProviderType::Auto => ExecutionProviderType::Auto,
            kreuzberg::ExecutionProviderType::Cpu => ExecutionProviderType::Cpu,
            kreuzberg::ExecutionProviderType::CoreMl => ExecutionProviderType::CoreMl,
            kreuzberg::ExecutionProviderType::Cuda => ExecutionProviderType::Cuda,
            kreuzberg::ExecutionProviderType::TensorRt => ExecutionProviderType::TensorRt,
        }
    }
}

impl From<kreuzberg::OutputFormat> for OutputFormat {
    fn from(v: kreuzberg::OutputFormat) -> Self {
        match v {
            kreuzberg::OutputFormat::Plain => OutputFormat::Plain,
            kreuzberg::OutputFormat::Markdown => OutputFormat::Markdown,
            kreuzberg::OutputFormat::Djot => OutputFormat::Djot,
            kreuzberg::OutputFormat::Html => OutputFormat::Html,
            kreuzberg::OutputFormat::Json => OutputFormat::Json,
            kreuzberg::OutputFormat::Structured => OutputFormat::Structured,
            kreuzberg::OutputFormat::Custom(f0) => OutputFormat::Custom { field0: f0 },
        }
    }
}

impl From<kreuzberg::HtmlTheme> for HtmlTheme {
    fn from(v: kreuzberg::HtmlTheme) -> Self {
        match v {
            kreuzberg::HtmlTheme::Default => HtmlTheme::Default,
            kreuzberg::HtmlTheme::GitHub => HtmlTheme::GitHub,
            kreuzberg::HtmlTheme::Dark => HtmlTheme::Dark,
            kreuzberg::HtmlTheme::Light => HtmlTheme::Light,
            kreuzberg::HtmlTheme::Unstyled => HtmlTheme::Unstyled,
        }
    }
}

impl From<kreuzberg::TableModel> for TableModel {
    fn from(v: kreuzberg::TableModel) -> Self {
        match v {
            kreuzberg::TableModel::Tatr => TableModel::Tatr,
            kreuzberg::TableModel::SlanetWired => TableModel::SlanetWired,
            kreuzberg::TableModel::SlanetWireless => TableModel::SlanetWireless,
            kreuzberg::TableModel::SlanetPlus => TableModel::SlanetPlus,
            kreuzberg::TableModel::SlanetAuto => TableModel::SlanetAuto,
            kreuzberg::TableModel::Disabled => TableModel::Disabled,
        }
    }
}

impl From<kreuzberg::ChunkerType> for ChunkerType {
    fn from(v: kreuzberg::ChunkerType) -> Self {
        match v {
            kreuzberg::ChunkerType::Text => ChunkerType::Text,
            kreuzberg::ChunkerType::Markdown => ChunkerType::Markdown,
            kreuzberg::ChunkerType::Yaml => ChunkerType::Yaml,
            kreuzberg::ChunkerType::Semantic => ChunkerType::Semantic,
        }
    }
}

impl From<kreuzberg::ChunkSizing> for ChunkSizing {
    fn from(v: kreuzberg::ChunkSizing) -> Self {
        match v {
            kreuzberg::ChunkSizing::Characters => ChunkSizing::Characters,
            kreuzberg::ChunkSizing::Tokenizer { model, cache_dir } => ChunkSizing::Tokenizer {
                model,
                cache_dir: cache_dir.map(|p| p.to_string_lossy().into_owned()).unwrap_or_default(),
            },
        }
    }
}

impl From<kreuzberg::EmbeddingModelType> for EmbeddingModelType {
    fn from(v: kreuzberg::EmbeddingModelType) -> Self {
        match v {
            kreuzberg::EmbeddingModelType::Preset { name } => EmbeddingModelType::Preset { name },
            kreuzberg::EmbeddingModelType::Custom { model_id, dimensions } => EmbeddingModelType::Custom {
                model_id,
                dimensions: dimensions as _,
            },
            kreuzberg::EmbeddingModelType::Llm { llm } => EmbeddingModelType::Llm {
                llm: LlmConfig::from(llm),
            },
            kreuzberg::EmbeddingModelType::Plugin { name } => EmbeddingModelType::Plugin { name },
        }
    }
}

impl From<kreuzberg::CodeContentMode> for CodeContentMode {
    fn from(v: kreuzberg::CodeContentMode) -> Self {
        match v {
            kreuzberg::CodeContentMode::Chunks => CodeContentMode::Chunks,
            kreuzberg::CodeContentMode::Raw => CodeContentMode::Raw,
            kreuzberg::CodeContentMode::Structure => CodeContentMode::Structure,
        }
    }
}

impl From<kreuzberg::extraction::transform::ListType> for ListType {
    fn from(v: kreuzberg::extraction::transform::ListType) -> Self {
        match v {
            kreuzberg::extraction::transform::ListType::Bullet => ListType::Bullet,
            kreuzberg::extraction::transform::ListType::Numbered => ListType::Numbered,
            kreuzberg::extraction::transform::ListType::Lettered => ListType::Lettered,
            kreuzberg::extraction::transform::ListType::Indented => ListType::Indented,
        }
    }
}

impl From<kreuzberg::OcrBackendType> for OcrBackendType {
    fn from(v: kreuzberg::OcrBackendType) -> Self {
        match v {
            kreuzberg::OcrBackendType::Tesseract => OcrBackendType::Tesseract,
            kreuzberg::OcrBackendType::EasyOCR => OcrBackendType::EasyOCR,
            kreuzberg::OcrBackendType::PaddleOCR => OcrBackendType::PaddleOCR,
            kreuzberg::OcrBackendType::Custom => OcrBackendType::Custom,
        }
    }
}

impl From<kreuzberg::ProcessingStage> for ProcessingStage {
    fn from(v: kreuzberg::ProcessingStage) -> Self {
        match v {
            kreuzberg::ProcessingStage::Early => ProcessingStage::Early,
            kreuzberg::ProcessingStage::Middle => ProcessingStage::Middle,
            kreuzberg::ProcessingStage::Late => ProcessingStage::Late,
        }
    }
}

impl From<kreuzberg::ReductionLevel> for ReductionLevel {
    fn from(v: kreuzberg::ReductionLevel) -> Self {
        match v {
            kreuzberg::ReductionLevel::Off => ReductionLevel::Off,
            kreuzberg::ReductionLevel::Light => ReductionLevel::Light,
            kreuzberg::ReductionLevel::Moderate => ReductionLevel::Moderate,
            kreuzberg::ReductionLevel::Aggressive => ReductionLevel::Aggressive,
            kreuzberg::ReductionLevel::Maximum => ReductionLevel::Maximum,
        }
    }
}

impl From<kreuzberg::PdfAnnotationType> for PdfAnnotationType {
    fn from(v: kreuzberg::PdfAnnotationType) -> Self {
        match v {
            kreuzberg::PdfAnnotationType::Text => PdfAnnotationType::Text,
            kreuzberg::PdfAnnotationType::Highlight => PdfAnnotationType::Highlight,
            kreuzberg::PdfAnnotationType::Link => PdfAnnotationType::Link,
            kreuzberg::PdfAnnotationType::Stamp => PdfAnnotationType::Stamp,
            kreuzberg::PdfAnnotationType::Underline => PdfAnnotationType::Underline,
            kreuzberg::PdfAnnotationType::StrikeOut => PdfAnnotationType::StrikeOut,
            kreuzberg::PdfAnnotationType::Other => PdfAnnotationType::Other,
        }
    }
}

impl From<kreuzberg::BlockType> for BlockType {
    fn from(v: kreuzberg::BlockType) -> Self {
        match v {
            kreuzberg::BlockType::Paragraph => BlockType::Paragraph,
            kreuzberg::BlockType::Heading => BlockType::Heading,
            kreuzberg::BlockType::Blockquote => BlockType::Blockquote,
            kreuzberg::BlockType::CodeBlock => BlockType::CodeBlock,
            kreuzberg::BlockType::ListItem => BlockType::ListItem,
            kreuzberg::BlockType::OrderedList => BlockType::OrderedList,
            kreuzberg::BlockType::BulletList => BlockType::BulletList,
            kreuzberg::BlockType::TaskList => BlockType::TaskList,
            kreuzberg::BlockType::DefinitionList => BlockType::DefinitionList,
            kreuzberg::BlockType::DefinitionTerm => BlockType::DefinitionTerm,
            kreuzberg::BlockType::DefinitionDescription => BlockType::DefinitionDescription,
            kreuzberg::BlockType::Div => BlockType::Div,
            kreuzberg::BlockType::Section => BlockType::Section,
            kreuzberg::BlockType::ThematicBreak => BlockType::ThematicBreak,
            kreuzberg::BlockType::RawBlock => BlockType::RawBlock,
            kreuzberg::BlockType::MathDisplay => BlockType::MathDisplay,
        }
    }
}

impl From<kreuzberg::InlineType> for InlineType {
    fn from(v: kreuzberg::InlineType) -> Self {
        match v {
            kreuzberg::InlineType::Text => InlineType::Text,
            kreuzberg::InlineType::Strong => InlineType::Strong,
            kreuzberg::InlineType::Emphasis => InlineType::Emphasis,
            kreuzberg::InlineType::Highlight => InlineType::Highlight,
            kreuzberg::InlineType::Subscript => InlineType::Subscript,
            kreuzberg::InlineType::Superscript => InlineType::Superscript,
            kreuzberg::InlineType::Insert => InlineType::Insert,
            kreuzberg::InlineType::Delete => InlineType::Delete,
            kreuzberg::InlineType::Code => InlineType::Code,
            kreuzberg::InlineType::Link => InlineType::Link,
            kreuzberg::InlineType::Image => InlineType::Image,
            kreuzberg::InlineType::Span => InlineType::Span,
            kreuzberg::InlineType::Math => InlineType::Math,
            kreuzberg::InlineType::RawInline => InlineType::RawInline,
            kreuzberg::InlineType::FootnoteRef => InlineType::FootnoteRef,
            kreuzberg::InlineType::Symbol => InlineType::Symbol,
        }
    }
}

impl From<kreuzberg::RelationshipKind> for RelationshipKind {
    fn from(v: kreuzberg::RelationshipKind) -> Self {
        match v {
            kreuzberg::RelationshipKind::FootnoteReference => RelationshipKind::FootnoteReference,
            kreuzberg::RelationshipKind::CitationReference => RelationshipKind::CitationReference,
            kreuzberg::RelationshipKind::InternalLink => RelationshipKind::InternalLink,
            kreuzberg::RelationshipKind::Caption => RelationshipKind::Caption,
            kreuzberg::RelationshipKind::Label => RelationshipKind::Label,
            kreuzberg::RelationshipKind::TocEntry => RelationshipKind::TocEntry,
            kreuzberg::RelationshipKind::CrossReference => RelationshipKind::CrossReference,
        }
    }
}

impl From<kreuzberg::ContentLayer> for ContentLayer {
    fn from(v: kreuzberg::ContentLayer) -> Self {
        match v {
            kreuzberg::ContentLayer::Body => ContentLayer::Body,
            kreuzberg::ContentLayer::Header => ContentLayer::Header,
            kreuzberg::ContentLayer::Footer => ContentLayer::Footer,
            kreuzberg::ContentLayer::Footnote => ContentLayer::Footnote,
        }
    }
}

impl From<kreuzberg::NodeContent> for NodeContent {
    fn from(v: kreuzberg::NodeContent) -> Self {
        match v {
            kreuzberg::NodeContent::Title { text } => NodeContent::Title { text },
            kreuzberg::NodeContent::Heading { level, text } => NodeContent::Heading {
                level: level as _,
                text,
            },
            kreuzberg::NodeContent::Paragraph { text } => NodeContent::Paragraph { text },
            kreuzberg::NodeContent::List { ordered } => NodeContent::List { ordered: ordered as _ },
            kreuzberg::NodeContent::ListItem { text } => NodeContent::ListItem { text },
            kreuzberg::NodeContent::Table { grid } => NodeContent::Table {
                grid: TableGrid::from(grid),
            },
            kreuzberg::NodeContent::Image {
                description,
                image_index,
                src,
            } => NodeContent::Image {
                description: description.unwrap_or_default(),
                image_index: image_index.map(|x| x as _).unwrap_or_default(),
                src: src.unwrap_or_default(),
            },
            kreuzberg::NodeContent::Code { text, language } => NodeContent::Code {
                text,
                language: language.unwrap_or_default(),
            },
            kreuzberg::NodeContent::Quote => NodeContent::Quote,
            kreuzberg::NodeContent::Formula { text } => NodeContent::Formula { text },
            kreuzberg::NodeContent::Footnote { text } => NodeContent::Footnote { text },
            kreuzberg::NodeContent::Group {
                label,
                heading_level,
                heading_text,
            } => NodeContent::Group {
                label: label.unwrap_or_default(),
                heading_level: heading_level.map(|x| x as _).unwrap_or_default(),
                heading_text: heading_text.unwrap_or_default(),
            },
            kreuzberg::NodeContent::PageBreak => NodeContent::PageBreak,
            kreuzberg::NodeContent::Slide { number, title } => NodeContent::Slide {
                number: number as _,
                title: title.unwrap_or_default(),
            },
            kreuzberg::NodeContent::DefinitionList => NodeContent::DefinitionList,
            kreuzberg::NodeContent::DefinitionItem { term, definition } => {
                NodeContent::DefinitionItem { term, definition }
            }
            kreuzberg::NodeContent::Citation { key, text } => NodeContent::Citation { key, text },
            kreuzberg::NodeContent::Admonition { kind, title } => NodeContent::Admonition {
                kind,
                title: title.unwrap_or_default(),
            },
            kreuzberg::NodeContent::RawBlock { format, content } => NodeContent::RawBlock { format, content },
            kreuzberg::NodeContent::MetadataBlock { entries } => NodeContent::MetadataBlock {
                entries: entries
                    .into_iter()
                    .map(|(a, b)| vec![a.to_string(), b.to_string()])
                    .collect(),
            },
        }
    }
}

impl From<kreuzberg::AnnotationKind> for AnnotationKind {
    fn from(v: kreuzberg::AnnotationKind) -> Self {
        match v {
            kreuzberg::AnnotationKind::Bold => AnnotationKind::Bold,
            kreuzberg::AnnotationKind::Italic => AnnotationKind::Italic,
            kreuzberg::AnnotationKind::Underline => AnnotationKind::Underline,
            kreuzberg::AnnotationKind::Strikethrough => AnnotationKind::Strikethrough,
            kreuzberg::AnnotationKind::Code => AnnotationKind::Code,
            kreuzberg::AnnotationKind::Subscript => AnnotationKind::Subscript,
            kreuzberg::AnnotationKind::Superscript => AnnotationKind::Superscript,
            kreuzberg::AnnotationKind::Link { url, title } => AnnotationKind::Link {
                url,
                title: title.unwrap_or_default(),
            },
            kreuzberg::AnnotationKind::Highlight => AnnotationKind::Highlight,
            kreuzberg::AnnotationKind::Color { value } => AnnotationKind::Color { value },
            kreuzberg::AnnotationKind::FontSize { value } => AnnotationKind::FontSize { value },
            kreuzberg::AnnotationKind::Custom { name, value } => AnnotationKind::Custom {
                name,
                value: value.unwrap_or_default(),
            },
        }
    }
}

impl From<kreuzberg::ExtractionMethod> for ExtractionMethod {
    fn from(v: kreuzberg::ExtractionMethod) -> Self {
        match v {
            kreuzberg::ExtractionMethod::Native => ExtractionMethod::Native,
            kreuzberg::ExtractionMethod::Ocr => ExtractionMethod::Ocr,
            kreuzberg::ExtractionMethod::Mixed => ExtractionMethod::Mixed,
        }
    }
}

impl From<kreuzberg::ChunkType> for ChunkType {
    fn from(v: kreuzberg::ChunkType) -> Self {
        match v {
            kreuzberg::ChunkType::Heading => ChunkType::Heading,
            kreuzberg::ChunkType::PartyList => ChunkType::PartyList,
            kreuzberg::ChunkType::Definitions => ChunkType::Definitions,
            kreuzberg::ChunkType::OperativeClause => ChunkType::OperativeClause,
            kreuzberg::ChunkType::SignatureBlock => ChunkType::SignatureBlock,
            kreuzberg::ChunkType::Schedule => ChunkType::Schedule,
            kreuzberg::ChunkType::TableLike => ChunkType::TableLike,
            kreuzberg::ChunkType::Formula => ChunkType::Formula,
            kreuzberg::ChunkType::CodeBlock => ChunkType::CodeBlock,
            kreuzberg::ChunkType::Image => ChunkType::Image,
            kreuzberg::ChunkType::OrgChart => ChunkType::OrgChart,
            kreuzberg::ChunkType::Diagram => ChunkType::Diagram,
            kreuzberg::ChunkType::Unknown => ChunkType::Unknown,
        }
    }
}

impl From<kreuzberg::ImageKind> for ImageKind {
    fn from(v: kreuzberg::ImageKind) -> Self {
        match v {
            kreuzberg::ImageKind::Photograph => ImageKind::Photograph,
            kreuzberg::ImageKind::Diagram => ImageKind::Diagram,
            kreuzberg::ImageKind::Chart => ImageKind::Chart,
            kreuzberg::ImageKind::Drawing => ImageKind::Drawing,
            kreuzberg::ImageKind::TextBlock => ImageKind::TextBlock,
            kreuzberg::ImageKind::Decoration => ImageKind::Decoration,
            kreuzberg::ImageKind::Logo => ImageKind::Logo,
            kreuzberg::ImageKind::Icon => ImageKind::Icon,
            kreuzberg::ImageKind::TileFragment => ImageKind::TileFragment,
            kreuzberg::ImageKind::Mask => ImageKind::Mask,
            kreuzberg::ImageKind::PageRaster => ImageKind::PageRaster,
            kreuzberg::ImageKind::Unknown => ImageKind::Unknown,
        }
    }
}

impl From<kreuzberg::ResultFormat> for ResultFormat {
    fn from(v: kreuzberg::ResultFormat) -> Self {
        match v {
            kreuzberg::ResultFormat::Unified => ResultFormat::Unified,
            kreuzberg::ResultFormat::ElementBased => ResultFormat::ElementBased,
        }
    }
}

impl From<kreuzberg::ElementType> for ElementType {
    fn from(v: kreuzberg::ElementType) -> Self {
        match v {
            kreuzberg::ElementType::Title => ElementType::Title,
            kreuzberg::ElementType::NarrativeText => ElementType::NarrativeText,
            kreuzberg::ElementType::Heading => ElementType::Heading,
            kreuzberg::ElementType::ListItem => ElementType::ListItem,
            kreuzberg::ElementType::Table => ElementType::Table,
            kreuzberg::ElementType::Image => ElementType::Image,
            kreuzberg::ElementType::PageBreak => ElementType::PageBreak,
            kreuzberg::ElementType::CodeBlock => ElementType::CodeBlock,
            kreuzberg::ElementType::BlockQuote => ElementType::BlockQuote,
            kreuzberg::ElementType::Footer => ElementType::Footer,
            kreuzberg::ElementType::Header => ElementType::Header,
        }
    }
}

impl From<kreuzberg::FormatMetadata> for FormatMetadata {
    fn from(v: kreuzberg::FormatMetadata) -> Self {
        match v {
            kreuzberg::FormatMetadata::Pdf(f0) => FormatMetadata::Pdf {
                field0: PdfMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Docx(f0) => FormatMetadata::Docx {
                field0: DocxMetadata::from(*f0),
            },
            kreuzberg::FormatMetadata::Excel(f0) => FormatMetadata::Excel {
                field0: ExcelMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Email(f0) => FormatMetadata::Email {
                field0: EmailMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Pptx(f0) => FormatMetadata::Pptx {
                field0: PptxMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Archive(f0) => FormatMetadata::Archive {
                field0: ArchiveMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Image(f0) => FormatMetadata::Image {
                field0: ImageMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Xml(f0) => FormatMetadata::Xml {
                field0: XmlMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Text(f0) => FormatMetadata::Text {
                field0: TextMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Html(f0) => FormatMetadata::Html {
                field0: HtmlMetadata::from(*f0),
            },
            kreuzberg::FormatMetadata::Ocr(f0) => FormatMetadata::Ocr {
                field0: OcrMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Csv(f0) => FormatMetadata::Csv {
                field0: CsvMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Bibtex(f0) => FormatMetadata::Bibtex {
                field0: BibtexMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Citation(f0) => FormatMetadata::Citation {
                field0: CitationMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::FictionBook(f0) => FormatMetadata::FictionBook {
                field0: FictionBookMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Dbf(f0) => FormatMetadata::Dbf {
                field0: DbfMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Jats(f0) => FormatMetadata::Jats {
                field0: JatsMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Epub(f0) => FormatMetadata::Epub {
                field0: EpubMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Pst(f0) => FormatMetadata::Pst {
                field0: PstMetadata::from(f0),
            },
            kreuzberg::FormatMetadata::Code(f0) => FormatMetadata::Code {
                field0: serde_json::to_string(&f0).unwrap_or_default(),
            },
        }
    }
}

impl From<kreuzberg::TextDirection> for TextDirection {
    fn from(v: kreuzberg::TextDirection) -> Self {
        match v {
            kreuzberg::TextDirection::LeftToRight => TextDirection::LeftToRight,
            kreuzberg::TextDirection::RightToLeft => TextDirection::RightToLeft,
            kreuzberg::TextDirection::Auto => TextDirection::Auto,
        }
    }
}

impl From<kreuzberg::LinkType> for LinkType {
    fn from(v: kreuzberg::LinkType) -> Self {
        match v {
            kreuzberg::LinkType::Anchor => LinkType::Anchor,
            kreuzberg::LinkType::Internal => LinkType::Internal,
            kreuzberg::LinkType::External => LinkType::External,
            kreuzberg::LinkType::Email => LinkType::Email,
            kreuzberg::LinkType::Phone => LinkType::Phone,
            kreuzberg::LinkType::Other => LinkType::Other,
        }
    }
}

impl From<kreuzberg::ImageType> for ImageType {
    fn from(v: kreuzberg::ImageType) -> Self {
        match v {
            kreuzberg::ImageType::DataUri => ImageType::DataUri,
            kreuzberg::ImageType::InlineSvg => ImageType::InlineSvg,
            kreuzberg::ImageType::External => ImageType::External,
            kreuzberg::ImageType::Relative => ImageType::Relative,
        }
    }
}

impl From<kreuzberg::StructuredDataType> for StructuredDataType {
    fn from(v: kreuzberg::StructuredDataType) -> Self {
        match v {
            kreuzberg::StructuredDataType::JsonLd => StructuredDataType::JsonLd,
            kreuzberg::StructuredDataType::Microdata => StructuredDataType::Microdata,
            kreuzberg::StructuredDataType::RDFa => StructuredDataType::RDFa,
        }
    }
}

impl From<kreuzberg::OcrBoundingGeometry> for OcrBoundingGeometry {
    fn from(v: kreuzberg::OcrBoundingGeometry) -> Self {
        match v {
            kreuzberg::OcrBoundingGeometry::Rectangle {
                left,
                top,
                width,
                height,
            } => OcrBoundingGeometry::Rectangle {
                left: left as _,
                top: top as _,
                width: width as _,
                height: height as _,
            },
            kreuzberg::OcrBoundingGeometry::Quadrilateral { points } => OcrBoundingGeometry::Quadrilateral {
                points: serde_json::to_string(&points).unwrap_or_default(),
            },
        }
    }
}

impl From<kreuzberg::OcrElementLevel> for OcrElementLevel {
    fn from(v: kreuzberg::OcrElementLevel) -> Self {
        match v {
            kreuzberg::OcrElementLevel::Word => OcrElementLevel::Word,
            kreuzberg::OcrElementLevel::Line => OcrElementLevel::Line,
            kreuzberg::OcrElementLevel::Block => OcrElementLevel::Block,
            kreuzberg::OcrElementLevel::Page => OcrElementLevel::Page,
        }
    }
}

impl From<kreuzberg::PageUnitType> for PageUnitType {
    fn from(v: kreuzberg::PageUnitType) -> Self {
        match v {
            kreuzberg::PageUnitType::Page => PageUnitType::Page,
            kreuzberg::PageUnitType::Slide => PageUnitType::Slide,
            kreuzberg::PageUnitType::Sheet => PageUnitType::Sheet,
        }
    }
}

impl From<kreuzberg::DiffLine> for DiffLine {
    fn from(v: kreuzberg::DiffLine) -> Self {
        match v {
            kreuzberg::DiffLine::Context(f0) => DiffLine::Context { field0: f0 },
            kreuzberg::DiffLine::Added(f0) => DiffLine::Added { field0: f0 },
            kreuzberg::DiffLine::Removed(f0) => DiffLine::Removed { field0: f0 },
        }
    }
}

impl From<kreuzberg::RevisionKind> for RevisionKind {
    fn from(v: kreuzberg::RevisionKind) -> Self {
        match v {
            kreuzberg::RevisionKind::Insertion => RevisionKind::Insertion,
            kreuzberg::RevisionKind::Deletion => RevisionKind::Deletion,
            kreuzberg::RevisionKind::FormatChange => RevisionKind::FormatChange,
            kreuzberg::RevisionKind::Comment => RevisionKind::Comment,
        }
    }
}

impl From<kreuzberg::RevisionAnchor> for RevisionAnchor {
    fn from(v: kreuzberg::RevisionAnchor) -> Self {
        match v {
            kreuzberg::RevisionAnchor::Paragraph { index } => RevisionAnchor::Paragraph { index: index as _ },
            kreuzberg::RevisionAnchor::TableCell { row, col, table_index } => RevisionAnchor::TableCell {
                row: row as _,
                col: col as _,
                table_index: table_index as _,
            },
            kreuzberg::RevisionAnchor::Page { index } => RevisionAnchor::Page { index: index as _ },
            kreuzberg::RevisionAnchor::Slide { index } => RevisionAnchor::Slide { index: index as _ },
            kreuzberg::RevisionAnchor::Sheet { index, name } => RevisionAnchor::Sheet {
                index: index as _,
                name: name.unwrap_or_default(),
            },
        }
    }
}

impl From<kreuzberg::UriKind> for UriKind {
    fn from(v: kreuzberg::UriKind) -> Self {
        match v {
            kreuzberg::UriKind::Hyperlink => UriKind::Hyperlink,
            kreuzberg::UriKind::Image => UriKind::Image,
            kreuzberg::UriKind::Anchor => UriKind::Anchor,
            kreuzberg::UriKind::Citation => UriKind::Citation,
            kreuzberg::UriKind::Reference => UriKind::Reference,
            kreuzberg::UriKind::Email => UriKind::Email,
        }
    }
}

impl From<kreuzberg::KeywordAlgorithm> for KeywordAlgorithm {
    fn from(v: kreuzberg::KeywordAlgorithm) -> Self {
        match v {
            kreuzberg::KeywordAlgorithm::Yake => KeywordAlgorithm::Yake,
            kreuzberg::KeywordAlgorithm::Rake => KeywordAlgorithm::Rake,
        }
    }
}

impl From<kreuzberg::PSMMode> for PSMMode {
    fn from(v: kreuzberg::PSMMode) -> Self {
        match v {
            kreuzberg::PSMMode::OsdOnly => PSMMode::OsdOnly,
            kreuzberg::PSMMode::AutoOsd => PSMMode::AutoOsd,
            kreuzberg::PSMMode::AutoOnly => PSMMode::AutoOnly,
            kreuzberg::PSMMode::Auto => PSMMode::Auto,
            kreuzberg::PSMMode::SingleColumn => PSMMode::SingleColumn,
            kreuzberg::PSMMode::SingleBlockVertical => PSMMode::SingleBlockVertical,
            kreuzberg::PSMMode::SingleBlock => PSMMode::SingleBlock,
            kreuzberg::PSMMode::SingleLine => PSMMode::SingleLine,
            kreuzberg::PSMMode::SingleWord => PSMMode::SingleWord,
            kreuzberg::PSMMode::CircleWord => PSMMode::CircleWord,
            kreuzberg::PSMMode::SingleChar => PSMMode::SingleChar,
        }
    }
}

impl From<kreuzberg::PaddleLanguage> for PaddleLanguage {
    fn from(v: kreuzberg::PaddleLanguage) -> Self {
        match v {
            kreuzberg::PaddleLanguage::English => PaddleLanguage::English,
            kreuzberg::PaddleLanguage::Chinese => PaddleLanguage::Chinese,
            kreuzberg::PaddleLanguage::Japanese => PaddleLanguage::Japanese,
            kreuzberg::PaddleLanguage::Korean => PaddleLanguage::Korean,
            kreuzberg::PaddleLanguage::German => PaddleLanguage::German,
            kreuzberg::PaddleLanguage::French => PaddleLanguage::French,
            kreuzberg::PaddleLanguage::Latin => PaddleLanguage::Latin,
            kreuzberg::PaddleLanguage::Cyrillic => PaddleLanguage::Cyrillic,
            kreuzberg::PaddleLanguage::TraditionalChinese => PaddleLanguage::TraditionalChinese,
            kreuzberg::PaddleLanguage::Thai => PaddleLanguage::Thai,
            kreuzberg::PaddleLanguage::Greek => PaddleLanguage::Greek,
            kreuzberg::PaddleLanguage::EastSlavic => PaddleLanguage::EastSlavic,
            kreuzberg::PaddleLanguage::Arabic => PaddleLanguage::Arabic,
            kreuzberg::PaddleLanguage::Devanagari => PaddleLanguage::Devanagari,
            kreuzberg::PaddleLanguage::Tamil => PaddleLanguage::Tamil,
            kreuzberg::PaddleLanguage::Telugu => PaddleLanguage::Telugu,
        }
    }
}

impl From<kreuzberg::LayoutClass> for LayoutClass {
    fn from(v: kreuzberg::LayoutClass) -> Self {
        match v {
            kreuzberg::LayoutClass::Caption => LayoutClass::Caption,
            kreuzberg::LayoutClass::Footnote => LayoutClass::Footnote,
            kreuzberg::LayoutClass::Formula => LayoutClass::Formula,
            kreuzberg::LayoutClass::ListItem => LayoutClass::ListItem,
            kreuzberg::LayoutClass::PageFooter => LayoutClass::PageFooter,
            kreuzberg::LayoutClass::PageHeader => LayoutClass::PageHeader,
            kreuzberg::LayoutClass::Picture => LayoutClass::Picture,
            kreuzberg::LayoutClass::SectionHeader => LayoutClass::SectionHeader,
            kreuzberg::LayoutClass::Table => LayoutClass::Table,
            kreuzberg::LayoutClass::Text => LayoutClass::Text,
            kreuzberg::LayoutClass::Title => LayoutClass::Title,
            kreuzberg::LayoutClass::DocumentIndex => LayoutClass::DocumentIndex,
            kreuzberg::LayoutClass::Code => LayoutClass::Code,
            kreuzberg::LayoutClass::CheckboxSelected => LayoutClass::CheckboxSelected,
            kreuzberg::LayoutClass::CheckboxUnselected => LayoutClass::CheckboxUnselected,
            kreuzberg::LayoutClass::Form => LayoutClass::Form,
            kreuzberg::LayoutClass::KeyValueRegion => LayoutClass::KeyValueRegion,
        }
    }
}

// From<T> for SourceT conversions (mirror-to-core direction).
// Used in bridge functions for types with sanitized fields, and by
// nested conversions within those types.

impl From<AccelerationConfig> for kreuzberg::AccelerationConfig {
    fn from(v: AccelerationConfig) -> Self {
        kreuzberg::AccelerationConfig {
            provider: v.provider.into(),
            device_id: v.device_id as _,
        }
    }
}

impl From<ContentFilterConfig> for kreuzberg::ContentFilterConfig {
    fn from(v: ContentFilterConfig) -> Self {
        kreuzberg::ContentFilterConfig {
            include_headers: v.include_headers as _,
            include_footers: v.include_footers as _,
            strip_repeating_text: v.strip_repeating_text as _,
            include_watermarks: v.include_watermarks as _,
        }
    }
}

impl From<EmailConfig> for kreuzberg::EmailConfig {
    fn from(v: EmailConfig) -> Self {
        kreuzberg::EmailConfig {
            msg_fallback_codepage: v.msg_fallback_codepage.map(|x| x as _),
        }
    }
}

#[allow(clippy::needless_update)]
impl From<ExtractionConfig> for kreuzberg::ExtractionConfig {
    fn from(v: ExtractionConfig) -> Self {
        kreuzberg::ExtractionConfig {
            use_cache: v.use_cache as _,
            enable_quality_processing: v.enable_quality_processing as _,
            ocr: v.ocr.map(Into::into),
            force_ocr: v.force_ocr as _,
            force_ocr_pages: v.force_ocr_pages.map(|vec| vec.into_iter().map(|x| x as _).collect()),
            disable_ocr: v.disable_ocr as _,
            chunking: v.chunking.map(Into::into),
            content_filter: v.content_filter.map(Into::into),
            images: v.images.map(Into::into),
            pdf_options: v.pdf_options.map(Into::into),
            token_reduction: v.token_reduction.map(Into::into),
            language_detection: v.language_detection.map(Into::into),
            pages: v.pages.map(Into::into),
            keywords: v.keywords.map(Into::into),
            postprocessor: v.postprocessor.map(Into::into),
            html_options: Default::default(),
            html_output: v.html_output.map(Into::into),
            extraction_timeout_secs: v.extraction_timeout_secs.map(|x| x as _),
            max_concurrent_extractions: v.max_concurrent_extractions.map(|x| x as _),
            result_format: v.result_format.into(),
            security_limits: v.security_limits.map(Into::into),
            max_embedded_file_bytes: v.max_embedded_file_bytes.map(|x| x as _),
            output_format: v.output_format.into(),
            layout: v.layout.map(Into::into),
            use_layout_for_markdown: v.use_layout_for_markdown as _,
            include_document_structure: v.include_document_structure as _,
            acceleration: v.acceleration.map(Into::into),
            cache_namespace: v.cache_namespace.map(Into::into),
            cache_ttl_secs: v.cache_ttl_secs.map(|x| x as _),
            email: v.email.map(Into::into),
            concurrency: Default::default(),
            max_archive_depth: v.max_archive_depth as _,
            tree_sitter: v.tree_sitter.map(Into::into),
            structured_extraction: v.structured_extraction.map(Into::into),
            cancel_token: Default::default(),
            ..Default::default()
        }
    }
}

#[allow(clippy::needless_update)]
impl From<FileExtractionConfig> for kreuzberg::FileExtractionConfig {
    fn from(v: FileExtractionConfig) -> Self {
        kreuzberg::FileExtractionConfig {
            enable_quality_processing: v.enable_quality_processing.map(|x| x as _),
            ocr: v.ocr.map(Into::into),
            force_ocr: v.force_ocr.map(|x| x as _),
            force_ocr_pages: v.force_ocr_pages.map(|vec| vec.into_iter().map(|x| x as _).collect()),
            disable_ocr: v.disable_ocr.map(|x| x as _),
            chunking: v.chunking.map(Into::into),
            content_filter: v.content_filter.map(Into::into),
            images: v.images.map(Into::into),
            pdf_options: v.pdf_options.map(Into::into),
            token_reduction: v.token_reduction.map(Into::into),
            language_detection: v.language_detection.map(Into::into),
            pages: v.pages.map(Into::into),
            keywords: v.keywords.map(Into::into),
            postprocessor: v.postprocessor.map(Into::into),
            html_options: Default::default(),
            result_format: v.result_format.map(Into::into),
            output_format: v.output_format.map(Into::into),
            include_document_structure: v.include_document_structure.map(|x| x as _),
            layout: v.layout.map(Into::into),
            timeout_secs: v.timeout_secs.map(|x| x as _),
            tree_sitter: v.tree_sitter.map(Into::into),
            structured_extraction: v.structured_extraction.map(Into::into),
            ..Default::default()
        }
    }
}

impl From<BatchBytesItem> for kreuzberg::BatchBytesItem {
    fn from(v: BatchBytesItem) -> Self {
        kreuzberg::BatchBytesItem {
            content: v.content.into(),
            mime_type: v.mime_type.into(),
            config: v.config.map(Into::into),
        }
    }
}

impl From<BatchFileItem> for kreuzberg::BatchFileItem {
    fn from(v: BatchFileItem) -> Self {
        kreuzberg::BatchFileItem {
            path: std::path::PathBuf::from(v.path),
            config: v.config.map(Into::into),
        }
    }
}

impl From<ImageExtractionConfig> for kreuzberg::ImageExtractionConfig {
    fn from(v: ImageExtractionConfig) -> Self {
        kreuzberg::ImageExtractionConfig {
            extract_images: v.extract_images as _,
            target_dpi: v.target_dpi as _,
            max_image_dimension: v.max_image_dimension as _,
            inject_placeholders: v.inject_placeholders as _,
            auto_adjust_dpi: v.auto_adjust_dpi as _,
            min_dpi: v.min_dpi as _,
            max_dpi: v.max_dpi as _,
            max_images_per_page: v.max_images_per_page.map(|x| x as _),
            classify: v.classify as _,
            include_page_rasters: v.include_page_rasters as _,
            run_ocr_on_images: v.run_ocr_on_images as _,
            ocr_text_only: v.ocr_text_only as _,
            append_ocr_text: v.append_ocr_text as _,
        }
    }
}

impl From<TokenReductionOptions> for kreuzberg::TokenReductionOptions {
    fn from(v: TokenReductionOptions) -> Self {
        kreuzberg::TokenReductionOptions {
            mode: v.mode.into(),
            preserve_important_words: v.preserve_important_words as _,
        }
    }
}

impl From<LanguageDetectionConfig> for kreuzberg::LanguageDetectionConfig {
    fn from(v: LanguageDetectionConfig) -> Self {
        kreuzberg::LanguageDetectionConfig {
            enabled: v.enabled as _,
            min_confidence: v.min_confidence as _,
            detect_multiple: v.detect_multiple as _,
        }
    }
}

impl From<HtmlOutputConfig> for kreuzberg::HtmlOutputConfig {
    fn from(v: HtmlOutputConfig) -> Self {
        kreuzberg::HtmlOutputConfig {
            css: v.css.map(Into::into),
            css_file: v.css_file.map(std::path::PathBuf::from),
            theme: v.theme.into(),
            class_prefix: v.class_prefix.into(),
            embed_css: v.embed_css as _,
        }
    }
}

impl From<LayoutDetectionConfig> for kreuzberg::LayoutDetectionConfig {
    fn from(v: LayoutDetectionConfig) -> Self {
        kreuzberg::LayoutDetectionConfig {
            confidence_threshold: v.confidence_threshold.map(|x| x as _),
            apply_heuristics: v.apply_heuristics as _,
            table_model: v.table_model.into(),
            acceleration: v.acceleration.map(Into::into),
        }
    }
}

impl From<LlmConfig> for kreuzberg::LlmConfig {
    fn from(v: LlmConfig) -> Self {
        kreuzberg::LlmConfig {
            model: v.model.into(),
            api_key: v.api_key.map(Into::into),
            base_url: v.base_url.map(Into::into),
            timeout_secs: v.timeout_secs.map(|x| x as _),
            max_retries: v.max_retries.map(|x| x as _),
            temperature: v.temperature.map(|x| x as _),
            max_tokens: v.max_tokens.map(|x| x as _),
        }
    }
}

impl From<StructuredExtractionConfig> for kreuzberg::StructuredExtractionConfig {
    fn from(v: StructuredExtractionConfig) -> Self {
        kreuzberg::StructuredExtractionConfig {
            schema: serde_json::from_str(&v.schema).unwrap_or_default(),
            schema_name: v.schema_name.into(),
            schema_description: v.schema_description.map(Into::into),
            strict: v.strict as _,
            prompt: v.prompt.map(Into::into),
            llm: v.llm.into(),
        }
    }
}

impl From<OcrQualityThresholds> for kreuzberg::OcrQualityThresholds {
    fn from(v: OcrQualityThresholds) -> Self {
        kreuzberg::OcrQualityThresholds {
            min_total_non_whitespace: v.min_total_non_whitespace as _,
            min_non_whitespace_per_page: v.min_non_whitespace_per_page as _,
            min_meaningful_word_len: v.min_meaningful_word_len as _,
            min_meaningful_words: v.min_meaningful_words as _,
            min_alnum_ratio: v.min_alnum_ratio as _,
            min_garbage_chars: v.min_garbage_chars as _,
            max_fragmented_word_ratio: v.max_fragmented_word_ratio as _,
            critical_fragmented_word_ratio: v.critical_fragmented_word_ratio as _,
            min_avg_word_length: v.min_avg_word_length as _,
            min_words_for_avg_length_check: v.min_words_for_avg_length_check as _,
            min_consecutive_repeat_ratio: v.min_consecutive_repeat_ratio as _,
            min_words_for_repeat_check: v.min_words_for_repeat_check as _,
            substantive_min_chars: v.substantive_min_chars as _,
            non_text_min_chars: v.non_text_min_chars as _,
            alnum_ws_ratio_threshold: v.alnum_ws_ratio_threshold as _,
            pipeline_min_quality: v.pipeline_min_quality as _,
        }
    }
}

impl From<OcrPipelineStage> for kreuzberg::OcrPipelineStage {
    fn from(v: OcrPipelineStage) -> Self {
        kreuzberg::OcrPipelineStage {
            backend: v.backend.into(),
            priority: v.priority as _,
            language: v.language.map(Into::into),
            tesseract_config: v.tesseract_config.map(Into::into),
            paddle_ocr_config: v
                .paddle_ocr_config
                .as_deref()
                .and_then(|s| serde_json::from_str(s).ok()),
            vlm_config: v.vlm_config.map(Into::into),
            backend_options: v.backend_options.as_deref().and_then(|s| serde_json::from_str(s).ok()),
        }
    }
}

impl From<OcrPipelineConfig> for kreuzberg::OcrPipelineConfig {
    fn from(v: OcrPipelineConfig) -> Self {
        kreuzberg::OcrPipelineConfig {
            stages: v.stages.into_iter().map(Into::into).collect(),
            quality_thresholds: v.quality_thresholds.into(),
        }
    }
}

impl From<OcrConfig> for kreuzberg::OcrConfig {
    fn from(v: OcrConfig) -> Self {
        kreuzberg::OcrConfig {
            enabled: v.enabled as _,
            backend: v.backend.into(),
            language: v.language.into(),
            tesseract_config: v.tesseract_config.map(Into::into),
            output_format: v.output_format.map(Into::into),
            paddle_ocr_config: v
                .paddle_ocr_config
                .as_deref()
                .and_then(|s| serde_json::from_str(s).ok()),
            backend_options: v.backend_options.as_deref().and_then(|s| serde_json::from_str(s).ok()),
            element_config: v.element_config.map(Into::into),
            quality_thresholds: v.quality_thresholds.map(Into::into),
            pipeline: v.pipeline.map(Into::into),
            auto_rotate: v.auto_rotate as _,
            vlm_config: v.vlm_config.map(Into::into),
            vlm_prompt: v.vlm_prompt.map(Into::into),
            acceleration: v.acceleration.map(Into::into),
            tessdata_bytes: v
                .tessdata_bytes
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<PageConfig> for kreuzberg::PageConfig {
    fn from(v: PageConfig) -> Self {
        kreuzberg::PageConfig {
            extract_pages: v.extract_pages as _,
            insert_page_markers: v.insert_page_markers as _,
            marker_format: v.marker_format.into(),
        }
    }
}

impl From<PdfConfig> for kreuzberg::PdfConfig {
    fn from(v: PdfConfig) -> Self {
        kreuzberg::PdfConfig {
            extract_images: v.extract_images as _,
            extract_tables: v.extract_tables as _,
            passwords: v.passwords.map(|vec| vec.into_iter().map(Into::into).collect()),
            extract_metadata: v.extract_metadata as _,
            hierarchy: v.hierarchy.map(Into::into),
            extract_annotations: v.extract_annotations as _,
            top_margin_fraction: v.top_margin_fraction.map(|x| x as _),
            bottom_margin_fraction: v.bottom_margin_fraction.map(|x| x as _),
            allow_single_column_tables: v.allow_single_column_tables as _,
            ocr_inline_images: v.ocr_inline_images as _,
        }
    }
}

impl From<HierarchyConfig> for kreuzberg::HierarchyConfig {
    fn from(v: HierarchyConfig) -> Self {
        kreuzberg::HierarchyConfig {
            enabled: v.enabled as _,
            k_clusters: v.k_clusters as _,
            include_bbox: v.include_bbox as _,
            ocr_coverage_threshold: v.ocr_coverage_threshold.map(|x| x as _),
        }
    }
}

impl From<PostProcessorConfig> for kreuzberg::PostProcessorConfig {
    fn from(v: PostProcessorConfig) -> Self {
        kreuzberg::PostProcessorConfig {
            enabled: v.enabled as _,
            enabled_processors: v
                .enabled_processors
                .map(|vec| vec.into_iter().map(Into::into).collect()),
            disabled_processors: v
                .disabled_processors
                .map(|vec| vec.into_iter().map(Into::into).collect()),
            enabled_set: v.enabled_set.map(|vec| vec.into_iter().map(Into::into).collect()),
            disabled_set: v.disabled_set.map(|vec| vec.into_iter().map(Into::into).collect()),
        }
    }
}

impl From<ChunkingConfig> for kreuzberg::ChunkingConfig {
    fn from(v: ChunkingConfig) -> Self {
        kreuzberg::ChunkingConfig {
            max_characters: v.max_characters as _,
            overlap: v.overlap as _,
            trim: v.trim as _,
            chunker_type: v.chunker_type.into(),
            embedding: v.embedding.map(Into::into),
            preset: v.preset.map(Into::into),
            sizing: v.sizing.into(),
            prepend_heading_context: v.prepend_heading_context as _,
            topic_threshold: v.topic_threshold.map(|x| x as _),
        }
    }
}

impl From<EmbeddingConfig> for kreuzberg::EmbeddingConfig {
    fn from(v: EmbeddingConfig) -> Self {
        kreuzberg::EmbeddingConfig {
            model: v.model.into(),
            normalize: v.normalize as _,
            batch_size: v.batch_size as _,
            show_download_progress: v.show_download_progress as _,
            cache_dir: v.cache_dir.map(std::path::PathBuf::from),
            acceleration: v.acceleration.map(Into::into),
            max_embed_duration_secs: v.max_embed_duration_secs.map(|x| x as _),
        }
    }
}

impl From<TreeSitterConfig> for kreuzberg::TreeSitterConfig {
    fn from(v: TreeSitterConfig) -> Self {
        kreuzberg::TreeSitterConfig {
            enabled: v.enabled as _,
            cache_dir: v.cache_dir.map(std::path::PathBuf::from),
            languages: v.languages.map(|vec| vec.into_iter().map(Into::into).collect()),
            groups: v.groups.map(|vec| vec.into_iter().map(Into::into).collect()),
            process: v.process.into(),
        }
    }
}

impl From<TreeSitterProcessConfig> for kreuzberg::TreeSitterProcessConfig {
    fn from(v: TreeSitterProcessConfig) -> Self {
        kreuzberg::TreeSitterProcessConfig {
            structure: v.structure as _,
            imports: v.imports as _,
            exports: v.exports as _,
            comments: v.comments as _,
            docstrings: v.docstrings as _,
            symbols: v.symbols as _,
            diagnostics: v.diagnostics as _,
            chunk_max_size: v.chunk_max_size.map(|x| x as _),
            content_mode: v.content_mode.into(),
        }
    }
}

impl From<DocxAppProperties> for kreuzberg::DocxAppProperties {
    fn from(v: DocxAppProperties) -> Self {
        kreuzberg::DocxAppProperties {
            application: v.application.map(Into::into),
            app_version: v.app_version.map(Into::into),
            template: v.template.map(Into::into),
            total_time: v.total_time.map(|x| x as _),
            pages: v.pages.map(|x| x as _),
            words: v.words.map(|x| x as _),
            characters: v.characters.map(|x| x as _),
            characters_with_spaces: v.characters_with_spaces.map(|x| x as _),
            lines: v.lines.map(|x| x as _),
            paragraphs: v.paragraphs.map(|x| x as _),
            company: v.company.map(Into::into),
            doc_security: v.doc_security.map(|x| x as _),
            scale_crop: v.scale_crop.map(|x| x as _),
            links_up_to_date: v.links_up_to_date.map(|x| x as _),
            shared_doc: v.shared_doc.map(|x| x as _),
            hyperlinks_changed: v.hyperlinks_changed.map(|x| x as _),
        }
    }
}

impl From<CoreProperties> for kreuzberg::CoreProperties {
    fn from(v: CoreProperties) -> Self {
        kreuzberg::CoreProperties {
            title: v.title.map(Into::into),
            subject: v.subject.map(Into::into),
            creator: v.creator.map(Into::into),
            keywords: v.keywords.map(Into::into),
            description: v.description.map(Into::into),
            last_modified_by: v.last_modified_by.map(Into::into),
            revision: v.revision.map(Into::into),
            created: v.created.map(Into::into),
            modified: v.modified.map(Into::into),
            category: v.category.map(Into::into),
            content_status: v.content_status.map(Into::into),
            language: v.language.map(Into::into),
            identifier: v.identifier.map(Into::into),
            version: v.version.map(Into::into),
            last_printed: v.last_printed.map(Into::into),
        }
    }
}

impl From<SecurityLimits> for kreuzberg::SecurityLimits {
    fn from(v: SecurityLimits) -> Self {
        kreuzberg::SecurityLimits {
            max_archive_size: v.max_archive_size as _,
            max_compression_ratio: v.max_compression_ratio as _,
            max_files_in_archive: v.max_files_in_archive as _,
            max_nesting_depth: v.max_nesting_depth as _,
            max_entity_length: v.max_entity_length as _,
            max_content_size: v.max_content_size as _,
            max_iterations: v.max_iterations as _,
            max_xml_depth: v.max_xml_depth as _,
            max_table_cells: v.max_table_cells as _,
        }
    }
}

impl From<PdfAnnotation> for kreuzberg::PdfAnnotation {
    fn from(v: PdfAnnotation) -> Self {
        kreuzberg::PdfAnnotation {
            annotation_type: v.annotation_type.into(),
            content: v.content.map(Into::into),
            page_number: v.page_number as _,
            bounding_box: v.bounding_box.map(Into::into),
        }
    }
}

impl From<DjotContent> for kreuzberg::DjotContent {
    fn from(v: DjotContent) -> Self {
        kreuzberg::DjotContent {
            plain_text: v.plain_text.into(),
            blocks: v.blocks.into_iter().map(Into::into).collect(),
            metadata: v.metadata.into(),
            tables: v.tables.into_iter().map(Into::into).collect(),
            images: v.images.into_iter().map(Into::into).collect(),
            links: v.links.into_iter().map(Into::into).collect(),
            footnotes: v.footnotes.into_iter().map(Into::into).collect(),
            attributes: Default::default(),
        }
    }
}

impl From<FormattedBlock> for kreuzberg::FormattedBlock {
    fn from(v: FormattedBlock) -> Self {
        kreuzberg::FormattedBlock {
            block_type: v.block_type.into(),
            level: v.level.map(|x| x as _),
            inline_content: v.inline_content.into_iter().map(Into::into).collect(),
            attributes: Default::default(),
            language: v.language.map(Into::into),
            code: v.code.map(Into::into),
            children: v.children.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<InlineElement> for kreuzberg::InlineElement {
    fn from(v: InlineElement) -> Self {
        kreuzberg::InlineElement {
            element_type: v.element_type.into(),
            content: v.content.into(),
            attributes: Default::default(),
            metadata: v
                .metadata
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<DjotImage> for kreuzberg::DjotImage {
    fn from(v: DjotImage) -> Self {
        kreuzberg::DjotImage {
            src: v.src.into(),
            alt: v.alt.into(),
            title: v.title.map(Into::into),
            attributes: Default::default(),
        }
    }
}

impl From<DjotLink> for kreuzberg::DjotLink {
    fn from(v: DjotLink) -> Self {
        kreuzberg::DjotLink {
            url: v.url.into(),
            text: v.text.into(),
            title: v.title.map(Into::into),
            attributes: Default::default(),
        }
    }
}

impl From<Footnote> for kreuzberg::Footnote {
    fn from(v: Footnote) -> Self {
        kreuzberg::Footnote {
            label: v.label.into(),
            content: v.content.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<DocumentStructure> for kreuzberg::DocumentStructure {
    fn from(v: DocumentStructure) -> Self {
        kreuzberg::DocumentStructure {
            nodes: v.nodes.into_iter().map(Into::into).collect(),
            source_format: v.source_format.map(Into::into),
            relationships: v.relationships.into_iter().map(Into::into).collect(),
            node_types: v.node_types.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<DocumentRelationship> for kreuzberg::DocumentRelationship {
    fn from(v: DocumentRelationship) -> Self {
        kreuzberg::DocumentRelationship {
            source: kreuzberg::NodeIndex(v.source as _),
            target: kreuzberg::NodeIndex(v.target as _),
            kind: v.kind.into(),
        }
    }
}

impl From<DocumentNode> for kreuzberg::DocumentNode {
    fn from(v: DocumentNode) -> Self {
        kreuzberg::DocumentNode {
            id: Default::default(),
            content: v.content.into(),
            parent: v.parent.map(|x| kreuzberg::NodeIndex(x as _)),
            children: v.children.into_iter().map(|x| kreuzberg::NodeIndex(x as _)).collect(),
            content_layer: v.content_layer.into(),
            page: v.page.map(|x| x as _),
            page_end: v.page_end.map(|x| x as _),
            bbox: v.bbox.map(Into::into),
            annotations: v.annotations.into_iter().map(Into::into).collect(),
            attributes: v
                .attributes
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<TableGrid> for kreuzberg::TableGrid {
    fn from(v: TableGrid) -> Self {
        kreuzberg::TableGrid {
            rows: v.rows as _,
            cols: v.cols as _,
            cells: v.cells.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<GridCell> for kreuzberg::GridCell {
    fn from(v: GridCell) -> Self {
        kreuzberg::GridCell {
            content: v.content.into(),
            row: v.row as _,
            col: v.col as _,
            row_span: v.row_span as _,
            col_span: v.col_span as _,
            is_header: v.is_header as _,
            bbox: v.bbox.map(Into::into),
        }
    }
}

impl From<TextAnnotation> for kreuzberg::TextAnnotation {
    fn from(v: TextAnnotation) -> Self {
        kreuzberg::TextAnnotation {
            start: v.start as _,
            end: v.end as _,
            kind: v.kind.into(),
        }
    }
}

#[allow(clippy::needless_update)]
impl From<ExtractionResult> for kreuzberg::ExtractionResult {
    fn from(v: ExtractionResult) -> Self {
        kreuzberg::ExtractionResult {
            content: v.content.into(),
            mime_type: v.mime_type.into(),
            metadata: v.metadata.into(),
            extraction_method: v.extraction_method.map(Into::into),
            tables: v.tables.into_iter().map(Into::into).collect(),
            detected_languages: v
                .detected_languages
                .map(|vec| vec.into_iter().map(Into::into).collect()),
            chunks: v.chunks.map(|vec| vec.into_iter().map(Into::into).collect()),
            images: v.images.map(|vec| vec.into_iter().map(Into::into).collect()),
            pages: v.pages.map(|vec| vec.into_iter().map(Into::into).collect()),
            elements: v.elements.map(|vec| vec.into_iter().map(Into::into).collect()),
            djot_content: v.djot_content.map(Into::into),
            ocr_elements: v.ocr_elements.map(|vec| vec.into_iter().map(Into::into).collect()),
            document: v.document.map(Into::into),
            extracted_keywords: v
                .extracted_keywords
                .map(|vec| vec.into_iter().map(Into::into).collect()),
            quality_score: v.quality_score.map(|x| x as _),
            processing_warnings: v.processing_warnings.into_iter().map(Into::into).collect(),
            annotations: v.annotations.map(|vec| vec.into_iter().map(Into::into).collect()),
            children: v.children.map(|vec| vec.into_iter().map(Into::into).collect()),
            uris: v.uris.map(|vec| vec.into_iter().map(Into::into).collect()),
            revisions: v.revisions.map(|vec| vec.into_iter().map(Into::into).collect()),
            structured_output: v
                .structured_output
                .as_deref()
                .and_then(|s| serde_json::from_str(s).ok()),
            code_intelligence: v
                .code_intelligence
                .as_deref()
                .and_then(|s| serde_json::from_str(s).ok()),
            llm_usage: v.llm_usage.map(|vec| vec.into_iter().map(Into::into).collect()),
            formatted_content: v.formatted_content.map(Into::into),
            ocr_internal_document: Default::default(),
            ..Default::default()
        }
    }
}

impl From<ArchiveEntry> for kreuzberg::ArchiveEntry {
    fn from(v: ArchiveEntry) -> Self {
        kreuzberg::ArchiveEntry {
            path: v.path.into(),
            mime_type: v.mime_type.into(),
            result: Box::new(v.result.into()),
        }
    }
}

impl From<ProcessingWarning> for kreuzberg::ProcessingWarning {
    fn from(v: ProcessingWarning) -> Self {
        kreuzberg::ProcessingWarning {
            source: v.source.into(),
            message: v.message.into(),
        }
    }
}

impl From<LlmUsage> for kreuzberg::LlmUsage {
    fn from(v: LlmUsage) -> Self {
        kreuzberg::LlmUsage {
            model: v.model.into(),
            source: v.source.into(),
            input_tokens: v.input_tokens.map(|x| x as _),
            output_tokens: v.output_tokens.map(|x| x as _),
            total_tokens: v.total_tokens.map(|x| x as _),
            estimated_cost: v.estimated_cost.map(|x| x as _),
            finish_reason: v.finish_reason.map(Into::into),
        }
    }
}

impl From<Chunk> for kreuzberg::Chunk {
    fn from(v: Chunk) -> Self {
        kreuzberg::Chunk {
            content: v.content.into(),
            chunk_type: v.chunk_type.into(),
            embedding: v.embedding.map(|vec| vec.into_iter().map(|x| x as _).collect()),
            metadata: v.metadata.into(),
        }
    }
}

impl From<HeadingContext> for kreuzberg::HeadingContext {
    fn from(v: HeadingContext) -> Self {
        kreuzberg::HeadingContext {
            headings: v.headings.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<HeadingLevel> for kreuzberg::HeadingLevel {
    fn from(v: HeadingLevel) -> Self {
        kreuzberg::HeadingLevel {
            level: v.level as _,
            text: v.text.into(),
        }
    }
}

impl From<ChunkMetadata> for kreuzberg::ChunkMetadata {
    fn from(v: ChunkMetadata) -> Self {
        kreuzberg::ChunkMetadata {
            byte_start: v.byte_start as _,
            byte_end: v.byte_end as _,
            token_count: v.token_count.map(|x| x as _),
            chunk_index: v.chunk_index as _,
            total_chunks: v.total_chunks as _,
            first_page: v.first_page.map(|x| x as _),
            last_page: v.last_page.map(|x| x as _),
            heading_context: v.heading_context.map(Into::into),
            image_indices: v.image_indices.into_iter().map(|x| x as _).collect(),
        }
    }
}

impl From<ExtractedImage> for kreuzberg::ExtractedImage {
    fn from(v: ExtractedImage) -> Self {
        kreuzberg::ExtractedImage {
            data: v.data.into(),
            format: v.format.into(),
            image_index: v.image_index as _,
            page_number: v.page_number.map(|x| x as _),
            width: v.width.map(|x| x as _),
            height: v.height.map(|x| x as _),
            colorspace: v.colorspace.map(Into::into),
            bits_per_component: v.bits_per_component.map(|x| x as _),
            is_mask: v.is_mask as _,
            description: v.description.map(Into::into),
            ocr_result: v.ocr_result.map(|x| Box::new(x.into())),
            bounding_box: v.bounding_box.map(Into::into),
            source_path: v.source_path.map(Into::into),
            image_kind: v.image_kind.map(Into::into),
            kind_confidence: v.kind_confidence.map(|x| x as _),
            cluster_id: v.cluster_id.map(|x| x as _),
        }
    }
}

impl From<BoundingBox> for kreuzberg::BoundingBox {
    fn from(v: BoundingBox) -> Self {
        kreuzberg::BoundingBox {
            x0: v.x0 as _,
            y0: v.y0 as _,
            x1: v.x1 as _,
            y1: v.y1 as _,
        }
    }
}

impl From<ElementMetadata> for kreuzberg::ElementMetadata {
    fn from(v: ElementMetadata) -> Self {
        kreuzberg::ElementMetadata {
            page_number: v.page_number.map(|x| x as _),
            filename: v.filename.map(Into::into),
            coordinates: v.coordinates.map(Into::into),
            element_index: v.element_index.map(|x| x as _),
            additional: v.additional.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

impl From<Element> for kreuzberg::Element {
    fn from(v: Element) -> Self {
        kreuzberg::Element {
            element_id: Default::default(),
            element_type: v.element_type.into(),
            text: v.text.into(),
            metadata: v.metadata.into(),
        }
    }
}

impl From<ImagePreprocessingConfig> for kreuzberg::ImagePreprocessingConfig {
    fn from(v: ImagePreprocessingConfig) -> Self {
        kreuzberg::ImagePreprocessingConfig {
            target_dpi: v.target_dpi as _,
            auto_rotate: v.auto_rotate as _,
            deskew: v.deskew as _,
            denoise: v.denoise as _,
            contrast_enhance: v.contrast_enhance as _,
            binarization_method: v.binarization_method.into(),
            invert_colors: v.invert_colors as _,
        }
    }
}

impl From<TesseractConfig> for kreuzberg::TesseractConfig {
    fn from(v: TesseractConfig) -> Self {
        kreuzberg::TesseractConfig {
            language: v.language.into(),
            psm: v.psm as _,
            output_format: v.output_format.into(),
            oem: v.oem as _,
            min_confidence: v.min_confidence as _,
            preprocessing: v.preprocessing.map(Into::into),
            enable_table_detection: v.enable_table_detection as _,
            table_min_confidence: v.table_min_confidence as _,
            table_column_threshold: v.table_column_threshold as _,
            table_row_threshold_ratio: v.table_row_threshold_ratio as _,
            use_cache: v.use_cache as _,
            classify_use_pre_adapted_templates: v.classify_use_pre_adapted_templates as _,
            language_model_ngram_on: v.language_model_ngram_on as _,
            tessedit_dont_blkrej_good_wds: v.tessedit_dont_blkrej_good_wds as _,
            tessedit_dont_rowrej_good_wds: v.tessedit_dont_rowrej_good_wds as _,
            tessedit_enable_dict_correction: v.tessedit_enable_dict_correction as _,
            tessedit_char_whitelist: v.tessedit_char_whitelist.into(),
            tessedit_char_blacklist: v.tessedit_char_blacklist.into(),
            tessedit_use_primary_params_model: v.tessedit_use_primary_params_model as _,
            textord_space_size_is_variable: v.textord_space_size_is_variable as _,
            thresholding_method: v.thresholding_method as _,
        }
    }
}

impl From<ImagePreprocessingMetadata> for kreuzberg::ImagePreprocessingMetadata {
    fn from(v: ImagePreprocessingMetadata) -> Self {
        kreuzberg::ImagePreprocessingMetadata {
            original_dimensions: Default::default(),
            original_dpi: Default::default(),
            target_dpi: v.target_dpi as _,
            scale_factor: v.scale_factor as _,
            auto_adjusted: v.auto_adjusted as _,
            final_dpi: v.final_dpi as _,
            new_dimensions: Default::default(),
            resample_method: v.resample_method.into(),
            dimension_clamped: v.dimension_clamped as _,
            calculated_dpi: v.calculated_dpi.map(|x| x as _),
            skipped_resize: v.skipped_resize as _,
            resize_error: v.resize_error.map(Into::into),
        }
    }
}

impl From<Metadata> for kreuzberg::Metadata {
    fn from(v: Metadata) -> Self {
        kreuzberg::Metadata {
            title: v.title.map(Into::into),
            subject: v.subject.map(Into::into),
            authors: v.authors.map(|vec| vec.into_iter().map(Into::into).collect()),
            keywords: v.keywords.map(|vec| vec.into_iter().map(Into::into).collect()),
            language: v.language.map(Into::into),
            created_at: v.created_at.map(Into::into),
            modified_at: v.modified_at.map(Into::into),
            created_by: v.created_by.map(Into::into),
            modified_by: v.modified_by.map(Into::into),
            pages: v.pages.map(Into::into),
            format: v.format.map(Into::into),
            image_preprocessing: v.image_preprocessing.map(Into::into),
            json_schema: v.json_schema.as_deref().and_then(|s| serde_json::from_str(s).ok()),
            error: v.error.map(Into::into),
            extraction_duration_ms: v.extraction_duration_ms.map(|x| x as _),
            category: v.category.map(Into::into),
            tags: v.tags.map(|vec| vec.into_iter().map(Into::into).collect()),
            document_version: v.document_version.map(Into::into),
            abstract_text: v.abstract_text.map(Into::into),
            output_format: v.output_format.map(Into::into),
            ocr_used: v.ocr_used as _,
            additional: v.additional.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

impl From<ExcelMetadata> for kreuzberg::ExcelMetadata {
    fn from(v: ExcelMetadata) -> Self {
        kreuzberg::ExcelMetadata {
            sheet_count: v.sheet_count.map(|x| x as _),
            sheet_names: v.sheet_names.map(|vec| vec.into_iter().map(Into::into).collect()),
        }
    }
}

impl From<EmailMetadata> for kreuzberg::EmailMetadata {
    fn from(v: EmailMetadata) -> Self {
        kreuzberg::EmailMetadata {
            from_email: v.from_email.map(Into::into),
            from_name: v.from_name.map(Into::into),
            to_emails: v.to_emails.into_iter().map(Into::into).collect(),
            cc_emails: v.cc_emails.into_iter().map(Into::into).collect(),
            bcc_emails: v.bcc_emails.into_iter().map(Into::into).collect(),
            message_id: v.message_id.map(Into::into),
            attachments: v.attachments.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<ArchiveMetadata> for kreuzberg::ArchiveMetadata {
    fn from(v: ArchiveMetadata) -> Self {
        kreuzberg::ArchiveMetadata {
            format: v.format.into(),
            file_count: v.file_count as _,
            file_list: v.file_list.into_iter().map(Into::into).collect(),
            total_size: v.total_size as _,
            compressed_size: v.compressed_size.map(|x| x as _),
        }
    }
}

impl From<ImageMetadata> for kreuzberg::ImageMetadata {
    fn from(v: ImageMetadata) -> Self {
        kreuzberg::ImageMetadata {
            width: v.width as _,
            height: v.height as _,
            format: v.format.into(),
            exif: v.exif.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

impl From<XmlMetadata> for kreuzberg::XmlMetadata {
    fn from(v: XmlMetadata) -> Self {
        kreuzberg::XmlMetadata {
            element_count: v.element_count as _,
            unique_elements: v.unique_elements.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<TextMetadata> for kreuzberg::TextMetadata {
    fn from(v: TextMetadata) -> Self {
        kreuzberg::TextMetadata {
            line_count: v.line_count as _,
            word_count: v.word_count as _,
            character_count: v.character_count as _,
            headers: v.headers.map(|vec| vec.into_iter().map(Into::into).collect()),
            links: Default::default(),
            code_blocks: Default::default(),
        }
    }
}

impl From<HeaderMetadata> for kreuzberg::HeaderMetadata {
    fn from(v: HeaderMetadata) -> Self {
        kreuzberg::HeaderMetadata {
            level: v.level as _,
            text: v.text.into(),
            id: v.id.map(Into::into),
            depth: v.depth as _,
            html_offset: v.html_offset as _,
        }
    }
}

impl From<LinkMetadata> for kreuzberg::LinkMetadata {
    fn from(v: LinkMetadata) -> Self {
        kreuzberg::LinkMetadata {
            href: v.href.into(),
            text: v.text.into(),
            title: v.title.map(Into::into),
            link_type: v.link_type.into(),
            rel: v.rel.into_iter().map(Into::into).collect(),
            attributes: Default::default(),
        }
    }
}

impl From<ImageMetadataType> for kreuzberg::ImageMetadataType {
    fn from(v: ImageMetadataType) -> Self {
        kreuzberg::ImageMetadataType {
            src: v.src.into(),
            alt: v.alt.map(Into::into),
            title: v.title.map(Into::into),
            dimensions: Default::default(),
            image_type: v.image_type.into(),
            attributes: Default::default(),
        }
    }
}

impl From<StructuredData> for kreuzberg::StructuredData {
    fn from(v: StructuredData) -> Self {
        kreuzberg::StructuredData {
            data_type: v.data_type.into(),
            raw_json: v.raw_json.into(),
            schema_type: v.schema_type.map(Into::into),
        }
    }
}

impl From<HtmlMetadata> for kreuzberg::HtmlMetadata {
    fn from(v: HtmlMetadata) -> Self {
        kreuzberg::HtmlMetadata {
            title: v.title.map(Into::into),
            description: v.description.map(Into::into),
            keywords: v.keywords.into_iter().map(Into::into).collect(),
            author: v.author.map(Into::into),
            canonical_url: v.canonical_url.map(Into::into),
            base_href: v.base_href.map(Into::into),
            language: v.language.map(Into::into),
            text_direction: v.text_direction.map(Into::into),
            open_graph: v.open_graph.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            twitter_card: v.twitter_card.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            meta_tags: v.meta_tags.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            headers: v.headers.into_iter().map(Into::into).collect(),
            links: v.links.into_iter().map(Into::into).collect(),
            images: v.images.into_iter().map(Into::into).collect(),
            structured_data: v.structured_data.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<OcrMetadata> for kreuzberg::OcrMetadata {
    fn from(v: OcrMetadata) -> Self {
        kreuzberg::OcrMetadata {
            language: v.language.into(),
            psm: v.psm as _,
            output_format: v.output_format.into(),
            table_count: v.table_count as _,
            table_rows: v.table_rows.map(|x| x as _),
            table_cols: v.table_cols.map(|x| x as _),
        }
    }
}

impl From<ErrorMetadata> for kreuzberg::ErrorMetadata {
    fn from(v: ErrorMetadata) -> Self {
        kreuzberg::ErrorMetadata {
            error_type: v.error_type.into(),
            message: v.message.into(),
        }
    }
}

impl From<PptxMetadata> for kreuzberg::PptxMetadata {
    fn from(v: PptxMetadata) -> Self {
        kreuzberg::PptxMetadata {
            slide_count: v.slide_count as _,
            slide_names: v.slide_names.into_iter().map(Into::into).collect(),
            image_count: v.image_count.map(|x| x as _),
            table_count: v.table_count.map(|x| x as _),
        }
    }
}

impl From<DocxMetadata> for kreuzberg::DocxMetadata {
    fn from(v: DocxMetadata) -> Self {
        kreuzberg::DocxMetadata {
            core_properties: v.core_properties.map(Into::into),
            app_properties: v.app_properties.map(Into::into),
            custom_properties: v
                .custom_properties
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

impl From<CsvMetadata> for kreuzberg::CsvMetadata {
    fn from(v: CsvMetadata) -> Self {
        kreuzberg::CsvMetadata {
            row_count: v.row_count as _,
            column_count: v.column_count as _,
            delimiter: v.delimiter.map(Into::into),
            has_header: v.has_header as _,
            column_types: v.column_types.map(|vec| vec.into_iter().map(Into::into).collect()),
        }
    }
}

impl From<BibtexMetadata> for kreuzberg::BibtexMetadata {
    fn from(v: BibtexMetadata) -> Self {
        kreuzberg::BibtexMetadata {
            entry_count: v.entry_count as _,
            citation_keys: v.citation_keys.into_iter().map(Into::into).collect(),
            authors: v.authors.into_iter().map(Into::into).collect(),
            year_range: v.year_range.map(Into::into),
            entry_types: v
                .entry_types
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v as _)).collect()),
        }
    }
}

impl From<CitationMetadata> for kreuzberg::CitationMetadata {
    fn from(v: CitationMetadata) -> Self {
        kreuzberg::CitationMetadata {
            citation_count: v.citation_count as _,
            format: v.format.map(Into::into),
            authors: v.authors.into_iter().map(Into::into).collect(),
            year_range: v.year_range.map(Into::into),
            dois: v.dois.into_iter().map(Into::into).collect(),
            keywords: v.keywords.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<YearRange> for kreuzberg::YearRange {
    fn from(v: YearRange) -> Self {
        kreuzberg::YearRange {
            min: v.min.map(|x| x as _),
            max: v.max.map(|x| x as _),
            years: v.years.into_iter().map(|x| x as _).collect(),
        }
    }
}

impl From<FictionBookMetadata> for kreuzberg::FictionBookMetadata {
    fn from(v: FictionBookMetadata) -> Self {
        kreuzberg::FictionBookMetadata {
            genres: v.genres.into_iter().map(Into::into).collect(),
            sequences: v.sequences.into_iter().map(Into::into).collect(),
            annotation: v.annotation.map(Into::into),
        }
    }
}

impl From<DbfMetadata> for kreuzberg::DbfMetadata {
    fn from(v: DbfMetadata) -> Self {
        kreuzberg::DbfMetadata {
            record_count: v.record_count as _,
            field_count: v.field_count as _,
            fields: v.fields.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<DbfFieldInfo> for kreuzberg::DbfFieldInfo {
    fn from(v: DbfFieldInfo) -> Self {
        kreuzberg::DbfFieldInfo {
            name: v.name.into(),
            field_type: v.field_type.into(),
        }
    }
}

impl From<JatsMetadata> for kreuzberg::JatsMetadata {
    fn from(v: JatsMetadata) -> Self {
        kreuzberg::JatsMetadata {
            copyright: v.copyright.map(Into::into),
            license: v.license.map(Into::into),
            history_dates: v.history_dates.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            contributor_roles: v.contributor_roles.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<ContributorRole> for kreuzberg::ContributorRole {
    fn from(v: ContributorRole) -> Self {
        kreuzberg::ContributorRole {
            name: v.name.into(),
            role: v.role.map(Into::into),
        }
    }
}

impl From<EpubMetadata> for kreuzberg::EpubMetadata {
    fn from(v: EpubMetadata) -> Self {
        kreuzberg::EpubMetadata {
            coverage: v.coverage.map(Into::into),
            dc_format: v.dc_format.map(Into::into),
            relation: v.relation.map(Into::into),
            source: v.source.map(Into::into),
            dc_type: v.dc_type.map(Into::into),
            cover_image: v.cover_image.map(Into::into),
        }
    }
}

impl From<PstMetadata> for kreuzberg::PstMetadata {
    fn from(v: PstMetadata) -> Self {
        kreuzberg::PstMetadata {
            message_count: v.message_count as _,
        }
    }
}

impl From<OcrConfidence> for kreuzberg::OcrConfidence {
    fn from(v: OcrConfidence) -> Self {
        kreuzberg::OcrConfidence {
            detection: v.detection.map(|x| x as _),
            recognition: v.recognition as _,
        }
    }
}

impl From<OcrRotation> for kreuzberg::OcrRotation {
    fn from(v: OcrRotation) -> Self {
        kreuzberg::OcrRotation {
            angle_degrees: v.angle_degrees as _,
            confidence: v.confidence.map(|x| x as _),
        }
    }
}

impl From<OcrElement> for kreuzberg::OcrElement {
    fn from(v: OcrElement) -> Self {
        kreuzberg::OcrElement {
            text: v.text.into(),
            geometry: v.geometry.into(),
            confidence: v.confidence.into(),
            level: v.level.into(),
            rotation: v.rotation.map(Into::into),
            page_number: v.page_number as _,
            parent_id: v.parent_id.map(Into::into),
            backend_metadata: v
                .backend_metadata
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
        }
    }
}

impl From<OcrElementConfig> for kreuzberg::OcrElementConfig {
    fn from(v: OcrElementConfig) -> Self {
        kreuzberg::OcrElementConfig {
            include_elements: v.include_elements as _,
            min_level: v.min_level.into(),
            min_confidence: v.min_confidence as _,
            build_hierarchy: v.build_hierarchy as _,
        }
    }
}

impl From<PageStructure> for kreuzberg::PageStructure {
    fn from(v: PageStructure) -> Self {
        kreuzberg::PageStructure {
            total_count: v.total_count as _,
            unit_type: v.unit_type.into(),
            boundaries: v.boundaries.map(|vec| vec.into_iter().map(Into::into).collect()),
            pages: v.pages.map(|vec| vec.into_iter().map(Into::into).collect()),
        }
    }
}

impl From<PageBoundary> for kreuzberg::PageBoundary {
    fn from(v: PageBoundary) -> Self {
        kreuzberg::PageBoundary {
            byte_start: v.byte_start as _,
            byte_end: v.byte_end as _,
            page_number: v.page_number as _,
        }
    }
}

impl From<PageInfo> for kreuzberg::PageInfo {
    fn from(v: PageInfo) -> Self {
        kreuzberg::PageInfo {
            number: v.number as _,
            title: v.title.map(Into::into),
            dimensions: Default::default(),
            image_count: v.image_count.map(|x| x as _),
            table_count: v.table_count.map(|x| x as _),
            hidden: v.hidden.map(|x| x as _),
            is_blank: v.is_blank.map(|x| x as _),
            has_vector_graphics: v.has_vector_graphics as _,
        }
    }
}

impl From<PageContent> for kreuzberg::PageContent {
    fn from(v: PageContent) -> Self {
        kreuzberg::PageContent {
            page_number: v.page_number as _,
            content: v.content.into(),
            tables: v.tables.into_iter().map(|x| std::sync::Arc::new(x.into())).collect(),
            image_indices: v.image_indices.into_iter().map(|x| x as _).collect(),
            hierarchy: v.hierarchy.map(Into::into),
            is_blank: v.is_blank.map(|x| x as _),
            layout_regions: v.layout_regions.map(|vec| vec.into_iter().map(Into::into).collect()),
            speaker_notes: v.speaker_notes.map(Into::into),
            section_name: v.section_name.map(Into::into),
            sheet_name: v.sheet_name.map(Into::into),
        }
    }
}

impl From<LayoutRegion> for kreuzberg::LayoutRegion {
    fn from(v: LayoutRegion) -> Self {
        kreuzberg::LayoutRegion {
            class_name: v.class_name.into(),
            confidence: v.confidence as _,
            bounding_box: v.bounding_box.into(),
            area_fraction: v.area_fraction as _,
        }
    }
}

impl From<PageHierarchy> for kreuzberg::PageHierarchy {
    fn from(v: PageHierarchy) -> Self {
        kreuzberg::PageHierarchy {
            block_count: v.block_count as _,
            blocks: v.blocks.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<HierarchicalBlock> for kreuzberg::HierarchicalBlock {
    fn from(v: HierarchicalBlock) -> Self {
        kreuzberg::HierarchicalBlock {
            text: v.text.into(),
            font_size: v.font_size as _,
            level: v.level.into(),
            bbox: Default::default(),
        }
    }
}

impl From<CellChange> for kreuzberg::CellChange {
    fn from(v: CellChange) -> Self {
        kreuzberg::CellChange {
            row: v.row as _,
            col: v.col as _,
            from: v.from.into(),
            to: v.to.into(),
        }
    }
}

impl From<DocumentRevision> for kreuzberg::DocumentRevision {
    fn from(v: DocumentRevision) -> Self {
        kreuzberg::DocumentRevision {
            revision_id: v.revision_id.into(),
            author: v.author.map(Into::into),
            timestamp: v.timestamp.map(Into::into),
            kind: v.kind.into(),
            anchor: v.anchor.map(Into::into),
            delta: v.delta.into(),
        }
    }
}

impl From<RevisionDelta> for kreuzberg::RevisionDelta {
    fn from(v: RevisionDelta) -> Self {
        kreuzberg::RevisionDelta {
            content: v.content.into_iter().map(Into::into).collect(),
            table_changes: v.table_changes.into_iter().map(Into::into).collect(),
        }
    }
}

impl From<Table> for kreuzberg::Table {
    fn from(v: Table) -> Self {
        kreuzberg::Table {
            cells: v
                .cells
                .into_iter()
                .map(|inner| inner.into_iter().map(Into::into).collect())
                .collect(),
            markdown: v.markdown.into(),
            page_number: v.page_number as _,
            bounding_box: v.bounding_box.map(Into::into),
        }
    }
}

impl From<ExtractedUri> for kreuzberg::ExtractedUri {
    fn from(v: ExtractedUri) -> Self {
        kreuzberg::ExtractedUri {
            url: v.url.into(),
            label: v.label.map(Into::into),
            page: v.page.map(|x| x as _),
            kind: v.kind.into(),
        }
    }
}

impl From<DiffOptions> for kreuzberg::DiffOptions {
    fn from(v: DiffOptions) -> Self {
        kreuzberg::DiffOptions {
            include_metadata: v.include_metadata as _,
            include_embedded: v.include_embedded as _,
            max_content_chars: v.max_content_chars.map(|x| x as _),
        }
    }
}

impl From<YakeParams> for kreuzberg::YakeParams {
    fn from(v: YakeParams) -> Self {
        kreuzberg::YakeParams {
            window_size: v.window_size as _,
        }
    }
}

impl From<RakeParams> for kreuzberg::RakeParams {
    fn from(v: RakeParams) -> Self {
        kreuzberg::RakeParams {
            min_word_length: v.min_word_length as _,
            max_words_per_phrase: v.max_words_per_phrase as _,
        }
    }
}

#[allow(clippy::needless_update)]
impl From<KeywordConfig> for kreuzberg::KeywordConfig {
    fn from(v: KeywordConfig) -> Self {
        kreuzberg::KeywordConfig {
            algorithm: v.algorithm.into(),
            max_keywords: v.max_keywords as _,
            min_score: v.min_score as _,
            ngram_range: Default::default(),
            language: v.language.map(Into::into),
            yake_params: v.yake_params.map(Into::into),
            rake_params: v.rake_params.map(Into::into),
            ..Default::default()
        }
    }
}

impl From<Keyword> for kreuzberg::Keyword {
    fn from(v: Keyword) -> Self {
        kreuzberg::Keyword {
            text: v.text.into(),
            score: v.score as _,
            algorithm: v.algorithm.into(),
            positions: v.positions.map(|vec| vec.into_iter().map(|x| x as _).collect()),
        }
    }
}

impl From<PdfMetadata> for kreuzberg::pdf::metadata::PdfMetadata {
    fn from(v: PdfMetadata) -> Self {
        kreuzberg::pdf::metadata::PdfMetadata {
            pdf_version: v.pdf_version.map(Into::into),
            producer: v.producer.map(Into::into),
            is_encrypted: v.is_encrypted.map(|x| x as _),
            width: v.width.map(|x| x as _),
            height: v.height.map(|x| x as _),
            page_count: v.page_count.map(|x| x as _),
        }
    }
}

impl From<ExecutionProviderType> for kreuzberg::ExecutionProviderType {
    fn from(v: ExecutionProviderType) -> Self {
        match v {
            ExecutionProviderType::Auto => kreuzberg::ExecutionProviderType::Auto,
            ExecutionProviderType::Cpu => kreuzberg::ExecutionProviderType::Cpu,
            ExecutionProviderType::CoreMl => kreuzberg::ExecutionProviderType::CoreMl,
            ExecutionProviderType::Cuda => kreuzberg::ExecutionProviderType::Cuda,
            ExecutionProviderType::TensorRt => kreuzberg::ExecutionProviderType::TensorRt,
        }
    }
}

impl From<OutputFormat> for kreuzberg::OutputFormat {
    fn from(v: OutputFormat) -> Self {
        match v {
            OutputFormat::Plain => kreuzberg::OutputFormat::Plain,
            OutputFormat::Markdown => kreuzberg::OutputFormat::Markdown,
            OutputFormat::Djot => kreuzberg::OutputFormat::Djot,
            OutputFormat::Html => kreuzberg::OutputFormat::Html,
            OutputFormat::Json => kreuzberg::OutputFormat::Json,
            OutputFormat::Structured => kreuzberg::OutputFormat::Structured,
            OutputFormat::Custom { field0 } => kreuzberg::OutputFormat::Custom(field0),
        }
    }
}

impl From<HtmlTheme> for kreuzberg::HtmlTheme {
    fn from(v: HtmlTheme) -> Self {
        match v {
            HtmlTheme::Default => kreuzberg::HtmlTheme::Default,
            HtmlTheme::GitHub => kreuzberg::HtmlTheme::GitHub,
            HtmlTheme::Dark => kreuzberg::HtmlTheme::Dark,
            HtmlTheme::Light => kreuzberg::HtmlTheme::Light,
            HtmlTheme::Unstyled => kreuzberg::HtmlTheme::Unstyled,
        }
    }
}

impl From<TableModel> for kreuzberg::TableModel {
    fn from(v: TableModel) -> Self {
        match v {
            TableModel::Tatr => kreuzberg::TableModel::Tatr,
            TableModel::SlanetWired => kreuzberg::TableModel::SlanetWired,
            TableModel::SlanetWireless => kreuzberg::TableModel::SlanetWireless,
            TableModel::SlanetPlus => kreuzberg::TableModel::SlanetPlus,
            TableModel::SlanetAuto => kreuzberg::TableModel::SlanetAuto,
            TableModel::Disabled => kreuzberg::TableModel::Disabled,
        }
    }
}

impl From<ChunkerType> for kreuzberg::ChunkerType {
    fn from(v: ChunkerType) -> Self {
        match v {
            ChunkerType::Text => kreuzberg::ChunkerType::Text,
            ChunkerType::Markdown => kreuzberg::ChunkerType::Markdown,
            ChunkerType::Yaml => kreuzberg::ChunkerType::Yaml,
            ChunkerType::Semantic => kreuzberg::ChunkerType::Semantic,
        }
    }
}

impl From<ChunkSizing> for kreuzberg::ChunkSizing {
    fn from(v: ChunkSizing) -> Self {
        match v {
            ChunkSizing::Characters => kreuzberg::ChunkSizing::Characters,
            ChunkSizing::Tokenizer { model, cache_dir } => kreuzberg::ChunkSizing::Tokenizer {
                model,
                cache_dir: if cache_dir.is_empty() {
                    None
                } else {
                    Some(std::path::PathBuf::from(cache_dir))
                },
            },
        }
    }
}

impl From<EmbeddingModelType> for kreuzberg::EmbeddingModelType {
    fn from(v: EmbeddingModelType) -> Self {
        match v {
            EmbeddingModelType::Preset { name } => kreuzberg::EmbeddingModelType::Preset { name },
            EmbeddingModelType::Custom { model_id, dimensions } => kreuzberg::EmbeddingModelType::Custom {
                model_id,
                dimensions: dimensions as _,
            },
            EmbeddingModelType::Llm { llm } => kreuzberg::EmbeddingModelType::Llm { llm: llm.into() },
            EmbeddingModelType::Plugin { name } => kreuzberg::EmbeddingModelType::Plugin { name },
        }
    }
}

impl From<CodeContentMode> for kreuzberg::CodeContentMode {
    fn from(v: CodeContentMode) -> Self {
        match v {
            CodeContentMode::Chunks => kreuzberg::CodeContentMode::Chunks,
            CodeContentMode::Raw => kreuzberg::CodeContentMode::Raw,
            CodeContentMode::Structure => kreuzberg::CodeContentMode::Structure,
        }
    }
}

impl From<OcrBackendType> for kreuzberg::OcrBackendType {
    fn from(v: OcrBackendType) -> Self {
        match v {
            OcrBackendType::Tesseract => kreuzberg::OcrBackendType::Tesseract,
            OcrBackendType::EasyOCR => kreuzberg::OcrBackendType::EasyOCR,
            OcrBackendType::PaddleOCR => kreuzberg::OcrBackendType::PaddleOCR,
            OcrBackendType::Custom => kreuzberg::OcrBackendType::Custom,
        }
    }
}

impl From<ProcessingStage> for kreuzberg::ProcessingStage {
    fn from(v: ProcessingStage) -> Self {
        match v {
            ProcessingStage::Early => kreuzberg::ProcessingStage::Early,
            ProcessingStage::Middle => kreuzberg::ProcessingStage::Middle,
            ProcessingStage::Late => kreuzberg::ProcessingStage::Late,
        }
    }
}

impl From<PdfAnnotationType> for kreuzberg::PdfAnnotationType {
    fn from(v: PdfAnnotationType) -> Self {
        match v {
            PdfAnnotationType::Text => kreuzberg::PdfAnnotationType::Text,
            PdfAnnotationType::Highlight => kreuzberg::PdfAnnotationType::Highlight,
            PdfAnnotationType::Link => kreuzberg::PdfAnnotationType::Link,
            PdfAnnotationType::Stamp => kreuzberg::PdfAnnotationType::Stamp,
            PdfAnnotationType::Underline => kreuzberg::PdfAnnotationType::Underline,
            PdfAnnotationType::StrikeOut => kreuzberg::PdfAnnotationType::StrikeOut,
            PdfAnnotationType::Other => kreuzberg::PdfAnnotationType::Other,
        }
    }
}

impl From<BlockType> for kreuzberg::BlockType {
    fn from(v: BlockType) -> Self {
        match v {
            BlockType::Paragraph => kreuzberg::BlockType::Paragraph,
            BlockType::Heading => kreuzberg::BlockType::Heading,
            BlockType::Blockquote => kreuzberg::BlockType::Blockquote,
            BlockType::CodeBlock => kreuzberg::BlockType::CodeBlock,
            BlockType::ListItem => kreuzberg::BlockType::ListItem,
            BlockType::OrderedList => kreuzberg::BlockType::OrderedList,
            BlockType::BulletList => kreuzberg::BlockType::BulletList,
            BlockType::TaskList => kreuzberg::BlockType::TaskList,
            BlockType::DefinitionList => kreuzberg::BlockType::DefinitionList,
            BlockType::DefinitionTerm => kreuzberg::BlockType::DefinitionTerm,
            BlockType::DefinitionDescription => kreuzberg::BlockType::DefinitionDescription,
            BlockType::Div => kreuzberg::BlockType::Div,
            BlockType::Section => kreuzberg::BlockType::Section,
            BlockType::ThematicBreak => kreuzberg::BlockType::ThematicBreak,
            BlockType::RawBlock => kreuzberg::BlockType::RawBlock,
            BlockType::MathDisplay => kreuzberg::BlockType::MathDisplay,
        }
    }
}

impl From<InlineType> for kreuzberg::InlineType {
    fn from(v: InlineType) -> Self {
        match v {
            InlineType::Text => kreuzberg::InlineType::Text,
            InlineType::Strong => kreuzberg::InlineType::Strong,
            InlineType::Emphasis => kreuzberg::InlineType::Emphasis,
            InlineType::Highlight => kreuzberg::InlineType::Highlight,
            InlineType::Subscript => kreuzberg::InlineType::Subscript,
            InlineType::Superscript => kreuzberg::InlineType::Superscript,
            InlineType::Insert => kreuzberg::InlineType::Insert,
            InlineType::Delete => kreuzberg::InlineType::Delete,
            InlineType::Code => kreuzberg::InlineType::Code,
            InlineType::Link => kreuzberg::InlineType::Link,
            InlineType::Image => kreuzberg::InlineType::Image,
            InlineType::Span => kreuzberg::InlineType::Span,
            InlineType::Math => kreuzberg::InlineType::Math,
            InlineType::RawInline => kreuzberg::InlineType::RawInline,
            InlineType::FootnoteRef => kreuzberg::InlineType::FootnoteRef,
            InlineType::Symbol => kreuzberg::InlineType::Symbol,
        }
    }
}

impl From<RelationshipKind> for kreuzberg::RelationshipKind {
    fn from(v: RelationshipKind) -> Self {
        match v {
            RelationshipKind::FootnoteReference => kreuzberg::RelationshipKind::FootnoteReference,
            RelationshipKind::CitationReference => kreuzberg::RelationshipKind::CitationReference,
            RelationshipKind::InternalLink => kreuzberg::RelationshipKind::InternalLink,
            RelationshipKind::Caption => kreuzberg::RelationshipKind::Caption,
            RelationshipKind::Label => kreuzberg::RelationshipKind::Label,
            RelationshipKind::TocEntry => kreuzberg::RelationshipKind::TocEntry,
            RelationshipKind::CrossReference => kreuzberg::RelationshipKind::CrossReference,
        }
    }
}

impl From<ContentLayer> for kreuzberg::ContentLayer {
    fn from(v: ContentLayer) -> Self {
        match v {
            ContentLayer::Body => kreuzberg::ContentLayer::Body,
            ContentLayer::Header => kreuzberg::ContentLayer::Header,
            ContentLayer::Footer => kreuzberg::ContentLayer::Footer,
            ContentLayer::Footnote => kreuzberg::ContentLayer::Footnote,
        }
    }
}

impl From<NodeContent> for kreuzberg::NodeContent {
    fn from(v: NodeContent) -> Self {
        match v {
            NodeContent::Title { text } => kreuzberg::NodeContent::Title { text },
            NodeContent::Heading { level, text } => kreuzberg::NodeContent::Heading {
                level: level as _,
                text,
            },
            NodeContent::Paragraph { text } => kreuzberg::NodeContent::Paragraph { text },
            NodeContent::List { ordered } => kreuzberg::NodeContent::List { ordered },
            NodeContent::ListItem { text } => kreuzberg::NodeContent::ListItem { text },
            NodeContent::Table { grid } => kreuzberg::NodeContent::Table { grid: grid.into() },
            NodeContent::Image {
                description,
                image_index,
                src,
            } => kreuzberg::NodeContent::Image {
                description: if description.is_empty() {
                    None
                } else {
                    Some(description)
                },
                image_index: if image_index == 0 { None } else { Some(image_index as _) },
                src: if src.is_empty() { None } else { Some(src) },
            },
            NodeContent::Code { text, language } => kreuzberg::NodeContent::Code {
                text,
                language: if language.is_empty() { None } else { Some(language) },
            },
            NodeContent::Quote => kreuzberg::NodeContent::Quote,
            NodeContent::Formula { text } => kreuzberg::NodeContent::Formula { text },
            NodeContent::Footnote { text } => kreuzberg::NodeContent::Footnote { text },
            NodeContent::Group {
                label,
                heading_level,
                heading_text,
            } => kreuzberg::NodeContent::Group {
                label: if label.is_empty() { None } else { Some(label) },
                heading_level: if heading_level == 0 {
                    None
                } else {
                    Some(heading_level as _)
                },
                heading_text: if heading_text.is_empty() {
                    None
                } else {
                    Some(heading_text)
                },
            },
            NodeContent::PageBreak => kreuzberg::NodeContent::PageBreak,
            NodeContent::Slide { number, title } => kreuzberg::NodeContent::Slide {
                number: number as _,
                title: if title.is_empty() { None } else { Some(title) },
            },
            NodeContent::DefinitionList => kreuzberg::NodeContent::DefinitionList,
            NodeContent::DefinitionItem { term, definition } => {
                kreuzberg::NodeContent::DefinitionItem { term, definition }
            }
            NodeContent::Citation { key, text } => kreuzberg::NodeContent::Citation { key, text },
            NodeContent::Admonition { kind, title } => kreuzberg::NodeContent::Admonition {
                kind,
                title: if title.is_empty() { None } else { Some(title) },
            },
            NodeContent::RawBlock { format, content } => kreuzberg::NodeContent::RawBlock { format, content },
            NodeContent::MetadataBlock { entries } => kreuzberg::NodeContent::MetadataBlock {
                entries: Default::default(),
            },
        }
    }
}

impl From<AnnotationKind> for kreuzberg::AnnotationKind {
    fn from(v: AnnotationKind) -> Self {
        match v {
            AnnotationKind::Bold => kreuzberg::AnnotationKind::Bold,
            AnnotationKind::Italic => kreuzberg::AnnotationKind::Italic,
            AnnotationKind::Underline => kreuzberg::AnnotationKind::Underline,
            AnnotationKind::Strikethrough => kreuzberg::AnnotationKind::Strikethrough,
            AnnotationKind::Code => kreuzberg::AnnotationKind::Code,
            AnnotationKind::Subscript => kreuzberg::AnnotationKind::Subscript,
            AnnotationKind::Superscript => kreuzberg::AnnotationKind::Superscript,
            AnnotationKind::Link { url, title } => kreuzberg::AnnotationKind::Link {
                url,
                title: if title.is_empty() { None } else { Some(title) },
            },
            AnnotationKind::Highlight => kreuzberg::AnnotationKind::Highlight,
            AnnotationKind::Color { value } => kreuzberg::AnnotationKind::Color { value },
            AnnotationKind::FontSize { value } => kreuzberg::AnnotationKind::FontSize { value },
            AnnotationKind::Custom { name, value } => kreuzberg::AnnotationKind::Custom {
                name,
                value: if value.is_empty() { None } else { Some(value) },
            },
        }
    }
}

impl From<ExtractionMethod> for kreuzberg::ExtractionMethod {
    fn from(v: ExtractionMethod) -> Self {
        match v {
            ExtractionMethod::Native => kreuzberg::ExtractionMethod::Native,
            ExtractionMethod::Ocr => kreuzberg::ExtractionMethod::Ocr,
            ExtractionMethod::Mixed => kreuzberg::ExtractionMethod::Mixed,
        }
    }
}

impl From<ChunkType> for kreuzberg::ChunkType {
    fn from(v: ChunkType) -> Self {
        match v {
            ChunkType::Heading => kreuzberg::ChunkType::Heading,
            ChunkType::PartyList => kreuzberg::ChunkType::PartyList,
            ChunkType::Definitions => kreuzberg::ChunkType::Definitions,
            ChunkType::OperativeClause => kreuzberg::ChunkType::OperativeClause,
            ChunkType::SignatureBlock => kreuzberg::ChunkType::SignatureBlock,
            ChunkType::Schedule => kreuzberg::ChunkType::Schedule,
            ChunkType::TableLike => kreuzberg::ChunkType::TableLike,
            ChunkType::Formula => kreuzberg::ChunkType::Formula,
            ChunkType::CodeBlock => kreuzberg::ChunkType::CodeBlock,
            ChunkType::Image => kreuzberg::ChunkType::Image,
            ChunkType::OrgChart => kreuzberg::ChunkType::OrgChart,
            ChunkType::Diagram => kreuzberg::ChunkType::Diagram,
            ChunkType::Unknown => kreuzberg::ChunkType::Unknown,
        }
    }
}

impl From<ImageKind> for kreuzberg::ImageKind {
    fn from(v: ImageKind) -> Self {
        match v {
            ImageKind::Photograph => kreuzberg::ImageKind::Photograph,
            ImageKind::Diagram => kreuzberg::ImageKind::Diagram,
            ImageKind::Chart => kreuzberg::ImageKind::Chart,
            ImageKind::Drawing => kreuzberg::ImageKind::Drawing,
            ImageKind::TextBlock => kreuzberg::ImageKind::TextBlock,
            ImageKind::Decoration => kreuzberg::ImageKind::Decoration,
            ImageKind::Logo => kreuzberg::ImageKind::Logo,
            ImageKind::Icon => kreuzberg::ImageKind::Icon,
            ImageKind::TileFragment => kreuzberg::ImageKind::TileFragment,
            ImageKind::Mask => kreuzberg::ImageKind::Mask,
            ImageKind::PageRaster => kreuzberg::ImageKind::PageRaster,
            ImageKind::Unknown => kreuzberg::ImageKind::Unknown,
        }
    }
}

impl From<ResultFormat> for kreuzberg::ResultFormat {
    fn from(v: ResultFormat) -> Self {
        match v {
            ResultFormat::Unified => kreuzberg::ResultFormat::Unified,
            ResultFormat::ElementBased => kreuzberg::ResultFormat::ElementBased,
        }
    }
}

impl From<ElementType> for kreuzberg::ElementType {
    fn from(v: ElementType) -> Self {
        match v {
            ElementType::Title => kreuzberg::ElementType::Title,
            ElementType::NarrativeText => kreuzberg::ElementType::NarrativeText,
            ElementType::Heading => kreuzberg::ElementType::Heading,
            ElementType::ListItem => kreuzberg::ElementType::ListItem,
            ElementType::Table => kreuzberg::ElementType::Table,
            ElementType::Image => kreuzberg::ElementType::Image,
            ElementType::PageBreak => kreuzberg::ElementType::PageBreak,
            ElementType::CodeBlock => kreuzberg::ElementType::CodeBlock,
            ElementType::BlockQuote => kreuzberg::ElementType::BlockQuote,
            ElementType::Footer => kreuzberg::ElementType::Footer,
            ElementType::Header => kreuzberg::ElementType::Header,
        }
    }
}

impl From<FormatMetadata> for kreuzberg::FormatMetadata {
    fn from(v: FormatMetadata) -> Self {
        match v {
            FormatMetadata::Pdf { field0 } => kreuzberg::FormatMetadata::Pdf(field0.into()),
            FormatMetadata::Docx { field0 } => kreuzberg::FormatMetadata::Docx(Box::new(field0.into())),
            FormatMetadata::Excel { field0 } => kreuzberg::FormatMetadata::Excel(field0.into()),
            FormatMetadata::Email { field0 } => kreuzberg::FormatMetadata::Email(field0.into()),
            FormatMetadata::Pptx { field0 } => kreuzberg::FormatMetadata::Pptx(field0.into()),
            FormatMetadata::Archive { field0 } => kreuzberg::FormatMetadata::Archive(field0.into()),
            FormatMetadata::Image { field0 } => kreuzberg::FormatMetadata::Image(field0.into()),
            FormatMetadata::Xml { field0 } => kreuzberg::FormatMetadata::Xml(field0.into()),
            FormatMetadata::Text { field0 } => kreuzberg::FormatMetadata::Text(field0.into()),
            FormatMetadata::Html { field0 } => kreuzberg::FormatMetadata::Html(Box::new(field0.into())),
            FormatMetadata::Ocr { field0 } => kreuzberg::FormatMetadata::Ocr(field0.into()),
            FormatMetadata::Csv { field0 } => kreuzberg::FormatMetadata::Csv(field0.into()),
            FormatMetadata::Bibtex { field0 } => kreuzberg::FormatMetadata::Bibtex(field0.into()),
            FormatMetadata::Citation { field0 } => kreuzberg::FormatMetadata::Citation(field0.into()),
            FormatMetadata::FictionBook { field0 } => kreuzberg::FormatMetadata::FictionBook(field0.into()),
            FormatMetadata::Dbf { field0 } => kreuzberg::FormatMetadata::Dbf(field0.into()),
            FormatMetadata::Jats { field0 } => kreuzberg::FormatMetadata::Jats(field0.into()),
            FormatMetadata::Epub { field0 } => kreuzberg::FormatMetadata::Epub(field0.into()),
            FormatMetadata::Pst { field0 } => kreuzberg::FormatMetadata::Pst(field0.into()),
            FormatMetadata::Code { field0 } => kreuzberg::FormatMetadata::Code(Default::default()),
        }
    }
}

impl From<TextDirection> for kreuzberg::TextDirection {
    fn from(v: TextDirection) -> Self {
        match v {
            TextDirection::LeftToRight => kreuzberg::TextDirection::LeftToRight,
            TextDirection::RightToLeft => kreuzberg::TextDirection::RightToLeft,
            TextDirection::Auto => kreuzberg::TextDirection::Auto,
        }
    }
}

impl From<LinkType> for kreuzberg::LinkType {
    fn from(v: LinkType) -> Self {
        match v {
            LinkType::Anchor => kreuzberg::LinkType::Anchor,
            LinkType::Internal => kreuzberg::LinkType::Internal,
            LinkType::External => kreuzberg::LinkType::External,
            LinkType::Email => kreuzberg::LinkType::Email,
            LinkType::Phone => kreuzberg::LinkType::Phone,
            LinkType::Other => kreuzberg::LinkType::Other,
        }
    }
}

impl From<ImageType> for kreuzberg::ImageType {
    fn from(v: ImageType) -> Self {
        match v {
            ImageType::DataUri => kreuzberg::ImageType::DataUri,
            ImageType::InlineSvg => kreuzberg::ImageType::InlineSvg,
            ImageType::External => kreuzberg::ImageType::External,
            ImageType::Relative => kreuzberg::ImageType::Relative,
        }
    }
}

impl From<StructuredDataType> for kreuzberg::StructuredDataType {
    fn from(v: StructuredDataType) -> Self {
        match v {
            StructuredDataType::JsonLd => kreuzberg::StructuredDataType::JsonLd,
            StructuredDataType::Microdata => kreuzberg::StructuredDataType::Microdata,
            StructuredDataType::RDFa => kreuzberg::StructuredDataType::RDFa,
        }
    }
}

impl From<OcrBoundingGeometry> for kreuzberg::OcrBoundingGeometry {
    fn from(v: OcrBoundingGeometry) -> Self {
        match v {
            OcrBoundingGeometry::Rectangle {
                left,
                top,
                width,
                height,
            } => kreuzberg::OcrBoundingGeometry::Rectangle {
                left: left as _,
                top: top as _,
                width: width as _,
                height: height as _,
            },
            OcrBoundingGeometry::Quadrilateral { points } => kreuzberg::OcrBoundingGeometry::Quadrilateral {
                points: Default::default(),
            },
        }
    }
}

impl From<OcrElementLevel> for kreuzberg::OcrElementLevel {
    fn from(v: OcrElementLevel) -> Self {
        match v {
            OcrElementLevel::Word => kreuzberg::OcrElementLevel::Word,
            OcrElementLevel::Line => kreuzberg::OcrElementLevel::Line,
            OcrElementLevel::Block => kreuzberg::OcrElementLevel::Block,
            OcrElementLevel::Page => kreuzberg::OcrElementLevel::Page,
        }
    }
}

impl From<PageUnitType> for kreuzberg::PageUnitType {
    fn from(v: PageUnitType) -> Self {
        match v {
            PageUnitType::Page => kreuzberg::PageUnitType::Page,
            PageUnitType::Slide => kreuzberg::PageUnitType::Slide,
            PageUnitType::Sheet => kreuzberg::PageUnitType::Sheet,
        }
    }
}

impl From<DiffLine> for kreuzberg::DiffLine {
    fn from(v: DiffLine) -> Self {
        match v {
            DiffLine::Context { field0 } => kreuzberg::DiffLine::Context(field0),
            DiffLine::Added { field0 } => kreuzberg::DiffLine::Added(field0),
            DiffLine::Removed { field0 } => kreuzberg::DiffLine::Removed(field0),
        }
    }
}

impl From<RevisionKind> for kreuzberg::RevisionKind {
    fn from(v: RevisionKind) -> Self {
        match v {
            RevisionKind::Insertion => kreuzberg::RevisionKind::Insertion,
            RevisionKind::Deletion => kreuzberg::RevisionKind::Deletion,
            RevisionKind::FormatChange => kreuzberg::RevisionKind::FormatChange,
            RevisionKind::Comment => kreuzberg::RevisionKind::Comment,
        }
    }
}

impl From<RevisionAnchor> for kreuzberg::RevisionAnchor {
    fn from(v: RevisionAnchor) -> Self {
        match v {
            RevisionAnchor::Paragraph { index } => kreuzberg::RevisionAnchor::Paragraph { index: index as _ },
            RevisionAnchor::TableCell { row, col, table_index } => kreuzberg::RevisionAnchor::TableCell {
                row: row as _,
                col: col as _,
                table_index: table_index as _,
            },
            RevisionAnchor::Page { index } => kreuzberg::RevisionAnchor::Page { index: index as _ },
            RevisionAnchor::Slide { index } => kreuzberg::RevisionAnchor::Slide { index: index as _ },
            RevisionAnchor::Sheet { index, name } => kreuzberg::RevisionAnchor::Sheet {
                index: index as _,
                name: if name.is_empty() { None } else { Some(name) },
            },
        }
    }
}

impl From<UriKind> for kreuzberg::UriKind {
    fn from(v: UriKind) -> Self {
        match v {
            UriKind::Hyperlink => kreuzberg::UriKind::Hyperlink,
            UriKind::Image => kreuzberg::UriKind::Image,
            UriKind::Anchor => kreuzberg::UriKind::Anchor,
            UriKind::Citation => kreuzberg::UriKind::Citation,
            UriKind::Reference => kreuzberg::UriKind::Reference,
            UriKind::Email => kreuzberg::UriKind::Email,
        }
    }
}

impl From<KeywordAlgorithm> for kreuzberg::KeywordAlgorithm {
    fn from(v: KeywordAlgorithm) -> Self {
        match v {
            KeywordAlgorithm::Yake => kreuzberg::KeywordAlgorithm::Yake,
            KeywordAlgorithm::Rake => kreuzberg::KeywordAlgorithm::Rake,
        }
    }
}

/// 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.
pub async fn extract_bytes(
    content: Vec<u8>,
    mime_type: String,
    config: ExtractionConfig,
) -> Result<ExtractionResult, String> {
    kreuzberg::extract_bytes(&content, &mime_type, &kreuzberg::ExtractionConfig::from(config))
        .await
        .map(ExtractionResult::from)
        .map_err(|e| e.to_string())
}

/// 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.
pub async fn extract_file(
    path: String,
    mime_type: Option<String>,
    config: ExtractionConfig,
) -> Result<ExtractionResult, String> {
    kreuzberg::extract_file(
        std::path::PathBuf::from(path),
        mime_type.as_deref(),
        &kreuzberg::ExtractionConfig::from(config),
    )
    .await
    .map(ExtractionResult::from)
    .map_err(|e| e.to_string())
}

/// 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.
pub fn extract_file_sync(
    path: String,
    mime_type: Option<String>,
    config: ExtractionConfig,
) -> Result<ExtractionResult, String> {
    kreuzberg::extract_file_sync(
        std::path::PathBuf::from(path),
        mime_type.as_deref(),
        &kreuzberg::ExtractionConfig::from(config),
    )
    .map(ExtractionResult::from)
    .map_err(|e| e.to_string())
}

/// 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.
pub fn extract_bytes_sync(
    content: Vec<u8>,
    mime_type: String,
    config: ExtractionConfig,
) -> Result<ExtractionResult, String> {
    kreuzberg::extract_bytes_sync(&content, &mime_type, &kreuzberg::ExtractionConfig::from(config))
        .map(ExtractionResult::from)
        .map_err(|e| e.to_string())
}

/// Synchronous wrapper for `batch_extract_files`.
///
/// Uses the global Tokio runtime for optimal performance.
/// Only available with `tokio-runtime` (WASM has no filesystem).
pub fn batch_extract_files_sync(
    items: Vec<BatchFileItem>,
    config: ExtractionConfig,
) -> Result<Vec<ExtractionResult>, String> {
    kreuzberg::batch_extract_files_sync(
        items
            .into_iter()
            .map(kreuzberg::BatchFileItem::from)
            .collect::<Vec<_>>(),
        &kreuzberg::ExtractionConfig::from(config),
    )
    .map(|v| v.into_iter().map(ExtractionResult::from).collect())
    .map_err(|e| e.to_string())
}

/// 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()`.
pub fn batch_extract_bytes_sync(
    items: Vec<BatchBytesItem>,
    config: ExtractionConfig,
) -> Result<Vec<ExtractionResult>, String> {
    ::std::unimplemented!("this method is listed in dart.stub_methods and cannot be bridged through FRB")
}

/// 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:
pub async fn batch_extract_files(
    items: Vec<BatchFileItem>,
    config: ExtractionConfig,
) -> Result<Vec<ExtractionResult>, String> {
    kreuzberg::batch_extract_files(
        items
            .into_iter()
            .map(kreuzberg::BatchFileItem::from)
            .collect::<Vec<_>>(),
        &kreuzberg::ExtractionConfig::from(config),
    )
    .await
    .map(|v| v.into_iter().map(ExtractionResult::from).collect())
    .map_err(|e| e.to_string())
}

/// 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:
pub async fn batch_extract_bytes(
    items: Vec<BatchBytesItem>,
    config: ExtractionConfig,
) -> Result<Vec<ExtractionResult>, String> {
    ::std::unimplemented!("this method is listed in dart.stub_methods and cannot be bridged through FRB")
}

/// 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.
pub fn detect_mime_type_from_bytes(content: Vec<u8>) -> Result<String, String> {
    kreuzberg::detect_mime_type_from_bytes(&content)
        .map(|v| v.to_string())
        .map_err(|e| e.to_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.
pub fn get_extensions_for_mime(mime_type: String) -> Result<Vec<String>, String> {
    kreuzberg::get_extensions_for_mime(&mime_type)
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_string())
}

/// List the names of all registered embedding backends.
///
/// Used by `kreuzberg-cli`, the api/mcp endpoints, and generated language
/// bindings.
pub fn list_embedding_backends() -> Result<Vec<String>, String> {
    kreuzberg::list_embedding_backends()
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_string())
}

/// List names of all registered document extractors.
pub fn list_document_extractors() -> Result<Vec<String>, String> {
    kreuzberg::list_document_extractors()
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_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.
pub fn list_ocr_backends() -> Result<Vec<String>, String> {
    kreuzberg::list_ocr_backends()
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_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
pub fn list_post_processors() -> Result<Vec<String>, String> {
    kreuzberg::list_post_processors()
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_string())
}

/// List names of all registered renderers.
///
/// **Errors:**
///
/// Returns an error if the registry lock is poisoned.
pub fn list_renderers() -> Result<Vec<String>, String> {
    kreuzberg::list_renderers()
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_string())
}

/// List names of all registered validators.
pub fn list_validators() -> Result<Vec<String>, String> {
    kreuzberg::list_validators()
        .map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>())
        .map_err(|e| e.to_string())
}

/// 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`.
pub fn compare(a: ExtractionResult, b: ExtractionResult, opts: DiffOptions) -> ExtractionDiff {
    (ExtractionDiff::from)(kreuzberg::compare(
        &kreuzberg::ExtractionResult::from(a),
        &kreuzberg::ExtractionResult::from(b),
        &kreuzberg::DiffOptions::from(opts),
    ))
}

/// 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.
///
/// **Errors:**
///
/// - `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
pub async fn embed_texts_async(texts: Vec<String>, config: EmbeddingConfig) -> Result<Vec<Vec<f64>>, String> {
    kreuzberg::embed_texts_async(texts, &kreuzberg::EmbeddingConfig::from(config))
        .await
        .map(|v| {
            v.into_iter()
                .map(|row| row.into_iter().map(|x| x as f64).collect::<Vec<_>>())
                .collect::<Vec<_>>()
        })
        .map_err(|e| e.to_string())
}

/// 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.
pub fn render_pdf_page_to_png(
    pdf_bytes: Vec<u8>,
    page_index: i64,
    dpi: Option<i64>,
    password: Option<String>,
) -> Result<Vec<u8>, String> {
    kreuzberg::render_pdf_page_to_png(
        &pdf_bytes,
        page_index as usize,
        dpi.map(|v| v as i32),
        password.as_deref(),
    )
    .map(|v| v)
    .map_err(|e| e.to_string())
}

/// 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.
pub fn detect_mime_type(path: String, check_exists: bool) -> Result<String, String> {
    kreuzberg::detect_mime_type(path, check_exists)
        .map(|v| v.to_string())
        .map_err(|e| e.to_string())
}

/// 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.
pub fn embed_texts(texts: Vec<String>, config: EmbeddingConfig) -> Result<Vec<Vec<f64>>, String> {
    kreuzberg::embed_texts(texts, &kreuzberg::EmbeddingConfig::from(config))
        .map(|v| {
            v.into_iter()
                .map(|row| row.into_iter().map(|x| x as f64).collect::<Vec<_>>())
                .collect::<Vec<_>>()
        })
        .map_err(|e| e.to_string())
}

/// 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.
pub fn get_embedding_preset(name: String) -> Option<EmbeddingPreset> {
    (|v: Option<_>| v.map(EmbeddingPreset::from))(kreuzberg::get_embedding_preset(&name))
}

/// List the names of all available embedding presets.
///
/// Returns owned `String`s so the values are safe to pass across FFI boundaries.
pub fn list_embedding_presets() -> Vec<String> {
    kreuzberg::list_embedding_presets()
        .into_iter()
        .map(|s| s.to_string())
        .collect::<Vec<_>>()
}

// `create_<Type>_from_json` helpers — deserialize a JSON string into a mirror type.

#[frb]
pub fn create_cache_stats_from_json(json: String) -> Result<CacheStats, String> {
    serde_json::from_str::<kreuzberg::CacheStats>(&json)
        .map(CacheStats::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_acceleration_config_from_json(json: String) -> Result<AccelerationConfig, String> {
    serde_json::from_str::<kreuzberg::AccelerationConfig>(&json)
        .map(AccelerationConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_content_filter_config_from_json(json: String) -> Result<ContentFilterConfig, String> {
    serde_json::from_str::<kreuzberg::ContentFilterConfig>(&json)
        .map(ContentFilterConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_email_config_from_json(json: String) -> Result<EmailConfig, String> {
    serde_json::from_str::<kreuzberg::EmailConfig>(&json)
        .map(EmailConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_extraction_config_from_json(json: String) -> Result<ExtractionConfig, String> {
    serde_json::from_str::<kreuzberg::ExtractionConfig>(&json)
        .map(ExtractionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_file_extraction_config_from_json(json: String) -> Result<FileExtractionConfig, String> {
    serde_json::from_str::<kreuzberg::FileExtractionConfig>(&json)
        .map(FileExtractionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_batch_bytes_item_from_json(json: String) -> Result<BatchBytesItem, String> {
    serde_json::from_str::<kreuzberg::BatchBytesItem>(&json)
        .map(BatchBytesItem::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_batch_file_item_from_json(json: String) -> Result<BatchFileItem, String> {
    serde_json::from_str::<kreuzberg::BatchFileItem>(&json)
        .map(BatchFileItem::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_image_extraction_config_from_json(json: String) -> Result<ImageExtractionConfig, String> {
    serde_json::from_str::<kreuzberg::ImageExtractionConfig>(&json)
        .map(ImageExtractionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_token_reduction_options_from_json(json: String) -> Result<TokenReductionOptions, String> {
    serde_json::from_str::<kreuzberg::TokenReductionOptions>(&json)
        .map(TokenReductionOptions::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_language_detection_config_from_json(json: String) -> Result<LanguageDetectionConfig, String> {
    serde_json::from_str::<kreuzberg::LanguageDetectionConfig>(&json)
        .map(LanguageDetectionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_html_output_config_from_json(json: String) -> Result<HtmlOutputConfig, String> {
    serde_json::from_str::<kreuzberg::HtmlOutputConfig>(&json)
        .map(HtmlOutputConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_layout_detection_config_from_json(json: String) -> Result<LayoutDetectionConfig, String> {
    serde_json::from_str::<kreuzberg::LayoutDetectionConfig>(&json)
        .map(LayoutDetectionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_llm_config_from_json(json: String) -> Result<LlmConfig, String> {
    serde_json::from_str::<kreuzberg::LlmConfig>(&json)
        .map(LlmConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_structured_extraction_config_from_json(json: String) -> Result<StructuredExtractionConfig, String> {
    serde_json::from_str::<kreuzberg::StructuredExtractionConfig>(&json)
        .map(StructuredExtractionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_quality_thresholds_from_json(json: String) -> Result<OcrQualityThresholds, String> {
    serde_json::from_str::<kreuzberg::OcrQualityThresholds>(&json)
        .map(OcrQualityThresholds::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_pipeline_stage_from_json(json: String) -> Result<OcrPipelineStage, String> {
    serde_json::from_str::<kreuzberg::OcrPipelineStage>(&json)
        .map(OcrPipelineStage::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_pipeline_config_from_json(json: String) -> Result<OcrPipelineConfig, String> {
    serde_json::from_str::<kreuzberg::OcrPipelineConfig>(&json)
        .map(OcrPipelineConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_config_from_json(json: String) -> Result<OcrConfig, String> {
    serde_json::from_str::<kreuzberg::OcrConfig>(&json)
        .map(OcrConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_page_config_from_json(json: String) -> Result<PageConfig, String> {
    serde_json::from_str::<kreuzberg::PageConfig>(&json)
        .map(PageConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pdf_config_from_json(json: String) -> Result<PdfConfig, String> {
    serde_json::from_str::<kreuzberg::PdfConfig>(&json)
        .map(PdfConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_hierarchy_config_from_json(json: String) -> Result<HierarchyConfig, String> {
    serde_json::from_str::<kreuzberg::HierarchyConfig>(&json)
        .map(HierarchyConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_post_processor_config_from_json(json: String) -> Result<PostProcessorConfig, String> {
    serde_json::from_str::<kreuzberg::PostProcessorConfig>(&json)
        .map(PostProcessorConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_chunking_config_from_json(json: String) -> Result<ChunkingConfig, String> {
    serde_json::from_str::<kreuzberg::ChunkingConfig>(&json)
        .map(ChunkingConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_embedding_config_from_json(json: String) -> Result<EmbeddingConfig, String> {
    serde_json::from_str::<kreuzberg::EmbeddingConfig>(&json)
        .map(EmbeddingConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_tree_sitter_config_from_json(json: String) -> Result<TreeSitterConfig, String> {
    serde_json::from_str::<kreuzberg::TreeSitterConfig>(&json)
        .map(TreeSitterConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_tree_sitter_process_config_from_json(json: String) -> Result<TreeSitterProcessConfig, String> {
    serde_json::from_str::<kreuzberg::TreeSitterProcessConfig>(&json)
        .map(TreeSitterProcessConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_supported_format_from_json(json: String) -> Result<SupportedFormat, String> {
    serde_json::from_str::<kreuzberg::SupportedFormat>(&json)
        .map(SupportedFormat::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_server_config_from_json(json: String) -> Result<ServerConfig, String> {
    serde_json::from_str::<kreuzberg::ServerConfig>(&json)
        .map(ServerConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_structured_data_result_from_json(json: String) -> Result<StructuredDataResult, String> {
    serde_json::from_str::<kreuzberg::extraction::structured::StructuredDataResult>(&json)
        .map(StructuredDataResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_docx_app_properties_from_json(json: String) -> Result<DocxAppProperties, String> {
    serde_json::from_str::<kreuzberg::DocxAppProperties>(&json)
        .map(DocxAppProperties::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_xlsx_app_properties_from_json(json: String) -> Result<XlsxAppProperties, String> {
    serde_json::from_str::<kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties>(&json)
        .map(XlsxAppProperties::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pptx_app_properties_from_json(json: String) -> Result<PptxAppProperties, String> {
    serde_json::from_str::<kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties>(&json)
        .map(PptxAppProperties::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_core_properties_from_json(json: String) -> Result<CoreProperties, String> {
    serde_json::from_str::<kreuzberg::CoreProperties>(&json)
        .map(CoreProperties::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_security_limits_from_json(json: String) -> Result<SecurityLimits, String> {
    serde_json::from_str::<kreuzberg::SecurityLimits>(&json)
        .map(SecurityLimits::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_token_reduction_config_from_json(json: String) -> Result<TokenReductionConfig, String> {
    serde_json::from_str::<kreuzberg::TokenReductionConfig>(&json)
        .map(TokenReductionConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pdf_annotation_from_json(json: String) -> Result<PdfAnnotation, String> {
    serde_json::from_str::<kreuzberg::PdfAnnotation>(&json)
        .map(PdfAnnotation::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_djot_content_from_json(json: String) -> Result<DjotContent, String> {
    serde_json::from_str::<kreuzberg::DjotContent>(&json)
        .map(DjotContent::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_formatted_block_from_json(json: String) -> Result<FormattedBlock, String> {
    serde_json::from_str::<kreuzberg::FormattedBlock>(&json)
        .map(FormattedBlock::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_inline_element_from_json(json: String) -> Result<InlineElement, String> {
    serde_json::from_str::<kreuzberg::InlineElement>(&json)
        .map(InlineElement::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_djot_image_from_json(json: String) -> Result<DjotImage, String> {
    serde_json::from_str::<kreuzberg::DjotImage>(&json)
        .map(DjotImage::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_djot_link_from_json(json: String) -> Result<DjotLink, String> {
    serde_json::from_str::<kreuzberg::DjotLink>(&json)
        .map(DjotLink::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_footnote_from_json(json: String) -> Result<Footnote, String> {
    serde_json::from_str::<kreuzberg::Footnote>(&json)
        .map(Footnote::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_document_structure_from_json(json: String) -> Result<DocumentStructure, String> {
    serde_json::from_str::<kreuzberg::DocumentStructure>(&json)
        .map(DocumentStructure::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_document_relationship_from_json(json: String) -> Result<DocumentRelationship, String> {
    serde_json::from_str::<kreuzberg::DocumentRelationship>(&json)
        .map(DocumentRelationship::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_document_node_from_json(json: String) -> Result<DocumentNode, String> {
    serde_json::from_str::<kreuzberg::DocumentNode>(&json)
        .map(DocumentNode::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_table_grid_from_json(json: String) -> Result<TableGrid, String> {
    serde_json::from_str::<kreuzberg::TableGrid>(&json)
        .map(TableGrid::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_grid_cell_from_json(json: String) -> Result<GridCell, String> {
    serde_json::from_str::<kreuzberg::GridCell>(&json)
        .map(GridCell::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_text_annotation_from_json(json: String) -> Result<TextAnnotation, String> {
    serde_json::from_str::<kreuzberg::TextAnnotation>(&json)
        .map(TextAnnotation::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_extraction_result_from_json(json: String) -> Result<ExtractionResult, String> {
    serde_json::from_str::<kreuzberg::ExtractionResult>(&json)
        .map(ExtractionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_archive_entry_from_json(json: String) -> Result<ArchiveEntry, String> {
    serde_json::from_str::<kreuzberg::ArchiveEntry>(&json)
        .map(ArchiveEntry::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_processing_warning_from_json(json: String) -> Result<ProcessingWarning, String> {
    serde_json::from_str::<kreuzberg::ProcessingWarning>(&json)
        .map(ProcessingWarning::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_llm_usage_from_json(json: String) -> Result<LlmUsage, String> {
    serde_json::from_str::<kreuzberg::LlmUsage>(&json)
        .map(LlmUsage::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_chunk_from_json(json: String) -> Result<Chunk, String> {
    serde_json::from_str::<kreuzberg::Chunk>(&json)
        .map(Chunk::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_heading_context_from_json(json: String) -> Result<HeadingContext, String> {
    serde_json::from_str::<kreuzberg::HeadingContext>(&json)
        .map(HeadingContext::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_heading_level_from_json(json: String) -> Result<HeadingLevel, String> {
    serde_json::from_str::<kreuzberg::HeadingLevel>(&json)
        .map(HeadingLevel::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_chunk_metadata_from_json(json: String) -> Result<ChunkMetadata, String> {
    serde_json::from_str::<kreuzberg::ChunkMetadata>(&json)
        .map(ChunkMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_extracted_image_from_json(json: String) -> Result<ExtractedImage, String> {
    serde_json::from_str::<kreuzberg::ExtractedImage>(&json)
        .map(ExtractedImage::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_bounding_box_from_json(json: String) -> Result<BoundingBox, String> {
    serde_json::from_str::<kreuzberg::BoundingBox>(&json)
        .map(BoundingBox::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_element_metadata_from_json(json: String) -> Result<ElementMetadata, String> {
    serde_json::from_str::<kreuzberg::ElementMetadata>(&json)
        .map(ElementMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_element_from_json(json: String) -> Result<Element, String> {
    serde_json::from_str::<kreuzberg::Element>(&json)
        .map(Element::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_excel_workbook_from_json(json: String) -> Result<ExcelWorkbook, String> {
    serde_json::from_str::<kreuzberg::ExcelWorkbook>(&json)
        .map(ExcelWorkbook::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_excel_sheet_from_json(json: String) -> Result<ExcelSheet, String> {
    serde_json::from_str::<kreuzberg::ExcelSheet>(&json)
        .map(ExcelSheet::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_xml_extraction_result_from_json(json: String) -> Result<XmlExtractionResult, String> {
    serde_json::from_str::<kreuzberg::XmlExtractionResult>(&json)
        .map(XmlExtractionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_text_extraction_result_from_json(json: String) -> Result<TextExtractionResult, String> {
    serde_json::from_str::<kreuzberg::TextExtractionResult>(&json)
        .map(TextExtractionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pptx_extraction_result_from_json(json: String) -> Result<PptxExtractionResult, String> {
    serde_json::from_str::<kreuzberg::PptxExtractionResult>(&json)
        .map(PptxExtractionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_email_extraction_result_from_json(json: String) -> Result<EmailExtractionResult, String> {
    serde_json::from_str::<kreuzberg::EmailExtractionResult>(&json)
        .map(EmailExtractionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_email_attachment_from_json(json: String) -> Result<EmailAttachment, String> {
    serde_json::from_str::<kreuzberg::EmailAttachment>(&json)
        .map(EmailAttachment::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_extraction_result_from_json(json: String) -> Result<OcrExtractionResult, String> {
    serde_json::from_str::<kreuzberg::OcrExtractionResult>(&json)
        .map(OcrExtractionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_table_from_json(json: String) -> Result<OcrTable, String> {
    serde_json::from_str::<kreuzberg::OcrTable>(&json)
        .map(OcrTable::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_table_bounding_box_from_json(json: String) -> Result<OcrTableBoundingBox, String> {
    serde_json::from_str::<kreuzberg::OcrTableBoundingBox>(&json)
        .map(OcrTableBoundingBox::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_image_preprocessing_config_from_json(json: String) -> Result<ImagePreprocessingConfig, String> {
    serde_json::from_str::<kreuzberg::ImagePreprocessingConfig>(&json)
        .map(ImagePreprocessingConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_tesseract_config_from_json(json: String) -> Result<TesseractConfig, String> {
    serde_json::from_str::<kreuzberg::TesseractConfig>(&json)
        .map(TesseractConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_image_preprocessing_metadata_from_json(json: String) -> Result<ImagePreprocessingMetadata, String> {
    serde_json::from_str::<kreuzberg::ImagePreprocessingMetadata>(&json)
        .map(ImagePreprocessingMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_metadata_from_json(json: String) -> Result<Metadata, String> {
    serde_json::from_str::<kreuzberg::Metadata>(&json)
        .map(Metadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_excel_metadata_from_json(json: String) -> Result<ExcelMetadata, String> {
    serde_json::from_str::<kreuzberg::ExcelMetadata>(&json)
        .map(ExcelMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_email_metadata_from_json(json: String) -> Result<EmailMetadata, String> {
    serde_json::from_str::<kreuzberg::EmailMetadata>(&json)
        .map(EmailMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_archive_metadata_from_json(json: String) -> Result<ArchiveMetadata, String> {
    serde_json::from_str::<kreuzberg::ArchiveMetadata>(&json)
        .map(ArchiveMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_image_metadata_from_json(json: String) -> Result<ImageMetadata, String> {
    serde_json::from_str::<kreuzberg::ImageMetadata>(&json)
        .map(ImageMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_xml_metadata_from_json(json: String) -> Result<XmlMetadata, String> {
    serde_json::from_str::<kreuzberg::XmlMetadata>(&json)
        .map(XmlMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_text_metadata_from_json(json: String) -> Result<TextMetadata, String> {
    serde_json::from_str::<kreuzberg::TextMetadata>(&json)
        .map(TextMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_header_metadata_from_json(json: String) -> Result<HeaderMetadata, String> {
    serde_json::from_str::<kreuzberg::HeaderMetadata>(&json)
        .map(HeaderMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_link_metadata_from_json(json: String) -> Result<LinkMetadata, String> {
    serde_json::from_str::<kreuzberg::LinkMetadata>(&json)
        .map(LinkMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_image_metadata_type_from_json(json: String) -> Result<ImageMetadataType, String> {
    serde_json::from_str::<kreuzberg::ImageMetadataType>(&json)
        .map(ImageMetadataType::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_structured_data_from_json(json: String) -> Result<StructuredData, String> {
    serde_json::from_str::<kreuzberg::StructuredData>(&json)
        .map(StructuredData::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_html_metadata_from_json(json: String) -> Result<HtmlMetadata, String> {
    serde_json::from_str::<kreuzberg::HtmlMetadata>(&json)
        .map(HtmlMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_metadata_from_json(json: String) -> Result<OcrMetadata, String> {
    serde_json::from_str::<kreuzberg::OcrMetadata>(&json)
        .map(OcrMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_error_metadata_from_json(json: String) -> Result<ErrorMetadata, String> {
    serde_json::from_str::<kreuzberg::ErrorMetadata>(&json)
        .map(ErrorMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pptx_metadata_from_json(json: String) -> Result<PptxMetadata, String> {
    serde_json::from_str::<kreuzberg::PptxMetadata>(&json)
        .map(PptxMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_docx_metadata_from_json(json: String) -> Result<DocxMetadata, String> {
    serde_json::from_str::<kreuzberg::DocxMetadata>(&json)
        .map(DocxMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_csv_metadata_from_json(json: String) -> Result<CsvMetadata, String> {
    serde_json::from_str::<kreuzberg::CsvMetadata>(&json)
        .map(CsvMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_bibtex_metadata_from_json(json: String) -> Result<BibtexMetadata, String> {
    serde_json::from_str::<kreuzberg::BibtexMetadata>(&json)
        .map(BibtexMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_citation_metadata_from_json(json: String) -> Result<CitationMetadata, String> {
    serde_json::from_str::<kreuzberg::CitationMetadata>(&json)
        .map(CitationMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_year_range_from_json(json: String) -> Result<YearRange, String> {
    serde_json::from_str::<kreuzberg::YearRange>(&json)
        .map(YearRange::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_fiction_book_metadata_from_json(json: String) -> Result<FictionBookMetadata, String> {
    serde_json::from_str::<kreuzberg::FictionBookMetadata>(&json)
        .map(FictionBookMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_dbf_metadata_from_json(json: String) -> Result<DbfMetadata, String> {
    serde_json::from_str::<kreuzberg::DbfMetadata>(&json)
        .map(DbfMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_dbf_field_info_from_json(json: String) -> Result<DbfFieldInfo, String> {
    serde_json::from_str::<kreuzberg::DbfFieldInfo>(&json)
        .map(DbfFieldInfo::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_jats_metadata_from_json(json: String) -> Result<JatsMetadata, String> {
    serde_json::from_str::<kreuzberg::JatsMetadata>(&json)
        .map(JatsMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_contributor_role_from_json(json: String) -> Result<ContributorRole, String> {
    serde_json::from_str::<kreuzberg::ContributorRole>(&json)
        .map(ContributorRole::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_epub_metadata_from_json(json: String) -> Result<EpubMetadata, String> {
    serde_json::from_str::<kreuzberg::EpubMetadata>(&json)
        .map(EpubMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pst_metadata_from_json(json: String) -> Result<PstMetadata, String> {
    serde_json::from_str::<kreuzberg::PstMetadata>(&json)
        .map(PstMetadata::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_confidence_from_json(json: String) -> Result<OcrConfidence, String> {
    serde_json::from_str::<kreuzberg::OcrConfidence>(&json)
        .map(OcrConfidence::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_rotation_from_json(json: String) -> Result<OcrRotation, String> {
    serde_json::from_str::<kreuzberg::OcrRotation>(&json)
        .map(OcrRotation::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_element_from_json(json: String) -> Result<OcrElement, String> {
    serde_json::from_str::<kreuzberg::OcrElement>(&json)
        .map(OcrElement::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_ocr_element_config_from_json(json: String) -> Result<OcrElementConfig, String> {
    serde_json::from_str::<kreuzberg::OcrElementConfig>(&json)
        .map(OcrElementConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_page_structure_from_json(json: String) -> Result<PageStructure, String> {
    serde_json::from_str::<kreuzberg::PageStructure>(&json)
        .map(PageStructure::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_page_boundary_from_json(json: String) -> Result<PageBoundary, String> {
    serde_json::from_str::<kreuzberg::PageBoundary>(&json)
        .map(PageBoundary::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_page_info_from_json(json: String) -> Result<PageInfo, String> {
    serde_json::from_str::<kreuzberg::PageInfo>(&json)
        .map(PageInfo::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_page_content_from_json(json: String) -> Result<PageContent, String> {
    serde_json::from_str::<kreuzberg::PageContent>(&json)
        .map(PageContent::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_layout_region_from_json(json: String) -> Result<LayoutRegion, String> {
    serde_json::from_str::<kreuzberg::LayoutRegion>(&json)
        .map(LayoutRegion::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_page_hierarchy_from_json(json: String) -> Result<PageHierarchy, String> {
    serde_json::from_str::<kreuzberg::PageHierarchy>(&json)
        .map(PageHierarchy::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_hierarchical_block_from_json(json: String) -> Result<HierarchicalBlock, String> {
    serde_json::from_str::<kreuzberg::HierarchicalBlock>(&json)
        .map(HierarchicalBlock::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_cell_change_from_json(json: String) -> Result<CellChange, String> {
    serde_json::from_str::<kreuzberg::CellChange>(&json)
        .map(CellChange::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_document_revision_from_json(json: String) -> Result<DocumentRevision, String> {
    serde_json::from_str::<kreuzberg::DocumentRevision>(&json)
        .map(DocumentRevision::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_revision_delta_from_json(json: String) -> Result<RevisionDelta, String> {
    serde_json::from_str::<kreuzberg::RevisionDelta>(&json)
        .map(RevisionDelta::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_table_from_json(json: String) -> Result<Table, String> {
    serde_json::from_str::<kreuzberg::Table>(&json)
        .map(Table::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_table_cell_from_json(json: String) -> Result<TableCell, String> {
    serde_json::from_str::<kreuzberg::TableCell>(&json)
        .map(TableCell::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_extracted_uri_from_json(json: String) -> Result<ExtractedUri, String> {
    serde_json::from_str::<kreuzberg::ExtractedUri>(&json)
        .map(ExtractedUri::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_detect_response_from_json(json: String) -> Result<DetectResponse, String> {
    serde_json::from_str::<kreuzberg::api::DetectResponse>(&json)
        .map(DetectResponse::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_diff_options_from_json(json: String) -> Result<DiffOptions, String> {
    serde_json::from_str::<kreuzberg::DiffOptions>(&json)
        .map(DiffOptions::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_extraction_diff_from_json(json: String) -> Result<ExtractionDiff, String> {
    serde_json::from_str::<kreuzberg::ExtractionDiff>(&json)
        .map(ExtractionDiff::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_diff_hunk_from_json(json: String) -> Result<DiffHunk, String> {
    serde_json::from_str::<kreuzberg::DiffHunk>(&json)
        .map(DiffHunk::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_table_diff_from_json(json: String) -> Result<TableDiff, String> {
    serde_json::from_str::<kreuzberg::TableDiff>(&json)
        .map(TableDiff::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_embedded_changes_from_json(json: String) -> Result<EmbeddedChanges, String> {
    serde_json::from_str::<kreuzberg::EmbeddedChanges>(&json)
        .map(EmbeddedChanges::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_embedded_diff_from_json(json: String) -> Result<EmbeddedDiff, String> {
    serde_json::from_str::<kreuzberg::EmbeddedDiff>(&json)
        .map(EmbeddedDiff::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_embedding_preset_from_json(json: String) -> Result<EmbeddingPreset, String> {
    serde_json::from_str::<kreuzberg::EmbeddingPreset>(&json)
        .map(EmbeddingPreset::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_yake_params_from_json(json: String) -> Result<YakeParams, String> {
    serde_json::from_str::<kreuzberg::YakeParams>(&json)
        .map(YakeParams::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_rake_params_from_json(json: String) -> Result<RakeParams, String> {
    serde_json::from_str::<kreuzberg::RakeParams>(&json)
        .map(RakeParams::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_keyword_config_from_json(json: String) -> Result<KeywordConfig, String> {
    serde_json::from_str::<kreuzberg::KeywordConfig>(&json)
        .map(KeywordConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_keyword_from_json(json: String) -> Result<Keyword, String> {
    serde_json::from_str::<kreuzberg::Keyword>(&json)
        .map(Keyword::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_paddle_ocr_config_from_json(json: String) -> Result<PaddleOcrConfig, String> {
    serde_json::from_str::<kreuzberg::PaddleOcrConfig>(&json)
        .map(PaddleOcrConfig::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_model_paths_from_json(json: String) -> Result<ModelPaths, String> {
    serde_json::from_str::<kreuzberg::ModelPaths>(&json)
        .map(ModelPaths::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_orientation_result_from_json(json: String) -> Result<OrientationResult, String> {
    serde_json::from_str::<kreuzberg::OrientationResult>(&json)
        .map(OrientationResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_b_box_from_json(json: String) -> Result<BBox, String> {
    serde_json::from_str::<kreuzberg::BBox>(&json)
        .map(BBox::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_layout_detection_from_json(json: String) -> Result<LayoutDetection, String> {
    serde_json::from_str::<kreuzberg::LayoutDetection>(&json)
        .map(LayoutDetection::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_recognized_table_from_json(json: String) -> Result<RecognizedTable, String> {
    serde_json::from_str::<kreuzberg::RecognizedTable>(&json)
        .map(RecognizedTable::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_detection_result_from_json(json: String) -> Result<DetectionResult, String> {
    serde_json::from_str::<kreuzberg::DetectionResult>(&json)
        .map(DetectionResult::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_embedded_file_from_json(json: String) -> Result<EmbeddedFile, String> {
    serde_json::from_str::<kreuzberg::pdf::embedded_files::EmbeddedFile>(&json)
        .map(EmbeddedFile::from)
        .map_err(|e| e.to_string())
}

#[frb]
pub fn create_pdf_metadata_from_json(json: String) -> Result<PdfMetadata, String> {
    serde_json::from_str::<kreuzberg::pdf::metadata::PdfMetadata>(&json)
        .map(PdfMetadata::from)
        .map_err(|e| e.to_string())
}

/// Internal Rust-side storage for Dart-provided plugin callbacks.
/// Not exposed via FRB (private to the bridge crate). The public factory
/// `create_{trait_snake}_dart_impl(...)` wraps an `Arc<dyn Trait + Send + Sync>`
/// of this struct in the public opaque `{Trait}DartImpl` newtype. Hiding the
/// closure fields behind the wrapper keeps FRB from walking them and silently
/// dropping the factory (FRB v2 cannot generate callable Dart classes for
/// `Box<dyn Fn(...)>` opaque-struct fields).
struct OcrBackendDartCallbacks {
    /// Plugin name used by the Plugin super-trait impl.
    plugin_name: String,
    /// Plugin version used by the Plugin super-trait impl.
    plugin_version: String,
    process_image: Box<dyn Fn(Vec<u8>, OcrConfig) -> DartFnFuture<ExtractionResult> + Send + Sync>,
    process_image_file: Box<dyn Fn(String, OcrConfig) -> DartFnFuture<ExtractionResult> + Send + Sync>,
    supports_language: Box<dyn Fn(String) -> DartFnFuture<bool> + Send + Sync>,
    backend_type: Box<dyn Fn() -> DartFnFuture<OcrBackendType> + Send + Sync>,
    supported_languages: Box<dyn Fn() -> DartFnFuture<Vec<String>> + Send + Sync>,
    supports_table_detection: Box<dyn Fn() -> DartFnFuture<bool> + Send + Sync>,
    supports_document_processing: Box<dyn Fn() -> DartFnFuture<bool> + Send + Sync>,
    process_document: Box<dyn Fn(String, OcrConfig) -> DartFnFuture<ExtractionResult> + Send + Sync>,
}
impl ::std::fmt::Debug for OcrBackendDartCallbacks {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        f.debug_struct("OcrBackendDartCallbacks").finish_non_exhaustive()
    }
}

impl kreuzberg::plugins::Plugin for OcrBackendDartCallbacks {
    fn name(&self) -> &str {
        &self.plugin_name
    }

    fn version(&self) -> String {
        self.plugin_version.clone()
    }

    fn initialize(&self) -> kreuzberg::Result<()> {
        Ok(())
    }

    fn shutdown(&self) -> kreuzberg::Result<()> {
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::OcrBackend for OcrBackendDartCallbacks {
    async fn process_image(
        &self,
        image_bytes: &[u8],
        config: &kreuzberg::OcrConfig,
    ) -> kreuzberg::Result<kreuzberg::ExtractionResult> {
        let image_bytes = image_bytes.to_vec();
        let config = OcrConfig::from(config.clone());
        Ok((self.process_image)(image_bytes, config).await.into())
    }

    async fn process_image_file(
        &self,
        path: &std::path::Path,
        config: &kreuzberg::OcrConfig,
    ) -> kreuzberg::Result<kreuzberg::ExtractionResult> {
        let path = path.to_string_lossy().into_owned();
        let config = OcrConfig::from(config.clone());
        Ok((self.process_image_file)(path, config).await.into())
    }

    fn supports_language(&self, lang: &str) -> bool {
        let lang = lang.to_string();
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.supports_language)(lang).await });
        __result
    }

    fn backend_type(&self) -> kreuzberg::OcrBackendType {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.backend_type)().await });
        __result.into()
    }

    fn supported_languages(&self) -> Vec<String> {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.supported_languages)().await });
        __result
    }

    fn supports_table_detection(&self) -> bool {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.supports_table_detection)().await });
        __result
    }

    fn supports_document_processing(&self) -> bool {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.supports_document_processing)().await });
        __result
    }

    async fn process_document(
        &self,
        _path: &std::path::Path,
        _config: &kreuzberg::OcrConfig,
    ) -> kreuzberg::Result<kreuzberg::ExtractionResult> {
        let _path = _path.to_string_lossy().into_owned();
        let _config = OcrConfig::from(_config.clone());
        Ok((self.process_document)(_path, _config).await.into())
    }
}

/// Re-exported so FRB's generated `frb_generated.rs` (which strips `dyn` and the
/// qualified path when copying the wrapper's inner type) can resolve `OcrBackend`
/// as a bare ident via its `use crate::*;` preamble.
pub use kreuzberg::OcrBackend;

/// Public opaque handle returned by `create_ocr_backend_dart_impl(...)`.
/// Wraps an `Arc<dyn OcrBackend + Send + Sync>` whose backing object carries the
/// Dart-side callbacks (private to this crate). The wrapper has no closure
/// fields itself, so FRB can bridge it as an opaque type without seeing the
/// callbacks.
#[frb(opaque)]
pub struct OcrBackendDartImpl {
    pub field0: std::sync::Arc<dyn OcrBackend + Send + Sync>,
}

/// Construct a `OcrBackendDartImpl` from Dart callback closures.
/// FRB synthesises a Dart-callable function type for each closure parameter,
/// which is the whole point of taking them as `impl Fn(...) -> DartFnFuture<R>`
/// parameters rather than storing them as `Box<dyn Fn(...)>` fields on an opaque
/// struct (FRB v2 silently drops factories that return opaque structs whose fields
/// it cannot bridge). The returned wrapper holds an `Arc<dyn Trait + Send + Sync>`
/// whose backing object carries the supplied callbacks privately.
/// `plugin_name` and `plugin_version` are required for the Plugin super-trait.
pub fn create_ocr_backend_dart_impl(
    plugin_name: String,
    plugin_version: String,
    process_image: impl Fn(Vec<u8>, OcrConfig) -> DartFnFuture<ExtractionResult> + Send + Sync + 'static,
    process_image_file: impl Fn(String, OcrConfig) -> DartFnFuture<ExtractionResult> + Send + Sync + 'static,
    supports_language: impl Fn(String) -> DartFnFuture<bool> + Send + Sync + 'static,
    backend_type: impl Fn() -> DartFnFuture<OcrBackendType> + Send + Sync + 'static,
    supported_languages: impl Fn() -> DartFnFuture<Vec<String>> + Send + Sync + 'static,
    supports_table_detection: impl Fn() -> DartFnFuture<bool> + Send + Sync + 'static,
    supports_document_processing: impl Fn() -> DartFnFuture<bool> + Send + Sync + 'static,
    process_document: impl Fn(String, OcrConfig) -> DartFnFuture<ExtractionResult> + Send + Sync + 'static,
) -> OcrBackendDartImpl {
    let __impl = OcrBackendDartCallbacks {
        plugin_name,
        plugin_version,
        process_image: Box::new(process_image),
        process_image_file: Box::new(process_image_file),
        supports_language: Box::new(supports_language),
        backend_type: Box::new(backend_type),
        supported_languages: Box::new(supported_languages),
        supports_table_detection: Box::new(supports_table_detection),
        supports_document_processing: Box::new(supports_document_processing),
        process_document: Box::new(process_document),
    };
    OcrBackendDartImpl {
        field0: std::sync::Arc::new(__impl),
    }
}
/// Register a Dart implementation as a `OcrBackend` plugin.
///
/// Forwards the `Arc<dyn OcrBackend>` wrapped by `OcrBackendDartImpl` to
/// `kreuzberg::plugins::registry::get_ocr_backend_registry()`. Errors from the host registry are stringified
/// for FRB transport.
pub fn register_ocr_backend(impl_: OcrBackendDartImpl) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_ocr_backend_registry();
    let mut registry = registry.write();
    registry.register(impl_.field0).map_err(|e| e.to_string())
}

/// Unregister a previously-registered `OcrBackend` plugin by name.
/// Removes the plugin from `kreuzberg::plugins::registry::get_ocr_backend_registry()` and stringifies any host error.
pub fn unregister_ocr_backend(name: String) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_ocr_backend_registry();
    let mut registry = registry.write();
    registry.remove(&name).map_err(|e| e.to_string())
}

/// Clear all registered `OcrBackend` plugins.
/// Removes every plugin from `kreuzberg::plugins::registry::get_ocr_backend_registry()` and stringifies any host error.
pub fn clear_ocr_backends() -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_ocr_backend_registry();
    let mut registry = registry.write();
    registry.clear().map_err(|e| e.to_string())
}

/// Internal Rust-side storage for Dart-provided plugin callbacks.
/// Not exposed via FRB (private to the bridge crate). The public factory
/// `create_{trait_snake}_dart_impl(...)` wraps an `Arc<dyn Trait + Send + Sync>`
/// of this struct in the public opaque `{Trait}DartImpl` newtype. Hiding the
/// closure fields behind the wrapper keeps FRB from walking them and silently
/// dropping the factory (FRB v2 cannot generate callable Dart classes for
/// `Box<dyn Fn(...)>` opaque-struct fields).
struct PostProcessorDartCallbacks {
    /// Plugin name used by the Plugin super-trait impl.
    plugin_name: String,
    /// Plugin version used by the Plugin super-trait impl.
    plugin_version: String,
    process: Box<dyn Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<()> + Send + Sync>,
    processing_stage: Box<dyn Fn() -> DartFnFuture<ProcessingStage> + Send + Sync>,
    should_process: Box<dyn Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<bool> + Send + Sync>,
    estimated_duration_ms: Box<dyn Fn(ExtractionResult) -> DartFnFuture<i64> + Send + Sync>,
    priority: Box<dyn Fn() -> DartFnFuture<i64> + Send + Sync>,
}
impl ::std::fmt::Debug for PostProcessorDartCallbacks {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        f.debug_struct("PostProcessorDartCallbacks").finish_non_exhaustive()
    }
}

impl kreuzberg::plugins::Plugin for PostProcessorDartCallbacks {
    fn name(&self) -> &str {
        &self.plugin_name
    }

    fn version(&self) -> String {
        self.plugin_version.clone()
    }

    fn initialize(&self) -> kreuzberg::Result<()> {
        Ok(())
    }

    fn shutdown(&self) -> kreuzberg::Result<()> {
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::PostProcessor for PostProcessorDartCallbacks {
    async fn process(
        &self,
        result: &mut kreuzberg::ExtractionResult,
        config: &kreuzberg::ExtractionConfig,
    ) -> kreuzberg::Result<()> {
        let result = ExtractionResult::from(result.clone());
        let config = ExtractionConfig::from(config.clone());
        Ok((self.process)(result, config).await)
    }

    fn processing_stage(&self) -> kreuzberg::ProcessingStage {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.processing_stage)().await });
        __result.into()
    }

    fn should_process(&self, _result: &kreuzberg::ExtractionResult, _config: &kreuzberg::ExtractionConfig) -> bool {
        let _result = ExtractionResult::from(_result.clone());
        let _config = ExtractionConfig::from(_config.clone());
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.should_process)(_result, _config).await });
        __result
    }

    fn estimated_duration_ms(&self, _result: &kreuzberg::ExtractionResult) -> u64 {
        let _result = ExtractionResult::from(_result.clone());
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.estimated_duration_ms)(_result).await });
        __result as u64
    }

    fn priority(&self) -> i32 {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.priority)().await });
        __result as i32
    }
}

/// Re-exported so FRB's generated `frb_generated.rs` (which strips `dyn` and the
/// qualified path when copying the wrapper's inner type) can resolve `PostProcessor`
/// as a bare ident via its `use crate::*;` preamble.
pub use kreuzberg::PostProcessor;

/// Public opaque handle returned by `create_post_processor_dart_impl(...)`.
/// Wraps an `Arc<dyn PostProcessor + Send + Sync>` whose backing object carries the
/// Dart-side callbacks (private to this crate). The wrapper has no closure
/// fields itself, so FRB can bridge it as an opaque type without seeing the
/// callbacks.
#[frb(opaque)]
pub struct PostProcessorDartImpl {
    pub field0: std::sync::Arc<dyn PostProcessor + Send + Sync>,
}

/// Construct a `PostProcessorDartImpl` from Dart callback closures.
/// FRB synthesises a Dart-callable function type for each closure parameter,
/// which is the whole point of taking them as `impl Fn(...) -> DartFnFuture<R>`
/// parameters rather than storing them as `Box<dyn Fn(...)>` fields on an opaque
/// struct (FRB v2 silently drops factories that return opaque structs whose fields
/// it cannot bridge). The returned wrapper holds an `Arc<dyn Trait + Send + Sync>`
/// whose backing object carries the supplied callbacks privately.
/// `plugin_name` and `plugin_version` are required for the Plugin super-trait.
pub fn create_post_processor_dart_impl(
    plugin_name: String,
    plugin_version: String,
    process: impl Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<()> + Send + Sync + 'static,
    processing_stage: impl Fn() -> DartFnFuture<ProcessingStage> + Send + Sync + 'static,
    should_process: impl Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<bool> + Send + Sync + 'static,
    estimated_duration_ms: impl Fn(ExtractionResult) -> DartFnFuture<i64> + Send + Sync + 'static,
    priority: impl Fn() -> DartFnFuture<i64> + Send + Sync + 'static,
) -> PostProcessorDartImpl {
    let __impl = PostProcessorDartCallbacks {
        plugin_name,
        plugin_version,
        process: Box::new(process),
        processing_stage: Box::new(processing_stage),
        should_process: Box::new(should_process),
        estimated_duration_ms: Box::new(estimated_duration_ms),
        priority: Box::new(priority),
    };
    PostProcessorDartImpl {
        field0: std::sync::Arc::new(__impl),
    }
}
/// Register a Dart implementation as a `PostProcessor` plugin.
///
/// Forwards the `Arc<dyn PostProcessor>` wrapped by `PostProcessorDartImpl` to
/// `kreuzberg::plugins::registry::get_post_processor_registry()`. Errors from the host registry are stringified
/// for FRB transport.
pub fn register_post_processor(impl_: PostProcessorDartImpl) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_post_processor_registry();
    let mut registry = registry.write();
    registry.register(impl_.field0).map_err(|e| e.to_string())
}

/// Unregister a previously-registered `PostProcessor` plugin by name.
/// Removes the plugin from `kreuzberg::plugins::registry::get_post_processor_registry()` and stringifies any host error.
pub fn unregister_post_processor(name: String) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_post_processor_registry();
    let mut registry = registry.write();
    registry.remove(&name).map_err(|e| e.to_string())
}

/// Clear all registered `PostProcessor` plugins.
/// Removes every plugin from `kreuzberg::plugins::registry::get_post_processor_registry()` and stringifies any host error.
pub fn clear_post_processors() -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_post_processor_registry();
    let mut registry = registry.write();
    registry.clear().map_err(|e| e.to_string())
}

/// Internal Rust-side storage for Dart-provided plugin callbacks.
/// Not exposed via FRB (private to the bridge crate). The public factory
/// `create_{trait_snake}_dart_impl(...)` wraps an `Arc<dyn Trait + Send + Sync>`
/// of this struct in the public opaque `{Trait}DartImpl` newtype. Hiding the
/// closure fields behind the wrapper keeps FRB from walking them and silently
/// dropping the factory (FRB v2 cannot generate callable Dart classes for
/// `Box<dyn Fn(...)>` opaque-struct fields).
struct ValidatorDartCallbacks {
    /// Plugin name used by the Plugin super-trait impl.
    plugin_name: String,
    /// Plugin version used by the Plugin super-trait impl.
    plugin_version: String,
    validate: Box<dyn Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<()> + Send + Sync>,
    should_validate: Box<dyn Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<bool> + Send + Sync>,
    priority: Box<dyn Fn() -> DartFnFuture<i64> + Send + Sync>,
}
impl ::std::fmt::Debug for ValidatorDartCallbacks {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        f.debug_struct("ValidatorDartCallbacks").finish_non_exhaustive()
    }
}

impl kreuzberg::plugins::Plugin for ValidatorDartCallbacks {
    fn name(&self) -> &str {
        &self.plugin_name
    }

    fn version(&self) -> String {
        self.plugin_version.clone()
    }

    fn initialize(&self) -> kreuzberg::Result<()> {
        Ok(())
    }

    fn shutdown(&self) -> kreuzberg::Result<()> {
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::Validator for ValidatorDartCallbacks {
    async fn validate(
        &self,
        result: &kreuzberg::ExtractionResult,
        config: &kreuzberg::ExtractionConfig,
    ) -> kreuzberg::Result<()> {
        let result = ExtractionResult::from(result.clone());
        let config = ExtractionConfig::from(config.clone());
        Ok((self.validate)(result, config).await)
    }

    fn should_validate(&self, _result: &kreuzberg::ExtractionResult, _config: &kreuzberg::ExtractionConfig) -> bool {
        let _result = ExtractionResult::from(_result.clone());
        let _config = ExtractionConfig::from(_config.clone());
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.should_validate)(_result, _config).await });
        __result
    }

    fn priority(&self) -> i32 {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.priority)().await });
        __result as i32
    }
}

/// Re-exported so FRB's generated `frb_generated.rs` (which strips `dyn` and the
/// qualified path when copying the wrapper's inner type) can resolve `Validator`
/// as a bare ident via its `use crate::*;` preamble.
pub use kreuzberg::Validator;

/// Public opaque handle returned by `create_validator_dart_impl(...)`.
/// Wraps an `Arc<dyn Validator + Send + Sync>` whose backing object carries the
/// Dart-side callbacks (private to this crate). The wrapper has no closure
/// fields itself, so FRB can bridge it as an opaque type without seeing the
/// callbacks.
#[frb(opaque)]
pub struct ValidatorDartImpl {
    pub field0: std::sync::Arc<dyn Validator + Send + Sync>,
}

/// Construct a `ValidatorDartImpl` from Dart callback closures.
/// FRB synthesises a Dart-callable function type for each closure parameter,
/// which is the whole point of taking them as `impl Fn(...) -> DartFnFuture<R>`
/// parameters rather than storing them as `Box<dyn Fn(...)>` fields on an opaque
/// struct (FRB v2 silently drops factories that return opaque structs whose fields
/// it cannot bridge). The returned wrapper holds an `Arc<dyn Trait + Send + Sync>`
/// whose backing object carries the supplied callbacks privately.
/// `plugin_name` and `plugin_version` are required for the Plugin super-trait.
pub fn create_validator_dart_impl(
    plugin_name: String,
    plugin_version: String,
    validate: impl Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<()> + Send + Sync + 'static,
    should_validate: impl Fn(ExtractionResult, ExtractionConfig) -> DartFnFuture<bool> + Send + Sync + 'static,
    priority: impl Fn() -> DartFnFuture<i64> + Send + Sync + 'static,
) -> ValidatorDartImpl {
    let __impl = ValidatorDartCallbacks {
        plugin_name,
        plugin_version,
        validate: Box::new(validate),
        should_validate: Box::new(should_validate),
        priority: Box::new(priority),
    };
    ValidatorDartImpl {
        field0: std::sync::Arc::new(__impl),
    }
}
/// Register a Dart implementation as a `Validator` plugin.
///
/// Forwards the `Arc<dyn Validator>` wrapped by `ValidatorDartImpl` to
/// `kreuzberg::plugins::registry::get_validator_registry()`. Errors from the host registry are stringified
/// for FRB transport.
pub fn register_validator(impl_: ValidatorDartImpl) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_validator_registry();
    let mut registry = registry.write();
    registry.register(impl_.field0).map_err(|e| e.to_string())
}

/// Unregister a previously-registered `Validator` plugin by name.
/// Removes the plugin from `kreuzberg::plugins::registry::get_validator_registry()` and stringifies any host error.
pub fn unregister_validator(name: String) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_validator_registry();
    let mut registry = registry.write();
    registry.remove(&name).map_err(|e| e.to_string())
}

/// Clear all registered `Validator` plugins.
/// Removes every plugin from `kreuzberg::plugins::registry::get_validator_registry()` and stringifies any host error.
pub fn clear_validators() -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_validator_registry();
    let mut registry = registry.write();
    registry.clear().map_err(|e| e.to_string())
}

/// Internal Rust-side storage for Dart-provided plugin callbacks.
/// Not exposed via FRB (private to the bridge crate). The public factory
/// `create_{trait_snake}_dart_impl(...)` wraps an `Arc<dyn Trait + Send + Sync>`
/// of this struct in the public opaque `{Trait}DartImpl` newtype. Hiding the
/// closure fields behind the wrapper keeps FRB from walking them and silently
/// dropping the factory (FRB v2 cannot generate callable Dart classes for
/// `Box<dyn Fn(...)>` opaque-struct fields).
struct EmbeddingBackendDartCallbacks {
    /// Plugin name used by the Plugin super-trait impl.
    plugin_name: String,
    /// Plugin version used by the Plugin super-trait impl.
    plugin_version: String,
    dimensions: Box<dyn Fn() -> DartFnFuture<i64> + Send + Sync>,
    embed: Box<dyn Fn(Vec<String>) -> DartFnFuture<Vec<Vec<f64>>> + Send + Sync>,
}
impl ::std::fmt::Debug for EmbeddingBackendDartCallbacks {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        f.debug_struct("EmbeddingBackendDartCallbacks").finish_non_exhaustive()
    }
}

impl kreuzberg::plugins::Plugin for EmbeddingBackendDartCallbacks {
    fn name(&self) -> &str {
        &self.plugin_name
    }

    fn version(&self) -> String {
        self.plugin_version.clone()
    }

    fn initialize(&self) -> kreuzberg::Result<()> {
        Ok(())
    }

    fn shutdown(&self) -> kreuzberg::Result<()> {
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::EmbeddingBackend for EmbeddingBackendDartCallbacks {
    fn dimensions(&self) -> usize {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.dimensions)().await });
        __result as usize
    }

    async fn embed(&self, texts: Vec<String>) -> kreuzberg::Result<Vec<Vec<f32>>> {
        Ok((self.embed)(texts)
            .await
            .into_iter()
            .map(|v| v.into_iter().map(|x| x as f32).collect())
            .collect::<Vec<_>>())
    }
}

/// Re-exported so FRB's generated `frb_generated.rs` (which strips `dyn` and the
/// qualified path when copying the wrapper's inner type) can resolve `EmbeddingBackend`
/// as a bare ident via its `use crate::*;` preamble.
pub use kreuzberg::EmbeddingBackend;

/// Public opaque handle returned by `create_embedding_backend_dart_impl(...)`.
/// Wraps an `Arc<dyn EmbeddingBackend + Send + Sync>` whose backing object carries the
/// Dart-side callbacks (private to this crate). The wrapper has no closure
/// fields itself, so FRB can bridge it as an opaque type without seeing the
/// callbacks.
#[frb(opaque)]
pub struct EmbeddingBackendDartImpl {
    pub field0: std::sync::Arc<dyn EmbeddingBackend + Send + Sync>,
}

/// Construct a `EmbeddingBackendDartImpl` from Dart callback closures.
/// FRB synthesises a Dart-callable function type for each closure parameter,
/// which is the whole point of taking them as `impl Fn(...) -> DartFnFuture<R>`
/// parameters rather than storing them as `Box<dyn Fn(...)>` fields on an opaque
/// struct (FRB v2 silently drops factories that return opaque structs whose fields
/// it cannot bridge). The returned wrapper holds an `Arc<dyn Trait + Send + Sync>`
/// whose backing object carries the supplied callbacks privately.
/// `plugin_name` and `plugin_version` are required for the Plugin super-trait.
pub fn create_embedding_backend_dart_impl(
    plugin_name: String,
    plugin_version: String,
    dimensions: impl Fn() -> DartFnFuture<i64> + Send + Sync + 'static,
    embed: impl Fn(Vec<String>) -> DartFnFuture<Vec<Vec<f64>>> + Send + Sync + 'static,
) -> EmbeddingBackendDartImpl {
    let __impl = EmbeddingBackendDartCallbacks {
        plugin_name,
        plugin_version,
        dimensions: Box::new(dimensions),
        embed: Box::new(embed),
    };
    EmbeddingBackendDartImpl {
        field0: std::sync::Arc::new(__impl),
    }
}
/// Register a Dart implementation as a `EmbeddingBackend` plugin.
///
/// Forwards the `Arc<dyn EmbeddingBackend>` wrapped by `EmbeddingBackendDartImpl` to
/// `kreuzberg::plugins::registry::get_embedding_backend_registry()`. Errors from the host registry are stringified
/// for FRB transport.
pub fn register_embedding_backend(impl_: EmbeddingBackendDartImpl) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_embedding_backend_registry();
    let mut registry = registry.write();
    registry.register(impl_.field0).map_err(|e| e.to_string())
}

/// Unregister a previously-registered `EmbeddingBackend` plugin by name.
/// Removes the plugin from `kreuzberg::plugins::registry::get_embedding_backend_registry()` and stringifies any host error.
pub fn unregister_embedding_backend(name: String) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_embedding_backend_registry();
    let mut registry = registry.write();
    registry.remove(&name).map_err(|e| e.to_string())
}

/// Clear all registered `EmbeddingBackend` plugins.
/// Removes every plugin from `kreuzberg::plugins::registry::get_embedding_backend_registry()` and stringifies any host error.
pub fn clear_embedding_backends() -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_embedding_backend_registry();
    let mut registry = registry.write();
    registry.clear().map_err(|e| e.to_string())
}

/// Internal Rust-side storage for Dart-provided plugin callbacks.
/// Not exposed via FRB (private to the bridge crate). The public factory
/// `create_{trait_snake}_dart_impl(...)` wraps an `Arc<dyn Trait + Send + Sync>`
/// of this struct in the public opaque `{Trait}DartImpl` newtype. Hiding the
/// closure fields behind the wrapper keeps FRB from walking them and silently
/// dropping the factory (FRB v2 cannot generate callable Dart classes for
/// `Box<dyn Fn(...)>` opaque-struct fields).
struct DocumentExtractorDartCallbacks {
    /// Plugin name used by the Plugin super-trait impl.
    plugin_name: String,
    /// Plugin version used by the Plugin super-trait impl.
    plugin_version: String,
    extract_bytes: Box<dyn Fn(Vec<u8>, String, ExtractionConfig) -> DartFnFuture<InternalDocumentBridge> + Send + Sync>,
    extract_file: Box<dyn Fn(String, String, ExtractionConfig) -> DartFnFuture<InternalDocumentBridge> + Send + Sync>,
    supported_mime_types: Box<dyn Fn() -> DartFnFuture<Vec<String>> + Send + Sync>,
    priority: Box<dyn Fn() -> DartFnFuture<i64> + Send + Sync>,
    can_handle: Box<dyn Fn(String, String) -> DartFnFuture<bool> + Send + Sync>,
}
impl ::std::fmt::Debug for DocumentExtractorDartCallbacks {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        f.debug_struct("DocumentExtractorDartCallbacks").finish_non_exhaustive()
    }
}

impl kreuzberg::plugins::Plugin for DocumentExtractorDartCallbacks {
    fn name(&self) -> &str {
        &self.plugin_name
    }

    fn version(&self) -> String {
        self.plugin_version.clone()
    }

    fn initialize(&self) -> kreuzberg::Result<()> {
        Ok(())
    }

    fn shutdown(&self) -> kreuzberg::Result<()> {
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::DocumentExtractor for DocumentExtractorDartCallbacks {
    async fn extract_bytes(
        &self,
        content: &[u8],
        mime_type: &str,
        config: &kreuzberg::ExtractionConfig,
    ) -> kreuzberg::Result<kreuzberg::InternalDocument> {
        let content = content.to_vec();
        let mime_type = mime_type.to_string();
        let config = ExtractionConfig::from(config.clone());
        let __ret_bridge: InternalDocumentBridge = (self.extract_bytes)(content, mime_type, config).await;
        let __ret: kreuzberg::InternalDocument = serde_json::from_str(&__ret_bridge.json)?;
        Ok(__ret)
    }

    async fn extract_file(
        &self,
        path: &std::path::Path,
        mime_type: &str,
        config: &kreuzberg::ExtractionConfig,
    ) -> kreuzberg::Result<kreuzberg::InternalDocument> {
        let path = path.to_string_lossy().into_owned();
        let mime_type = mime_type.to_string();
        let config = ExtractionConfig::from(config.clone());
        let __ret_bridge: InternalDocumentBridge = (self.extract_file)(path, mime_type, config).await;
        let __ret: kreuzberg::InternalDocument = serde_json::from_str(&__ret_bridge.json)?;
        Ok(__ret)
    }

    fn supported_mime_types(&self) -> &[&str] {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.supported_mime_types)().await });
        let __strs: Vec<&'static str> = __result
            .into_iter()
            .map(|s| -> &'static str { Box::leak(s.into_boxed_str()) })
            .collect();
        Box::leak(__strs.into_boxed_slice())
    }

    fn priority(&self) -> i32 {
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.priority)().await });
        __result as i32
    }

    fn can_handle(&self, _path: &std::path::Path, _mime_type: &str) -> bool {
        let _path = _path.to_string_lossy().into_owned();
        let _mime_type = _mime_type.to_string();
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.can_handle)(_path, _mime_type).await });
        __result
    }
}

/// Re-exported so FRB's generated `frb_generated.rs` (which strips `dyn` and the
/// qualified path when copying the wrapper's inner type) can resolve `DocumentExtractor`
/// as a bare ident via its `use crate::*;` preamble.
pub use kreuzberg::DocumentExtractor;

/// Public opaque handle returned by `create_document_extractor_dart_impl(...)`.
/// Wraps an `Arc<dyn DocumentExtractor + Send + Sync>` whose backing object carries the
/// Dart-side callbacks (private to this crate). The wrapper has no closure
/// fields itself, so FRB can bridge it as an opaque type without seeing the
/// callbacks.
#[frb(opaque)]
pub struct DocumentExtractorDartImpl {
    pub field0: std::sync::Arc<dyn DocumentExtractor + Send + Sync>,
}

/// Construct a `DocumentExtractorDartImpl` from Dart callback closures.
/// FRB synthesises a Dart-callable function type for each closure parameter,
/// which is the whole point of taking them as `impl Fn(...) -> DartFnFuture<R>`
/// parameters rather than storing them as `Box<dyn Fn(...)>` fields on an opaque
/// struct (FRB v2 silently drops factories that return opaque structs whose fields
/// it cannot bridge). The returned wrapper holds an `Arc<dyn Trait + Send + Sync>`
/// whose backing object carries the supplied callbacks privately.
/// `plugin_name` and `plugin_version` are required for the Plugin super-trait.
pub fn create_document_extractor_dart_impl(
    plugin_name: String,
    plugin_version: String,
    extract_bytes: impl Fn(Vec<u8>, String, ExtractionConfig) -> DartFnFuture<InternalDocumentBridge>
    + Send
    + Sync
    + 'static,
    extract_file: impl Fn(String, String, ExtractionConfig) -> DartFnFuture<InternalDocumentBridge> + Send + Sync + 'static,
    supported_mime_types: impl Fn() -> DartFnFuture<Vec<String>> + Send + Sync + 'static,
    priority: impl Fn() -> DartFnFuture<i64> + Send + Sync + 'static,
    can_handle: impl Fn(String, String) -> DartFnFuture<bool> + Send + Sync + 'static,
) -> DocumentExtractorDartImpl {
    let __impl = DocumentExtractorDartCallbacks {
        plugin_name,
        plugin_version,
        extract_bytes: Box::new(extract_bytes),
        extract_file: Box::new(extract_file),
        supported_mime_types: Box::new(supported_mime_types),
        priority: Box::new(priority),
        can_handle: Box::new(can_handle),
    };
    DocumentExtractorDartImpl {
        field0: std::sync::Arc::new(__impl),
    }
}
/// Register a Dart implementation as a `DocumentExtractor` plugin.
///
/// Forwards the `Arc<dyn DocumentExtractor>` wrapped by `DocumentExtractorDartImpl` to
/// `kreuzberg::plugins::registry::get_document_extractor_registry()`. Errors from the host registry are stringified
/// for FRB transport.
pub fn register_document_extractor(impl_: DocumentExtractorDartImpl) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_document_extractor_registry();
    let mut registry = registry.write();
    registry.register(impl_.field0).map_err(|e| e.to_string())
}

/// Unregister a previously-registered `DocumentExtractor` plugin by name.
/// Removes the plugin from `kreuzberg::plugins::registry::get_document_extractor_registry()` and stringifies any host error.
pub fn unregister_document_extractor(name: String) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_document_extractor_registry();
    let mut registry = registry.write();
    registry.remove(&name).map_err(|e| e.to_string())
}

/// Clear all registered `DocumentExtractor` plugins.
/// Removes every plugin from `kreuzberg::plugins::registry::get_document_extractor_registry()` and stringifies any host error.
pub fn clear_document_extractors() -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_document_extractor_registry();
    let mut registry = registry.write();
    registry.clear().map_err(|e| e.to_string())
}

/// Internal Rust-side storage for Dart-provided plugin callbacks.
/// Not exposed via FRB (private to the bridge crate). The public factory
/// `create_{trait_snake}_dart_impl(...)` wraps an `Arc<dyn Trait + Send + Sync>`
/// of this struct in the public opaque `{Trait}DartImpl` newtype. Hiding the
/// closure fields behind the wrapper keeps FRB from walking them and silently
/// dropping the factory (FRB v2 cannot generate callable Dart classes for
/// `Box<dyn Fn(...)>` opaque-struct fields).
struct RendererDartCallbacks {
    /// Plugin name used by the Plugin super-trait impl.
    plugin_name: String,
    /// Plugin version used by the Plugin super-trait impl.
    plugin_version: String,
    render: Box<dyn Fn(InternalDocumentBridge) -> DartFnFuture<String> + Send + Sync>,
}
impl ::std::fmt::Debug for RendererDartCallbacks {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        f.debug_struct("RendererDartCallbacks").finish_non_exhaustive()
    }
}

impl kreuzberg::plugins::Plugin for RendererDartCallbacks {
    fn name(&self) -> &str {
        &self.plugin_name
    }

    fn version(&self) -> String {
        self.plugin_version.clone()
    }

    fn initialize(&self) -> kreuzberg::Result<()> {
        Ok(())
    }

    fn shutdown(&self) -> kreuzberg::Result<()> {
        Ok(())
    }
}

impl kreuzberg::Renderer for RendererDartCallbacks {
    fn render(&self, doc: &kreuzberg::InternalDocument) -> kreuzberg::Result<String> {
        let doc = doc.clone();
        let __doc_local = InternalDocumentBridge {
            json: serde_json::to_string(&doc)?,
        };
        let __result = ::tokio::runtime::Builder::new_current_thread()
            .build()
            .expect("build alef visitor tokio runtime")
            .block_on(async { (self.render)(__doc_local).await });
        Ok(__result)
    }
}

/// Re-exported so FRB's generated `frb_generated.rs` (which strips `dyn` and the
/// qualified path when copying the wrapper's inner type) can resolve `Renderer`
/// as a bare ident via its `use crate::*;` preamble.
pub use kreuzberg::Renderer;

/// Public opaque handle returned by `create_renderer_dart_impl(...)`.
/// Wraps an `Arc<dyn Renderer + Send + Sync>` whose backing object carries the
/// Dart-side callbacks (private to this crate). The wrapper has no closure
/// fields itself, so FRB can bridge it as an opaque type without seeing the
/// callbacks.
#[frb(opaque)]
pub struct RendererDartImpl {
    pub field0: std::sync::Arc<dyn Renderer + Send + Sync>,
}

/// Construct a `RendererDartImpl` from Dart callback closures.
/// FRB synthesises a Dart-callable function type for each closure parameter,
/// which is the whole point of taking them as `impl Fn(...) -> DartFnFuture<R>`
/// parameters rather than storing them as `Box<dyn Fn(...)>` fields on an opaque
/// struct (FRB v2 silently drops factories that return opaque structs whose fields
/// it cannot bridge). The returned wrapper holds an `Arc<dyn Trait + Send + Sync>`
/// whose backing object carries the supplied callbacks privately.
/// `plugin_name` and `plugin_version` are required for the Plugin super-trait.
pub fn create_renderer_dart_impl(
    plugin_name: String,
    plugin_version: String,
    render: impl Fn(InternalDocumentBridge) -> DartFnFuture<String> + Send + Sync + 'static,
) -> RendererDartImpl {
    let __impl = RendererDartCallbacks {
        plugin_name,
        plugin_version,
        render: Box::new(render),
    };
    RendererDartImpl {
        field0: std::sync::Arc::new(__impl),
    }
}
/// Register a Dart implementation as a `Renderer` plugin.
///
/// Forwards the `Arc<dyn Renderer>` wrapped by `RendererDartImpl` to
/// `kreuzberg::plugins::registry::get_renderer_registry()`. Errors from the host registry are stringified
/// for FRB transport.
pub fn register_renderer(impl_: RendererDartImpl) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_renderer_registry();
    let mut registry = registry.write();
    registry.register(impl_.field0).map_err(|e| e.to_string())
}

/// Unregister a previously-registered `Renderer` plugin by name.
/// Removes the plugin from `kreuzberg::plugins::registry::get_renderer_registry()` and stringifies any host error.
pub fn unregister_renderer(name: String) -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_renderer_registry();
    let mut registry = registry.write();
    registry.remove(&name).map_err(|e| e.to_string())
}

/// Clear all registered `Renderer` plugins.
/// Removes every plugin from `kreuzberg::plugins::registry::get_renderer_registry()` and stringifies any host error.
pub fn clear_renderers() -> Result<(), String> {
    let registry = kreuzberg::plugins::registry::get_renderer_registry();
    let mut registry = registry.write();
    registry.clear().map_err(|e| e.to_string())
}
