// This file is auto-generated by alef. DO NOT EDIT.
// alef:hash:4e15143f4af1ae8bafbdb1506ef057da924484c66a19483966333558ad437e75
// Re-generate with: alef generate
#![allow(dead_code, unused_imports, unused_variables)]
#![allow(
    clippy::too_many_arguments,
    clippy::let_unit_value,
    clippy::needless_borrow,
    clippy::map_identity,
    clippy::just_underscores_and_digits,
    clippy::unnecessary_cast,
    clippy::unused_unit,
    clippy::unwrap_or_default,
    clippy::derivable_impls,
    clippy::needless_borrows_for_generic_args,
    clippy::unnecessary_fallible_conversions,
    clippy::type_complexity,
    clippy::useless_conversion,
    clippy::clone_on_copy
)]

use async_trait::async_trait;
use magnus::{Error, IntoValueFromNative, Ruby, function, method, prelude::*, try_convert::TryConvertOwned};
use std::collections::HashMap;
use std::sync::Arc;

fn json_to_ruby(handle: &Ruby, val: serde_json::Value) -> magnus::Value {
    use magnus::IntoValue;
    match val {
        serde_json::Value::Null => handle.qnil().into_value_with(handle),
        serde_json::Value::Bool(b) => b.into_value_with(handle),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                i.into_value_with(handle)
            } else if let Some(f) = n.as_f64() {
                f.into_value_with(handle)
            } else {
                handle.qnil().into_value_with(handle)
            }
        }
        serde_json::Value::String(s) => s.into_value_with(handle),
        serde_json::Value::Array(arr) => {
            let ruby_arr = handle.ary_new_capa(arr.len());
            for item in arr {
                let _ = ruby_arr.push(json_to_ruby(handle, item));
            }
            ruby_arr.into_value_with(handle)
        }
        serde_json::Value::Object(map) => {
            let hash = handle.hash_new();
            for (k, v) in map {
                let key = handle.to_symbol(&k);
                let val = json_to_ruby(handle, v);
                let _ = hash.aset(key, val);
            }
            hash.into_value_with(handle)
        }
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::CacheStats")]
pub struct CacheStats {
    total_files: usize,
    total_size_mb: f64,
    available_space_mb: f64,
    oldest_file_age_days: f64,
    newest_file_age_days: f64,
}

unsafe impl IntoValueFromNative for CacheStats {}

impl magnus::TryConvert for CacheStats {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&CacheStats as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into CacheStats: {}", e),
                )
            })?
        };
        serde_json::from_str::<CacheStats>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize CacheStats: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for CacheStats {}

impl CacheStats {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            total_files: kwargs
                .get(ruby.to_symbol("total_files"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            total_size_mb: kwargs
                .get(ruby.to_symbol("total_size_mb"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            available_space_mb: kwargs
                .get(ruby.to_symbol("available_space_mb"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            oldest_file_age_days: kwargs
                .get(ruby.to_symbol("oldest_file_age_days"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            newest_file_age_days: kwargs
                .get(ruby.to_symbol("newest_file_age_days"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn total_files(&self) -> usize {
        self.total_files
    }

    fn total_size_mb(&self) -> f64 {
        self.total_size_mb
    }

    fn available_space_mb(&self) -> f64 {
        self.available_space_mb
    }

    fn oldest_file_age_days(&self) -> f64 {
        self.oldest_file_age_days
    }

    fn newest_file_age_days(&self) -> f64 {
        self.newest_file_age_days
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::AccelerationConfig")]
pub struct AccelerationConfig {
    provider: ExecutionProviderType,
    device_id: u32,
}

unsafe impl IntoValueFromNative for AccelerationConfig {}

impl magnus::TryConvert for AccelerationConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&AccelerationConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into AccelerationConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<AccelerationConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize AccelerationConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for AccelerationConfig {}

impl Default for AccelerationConfig {
    fn default() -> Self {
        Self {
            provider: Default::default(),
            device_id: 0,
        }
    }
}

impl AccelerationConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            provider: kwargs
                .get(ruby.to_symbol("provider"))
                .and_then(|v| ExecutionProviderType::try_convert(v).ok())
                .unwrap_or_default(),
            device_id: kwargs
                .get(ruby.to_symbol("device_id"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn provider(&self) -> ExecutionProviderType {
        self.provider.clone()
    }

    fn device_id(&self) -> u32 {
        self.device_id
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ContentFilterConfig")]
pub struct ContentFilterConfig {
    include_headers: bool,
    include_footers: bool,
    strip_repeating_text: bool,
    include_watermarks: bool,
}

unsafe impl IntoValueFromNative for ContentFilterConfig {}

impl magnus::TryConvert for ContentFilterConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ContentFilterConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ContentFilterConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<ContentFilterConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ContentFilterConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ContentFilterConfig {}

impl Default for ContentFilterConfig {
    fn default() -> Self {
        kreuzberg::ContentFilterConfig::default().into()
    }
}

impl ContentFilterConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            include_headers: kwargs
                .get(ruby.to_symbol("include_headers"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            include_footers: kwargs
                .get(ruby.to_symbol("include_footers"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            strip_repeating_text: kwargs
                .get(ruby.to_symbol("strip_repeating_text"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            include_watermarks: kwargs
                .get(ruby.to_symbol("include_watermarks"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn include_headers(&self) -> bool {
        self.include_headers
    }

    fn include_footers(&self) -> bool {
        self.include_footers
    }

    fn strip_repeating_text(&self) -> bool {
        self.strip_repeating_text
    }

    fn include_watermarks(&self) -> bool {
        self.include_watermarks
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::EmailConfig")]
pub struct EmailConfig {
    msg_fallback_codepage: Option<u32>,
}

unsafe impl IntoValueFromNative for EmailConfig {}

impl magnus::TryConvert for EmailConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmailConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmailConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmailConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmailConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmailConfig {}

impl Default for EmailConfig {
    fn default() -> Self {
        kreuzberg::EmailConfig::default().into()
    }
}

impl EmailConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            msg_fallback_codepage: kwargs
                .get(ruby.to_symbol("msg_fallback_codepage"))
                .and_then(|v| u32::try_convert(v).ok()),
        })
    }

    fn msg_fallback_codepage(&self) -> Option<u32> {
        self.msg_fallback_codepage
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ExtractionConfig")]
pub struct ExtractionConfig {
    use_cache: bool,
    enable_quality_processing: bool,
    ocr: Option<OcrConfig>,
    force_ocr: bool,
    force_ocr_pages: Option<Vec<u32>>,
    disable_ocr: bool,
    chunking: Option<ChunkingConfig>,
    content_filter: Option<ContentFilterConfig>,
    images: Option<ImageExtractionConfig>,
    pdf_options: Option<PdfConfig>,
    token_reduction: Option<TokenReductionOptions>,
    language_detection: Option<LanguageDetectionConfig>,
    pages: Option<PageConfig>,
    keywords: Option<KeywordConfig>,
    postprocessor: Option<PostProcessorConfig>,
    html_options: Option<String>,
    html_output: Option<HtmlOutputConfig>,
    extraction_timeout_secs: Option<u64>,
    max_concurrent_extractions: Option<usize>,
    result_format: ResultFormat,
    security_limits: Option<SecurityLimits>,
    max_embedded_file_bytes: Option<u64>,
    output_format: OutputFormat,
    layout: Option<LayoutDetectionConfig>,
    use_layout_for_markdown: bool,
    include_document_structure: bool,
    acceleration: Option<AccelerationConfig>,
    cache_namespace: Option<String>,
    cache_ttl_secs: Option<u64>,
    email: Option<EmailConfig>,
    concurrency: Option<String>,
    max_archive_depth: usize,
    tree_sitter: Option<TreeSitterConfig>,
    structured_extraction: Option<StructuredExtractionConfig>,
    cancel_token: Option<String>,
}

unsafe impl IntoValueFromNative for ExtractionConfig {}

impl magnus::TryConvert for ExtractionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExtractionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExtractionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExtractionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExtractionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExtractionConfig {}

impl Default for ExtractionConfig {
    fn default() -> Self {
        Self {
            use_cache: true,
            enable_quality_processing: true,
            ocr: None,
            force_ocr: false,
            force_ocr_pages: None,
            disable_ocr: false,
            chunking: None,
            content_filter: None,
            images: None,
            pdf_options: None,
            token_reduction: None,
            language_detection: None,
            pages: None,
            keywords: None,
            postprocessor: None,
            html_options: None,
            html_output: None,
            extraction_timeout_secs: None,
            max_concurrent_extractions: None,
            result_format: Default::default(),
            security_limits: None,
            max_embedded_file_bytes: None,
            output_format: OutputFormat::Plain,
            layout: None,
            use_layout_for_markdown: false,
            include_document_structure: false,
            acceleration: None,
            cache_namespace: None,
            cache_ttl_secs: None,
            email: None,
            concurrency: None,
            max_archive_depth: 0,
            tree_sitter: None,
            structured_extraction: None,
            cancel_token: None,
        }
    }
}

impl ExtractionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            use_cache: kwargs
                .get(ruby.to_symbol("use_cache"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            enable_quality_processing: kwargs
                .get(ruby.to_symbol("enable_quality_processing"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            ocr: kwargs
                .get(ruby.to_symbol("ocr"))
                .and_then(|v| OcrConfig::try_convert(v).ok()),
            force_ocr: kwargs
                .get(ruby.to_symbol("force_ocr"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            force_ocr_pages: kwargs
                .get(ruby.to_symbol("force_ocr_pages"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok()),
            disable_ocr: kwargs
                .get(ruby.to_symbol("disable_ocr"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            chunking: kwargs
                .get(ruby.to_symbol("chunking"))
                .and_then(|v| ChunkingConfig::try_convert(v).ok()),
            content_filter: kwargs
                .get(ruby.to_symbol("content_filter"))
                .and_then(|v| ContentFilterConfig::try_convert(v).ok()),
            images: kwargs
                .get(ruby.to_symbol("images"))
                .and_then(|v| ImageExtractionConfig::try_convert(v).ok()),
            pdf_options: kwargs
                .get(ruby.to_symbol("pdf_options"))
                .and_then(|v| PdfConfig::try_convert(v).ok()),
            token_reduction: kwargs
                .get(ruby.to_symbol("token_reduction"))
                .and_then(|v| TokenReductionOptions::try_convert(v).ok()),
            language_detection: kwargs
                .get(ruby.to_symbol("language_detection"))
                .and_then(|v| LanguageDetectionConfig::try_convert(v).ok()),
            pages: kwargs
                .get(ruby.to_symbol("pages"))
                .and_then(|v| PageConfig::try_convert(v).ok()),
            keywords: kwargs
                .get(ruby.to_symbol("keywords"))
                .and_then(|v| KeywordConfig::try_convert(v).ok()),
            postprocessor: kwargs
                .get(ruby.to_symbol("postprocessor"))
                .and_then(|v| PostProcessorConfig::try_convert(v).ok()),
            html_options: kwargs
                .get(ruby.to_symbol("html_options"))
                .and_then(|v| String::try_convert(v).ok()),
            html_output: kwargs
                .get(ruby.to_symbol("html_output"))
                .and_then(|v| HtmlOutputConfig::try_convert(v).ok()),
            extraction_timeout_secs: kwargs
                .get(ruby.to_symbol("extraction_timeout_secs"))
                .and_then(|v| u64::try_convert(v).ok()),
            max_concurrent_extractions: kwargs
                .get(ruby.to_symbol("max_concurrent_extractions"))
                .and_then(|v| usize::try_convert(v).ok()),
            result_format: kwargs
                .get(ruby.to_symbol("result_format"))
                .and_then(|v| ResultFormat::try_convert(v).ok())
                .unwrap_or_default(),
            security_limits: kwargs
                .get(ruby.to_symbol("security_limits"))
                .and_then(|v| SecurityLimits::try_convert(v).ok()),
            max_embedded_file_bytes: kwargs
                .get(ruby.to_symbol("max_embedded_file_bytes"))
                .and_then(|v| u64::try_convert(v).ok()),
            output_format: kwargs
                .get(ruby.to_symbol("output_format"))
                .and_then(|v| OutputFormat::try_convert(v).ok())
                .unwrap_or(OutputFormat::Plain),
            layout: kwargs
                .get(ruby.to_symbol("layout"))
                .and_then(|v| LayoutDetectionConfig::try_convert(v).ok()),
            use_layout_for_markdown: kwargs
                .get(ruby.to_symbol("use_layout_for_markdown"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            include_document_structure: kwargs
                .get(ruby.to_symbol("include_document_structure"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            acceleration: kwargs
                .get(ruby.to_symbol("acceleration"))
                .and_then(|v| AccelerationConfig::try_convert(v).ok()),
            cache_namespace: kwargs
                .get(ruby.to_symbol("cache_namespace"))
                .and_then(|v| String::try_convert(v).ok()),
            cache_ttl_secs: kwargs
                .get(ruby.to_symbol("cache_ttl_secs"))
                .and_then(|v| u64::try_convert(v).ok()),
            email: kwargs
                .get(ruby.to_symbol("email"))
                .and_then(|v| EmailConfig::try_convert(v).ok()),
            concurrency: kwargs
                .get(ruby.to_symbol("concurrency"))
                .and_then(|v| String::try_convert(v).ok()),
            max_archive_depth: kwargs
                .get(ruby.to_symbol("max_archive_depth"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            tree_sitter: kwargs
                .get(ruby.to_symbol("tree_sitter"))
                .and_then(|v| TreeSitterConfig::try_convert(v).ok()),
            structured_extraction: kwargs
                .get(ruby.to_symbol("structured_extraction"))
                .and_then(|v| StructuredExtractionConfig::try_convert(v).ok()),
            cancel_token: kwargs
                .get(ruby.to_symbol("cancel_token"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn use_cache(&self) -> bool {
        self.use_cache
    }

    fn enable_quality_processing(&self) -> bool {
        self.enable_quality_processing
    }

    fn ocr(&self) -> Option<OcrConfig> {
        self.ocr.clone()
    }

    fn force_ocr(&self) -> bool {
        self.force_ocr
    }

    fn force_ocr_pages(&self) -> Option<Vec<u32>> {
        self.force_ocr_pages.clone()
    }

    fn disable_ocr(&self) -> bool {
        self.disable_ocr
    }

    fn chunking(&self) -> Option<ChunkingConfig> {
        self.chunking.clone()
    }

    fn content_filter(&self) -> Option<ContentFilterConfig> {
        self.content_filter.clone()
    }

    fn images(&self) -> Option<ImageExtractionConfig> {
        self.images.clone()
    }

    fn pdf_options(&self) -> Option<PdfConfig> {
        self.pdf_options.clone()
    }

    fn token_reduction(&self) -> Option<TokenReductionOptions> {
        self.token_reduction.clone()
    }

    fn language_detection(&self) -> Option<LanguageDetectionConfig> {
        self.language_detection.clone()
    }

    fn pages(&self) -> Option<PageConfig> {
        self.pages.clone()
    }

    fn keywords(&self) -> Option<KeywordConfig> {
        self.keywords.clone()
    }

    fn postprocessor(&self) -> Option<PostProcessorConfig> {
        self.postprocessor.clone()
    }

    fn html_options(&self) -> Option<String> {
        self.html_options.clone()
    }

    fn html_output(&self) -> Option<HtmlOutputConfig> {
        self.html_output.clone()
    }

    fn extraction_timeout_secs(&self) -> Option<u64> {
        self.extraction_timeout_secs
    }

    fn max_concurrent_extractions(&self) -> Option<usize> {
        self.max_concurrent_extractions
    }

    fn result_format(&self) -> ResultFormat {
        self.result_format.clone()
    }

    fn security_limits(&self) -> Option<SecurityLimits> {
        self.security_limits.clone()
    }

    fn max_embedded_file_bytes(&self) -> Option<u64> {
        self.max_embedded_file_bytes
    }

    fn output_format(&self) -> OutputFormat {
        self.output_format.clone()
    }

    fn layout(&self) -> Option<LayoutDetectionConfig> {
        self.layout.clone()
    }

    fn use_layout_for_markdown(&self) -> bool {
        self.use_layout_for_markdown
    }

    fn include_document_structure(&self) -> bool {
        self.include_document_structure
    }

    fn acceleration(&self) -> Option<AccelerationConfig> {
        self.acceleration.clone()
    }

    fn cache_namespace(&self) -> Option<String> {
        self.cache_namespace.clone()
    }

    fn cache_ttl_secs(&self) -> Option<u64> {
        self.cache_ttl_secs
    }

    fn email(&self) -> Option<EmailConfig> {
        self.email.clone()
    }

    fn concurrency(&self) -> Option<String> {
        self.concurrency.clone()
    }

    fn max_archive_depth(&self) -> usize {
        self.max_archive_depth
    }

    fn tree_sitter(&self) -> Option<TreeSitterConfig> {
        self.tree_sitter.clone()
    }

    fn structured_extraction(&self) -> Option<StructuredExtractionConfig> {
        self.structured_extraction.clone()
    }

    fn cancel_token(&self) -> Option<String> {
        self.cancel_token.clone()
    }

    fn needs_image_processing(&self) -> bool {
        #[allow(clippy::needless_update)]
        let core_self = kreuzberg::ExtractionConfig {
            use_cache: self.use_cache,

            enable_quality_processing: self.enable_quality_processing,

            ocr: self.ocr.clone().map(Into::into),

            force_ocr: self.force_ocr,

            force_ocr_pages: self.force_ocr_pages.clone(),

            disable_ocr: self.disable_ocr,

            chunking: self.chunking.clone().map(Into::into),

            content_filter: self.content_filter.clone().map(Into::into),

            images: self.images.clone().map(Into::into),

            token_reduction: self.token_reduction.clone().map(Into::into),

            language_detection: self.language_detection.clone().map(Into::into),

            pages: self.pages.clone().map(Into::into),

            postprocessor: self.postprocessor.clone().map(Into::into),

            extraction_timeout_secs: self.extraction_timeout_secs,

            max_concurrent_extractions: self.max_concurrent_extractions,

            result_format: self.result_format.clone().into(),

            security_limits: self.security_limits.clone().map(Into::into),

            max_embedded_file_bytes: self.max_embedded_file_bytes,

            output_format: self.output_format.clone().into(),

            use_layout_for_markdown: self.use_layout_for_markdown,

            include_document_structure: self.include_document_structure,

            acceleration: self.acceleration.clone().map(Into::into),

            cache_namespace: self.cache_namespace.clone(),

            cache_ttl_secs: self.cache_ttl_secs,

            email: self.email.clone().map(Into::into),

            concurrency: Default::default(),

            max_archive_depth: self.max_archive_depth,

            structured_extraction: self.structured_extraction.clone().map(Into::into),

            cancel_token: Default::default(),

            ..Default::default()
        };
        core_self.needs_image_processing()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::FileExtractionConfig")]
pub struct FileExtractionConfig {
    enable_quality_processing: Option<bool>,
    ocr: Option<OcrConfig>,
    force_ocr: Option<bool>,
    force_ocr_pages: Option<Vec<u32>>,
    disable_ocr: Option<bool>,
    chunking: Option<ChunkingConfig>,
    content_filter: Option<ContentFilterConfig>,
    images: Option<ImageExtractionConfig>,
    pdf_options: Option<PdfConfig>,
    token_reduction: Option<TokenReductionOptions>,
    language_detection: Option<LanguageDetectionConfig>,
    pages: Option<PageConfig>,
    keywords: Option<KeywordConfig>,
    postprocessor: Option<PostProcessorConfig>,
    html_options: Option<String>,
    result_format: Option<ResultFormat>,
    output_format: Option<OutputFormat>,
    include_document_structure: Option<bool>,
    layout: Option<LayoutDetectionConfig>,
    timeout_secs: Option<u64>,
    tree_sitter: Option<TreeSitterConfig>,
    structured_extraction: Option<StructuredExtractionConfig>,
}

unsafe impl IntoValueFromNative for FileExtractionConfig {}

impl magnus::TryConvert for FileExtractionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&FileExtractionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into FileExtractionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<FileExtractionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize FileExtractionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for FileExtractionConfig {}

impl Default for FileExtractionConfig {
    fn default() -> Self {
        Self {
            enable_quality_processing: None,
            ocr: None,
            force_ocr: None,
            force_ocr_pages: None,
            disable_ocr: None,
            chunking: None,
            content_filter: None,
            images: None,
            pdf_options: None,
            token_reduction: None,
            language_detection: None,
            pages: None,
            keywords: None,
            postprocessor: None,
            html_options: None,
            result_format: None,
            output_format: None,
            include_document_structure: None,
            layout: None,
            timeout_secs: None,
            tree_sitter: None,
            structured_extraction: None,
        }
    }
}

impl FileExtractionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            enable_quality_processing: kwargs
                .get(ruby.to_symbol("enable_quality_processing"))
                .and_then(|v| bool::try_convert(v).ok()),
            ocr: kwargs
                .get(ruby.to_symbol("ocr"))
                .and_then(|v| OcrConfig::try_convert(v).ok()),
            force_ocr: kwargs
                .get(ruby.to_symbol("force_ocr"))
                .and_then(|v| bool::try_convert(v).ok()),
            force_ocr_pages: kwargs
                .get(ruby.to_symbol("force_ocr_pages"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok()),
            disable_ocr: kwargs
                .get(ruby.to_symbol("disable_ocr"))
                .and_then(|v| bool::try_convert(v).ok()),
            chunking: kwargs
                .get(ruby.to_symbol("chunking"))
                .and_then(|v| ChunkingConfig::try_convert(v).ok()),
            content_filter: kwargs
                .get(ruby.to_symbol("content_filter"))
                .and_then(|v| ContentFilterConfig::try_convert(v).ok()),
            images: kwargs
                .get(ruby.to_symbol("images"))
                .and_then(|v| ImageExtractionConfig::try_convert(v).ok()),
            pdf_options: kwargs
                .get(ruby.to_symbol("pdf_options"))
                .and_then(|v| PdfConfig::try_convert(v).ok()),
            token_reduction: kwargs
                .get(ruby.to_symbol("token_reduction"))
                .and_then(|v| TokenReductionOptions::try_convert(v).ok()),
            language_detection: kwargs
                .get(ruby.to_symbol("language_detection"))
                .and_then(|v| LanguageDetectionConfig::try_convert(v).ok()),
            pages: kwargs
                .get(ruby.to_symbol("pages"))
                .and_then(|v| PageConfig::try_convert(v).ok()),
            keywords: kwargs
                .get(ruby.to_symbol("keywords"))
                .and_then(|v| KeywordConfig::try_convert(v).ok()),
            postprocessor: kwargs
                .get(ruby.to_symbol("postprocessor"))
                .and_then(|v| PostProcessorConfig::try_convert(v).ok()),
            html_options: kwargs
                .get(ruby.to_symbol("html_options"))
                .and_then(|v| String::try_convert(v).ok()),
            result_format: kwargs
                .get(ruby.to_symbol("result_format"))
                .and_then(|v| ResultFormat::try_convert(v).ok()),
            output_format: kwargs
                .get(ruby.to_symbol("output_format"))
                .and_then(|v| OutputFormat::try_convert(v).ok()),
            include_document_structure: kwargs
                .get(ruby.to_symbol("include_document_structure"))
                .and_then(|v| bool::try_convert(v).ok()),
            layout: kwargs
                .get(ruby.to_symbol("layout"))
                .and_then(|v| LayoutDetectionConfig::try_convert(v).ok()),
            timeout_secs: kwargs
                .get(ruby.to_symbol("timeout_secs"))
                .and_then(|v| u64::try_convert(v).ok()),
            tree_sitter: kwargs
                .get(ruby.to_symbol("tree_sitter"))
                .and_then(|v| TreeSitterConfig::try_convert(v).ok()),
            structured_extraction: kwargs
                .get(ruby.to_symbol("structured_extraction"))
                .and_then(|v| StructuredExtractionConfig::try_convert(v).ok()),
        })
    }

    fn enable_quality_processing(&self) -> Option<bool> {
        self.enable_quality_processing
    }

    fn ocr(&self) -> Option<OcrConfig> {
        self.ocr.clone()
    }

    fn force_ocr(&self) -> Option<bool> {
        self.force_ocr
    }

    fn force_ocr_pages(&self) -> Option<Vec<u32>> {
        self.force_ocr_pages.clone()
    }

    fn disable_ocr(&self) -> Option<bool> {
        self.disable_ocr
    }

    fn chunking(&self) -> Option<ChunkingConfig> {
        self.chunking.clone()
    }

    fn content_filter(&self) -> Option<ContentFilterConfig> {
        self.content_filter.clone()
    }

    fn images(&self) -> Option<ImageExtractionConfig> {
        self.images.clone()
    }

    fn pdf_options(&self) -> Option<PdfConfig> {
        self.pdf_options.clone()
    }

    fn token_reduction(&self) -> Option<TokenReductionOptions> {
        self.token_reduction.clone()
    }

    fn language_detection(&self) -> Option<LanguageDetectionConfig> {
        self.language_detection.clone()
    }

    fn pages(&self) -> Option<PageConfig> {
        self.pages.clone()
    }

    fn keywords(&self) -> Option<KeywordConfig> {
        self.keywords.clone()
    }

    fn postprocessor(&self) -> Option<PostProcessorConfig> {
        self.postprocessor.clone()
    }

    fn html_options(&self) -> Option<String> {
        self.html_options.clone()
    }

    fn result_format(&self) -> Option<ResultFormat> {
        self.result_format.clone()
    }

    fn output_format(&self) -> Option<OutputFormat> {
        self.output_format.clone()
    }

    fn include_document_structure(&self) -> Option<bool> {
        self.include_document_structure
    }

    fn layout(&self) -> Option<LayoutDetectionConfig> {
        self.layout.clone()
    }

    fn timeout_secs(&self) -> Option<u64> {
        self.timeout_secs
    }

    fn tree_sitter(&self) -> Option<TreeSitterConfig> {
        self.tree_sitter.clone()
    }

    fn structured_extraction(&self) -> Option<StructuredExtractionConfig> {
        self.structured_extraction.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::BatchBytesItem")]
pub struct BatchBytesItem {
    content: Vec<u8>,
    mime_type: String,
    config: Option<FileExtractionConfig>,
}

unsafe impl IntoValueFromNative for BatchBytesItem {}

impl magnus::TryConvert for BatchBytesItem {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&BatchBytesItem as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into BatchBytesItem: {}", e),
                )
            })?
        };
        serde_json::from_str::<BatchBytesItem>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize BatchBytesItem: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for BatchBytesItem {}

impl BatchBytesItem {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| <Vec<u8>>::try_convert(v).ok())
                .unwrap_or_default(),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            config: kwargs
                .get(ruby.to_symbol("config"))
                .and_then(|v| FileExtractionConfig::try_convert(v).ok()),
        })
    }

    fn content(&self) -> Vec<u8> {
        self.content.clone()
    }

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

    fn config(&self) -> Option<FileExtractionConfig> {
        self.config.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::BatchFileItem")]
pub struct BatchFileItem {
    path: String,
    config: Option<FileExtractionConfig>,
}

unsafe impl IntoValueFromNative for BatchFileItem {}

impl magnus::TryConvert for BatchFileItem {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&BatchFileItem as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into BatchFileItem: {}", e),
                )
            })?
        };
        serde_json::from_str::<BatchFileItem>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize BatchFileItem: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for BatchFileItem {}

impl BatchFileItem {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            path: kwargs
                .get(ruby.to_symbol("path"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            config: kwargs
                .get(ruby.to_symbol("config"))
                .and_then(|v| FileExtractionConfig::try_convert(v).ok()),
        })
    }

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

    fn config(&self) -> Option<FileExtractionConfig> {
        self.config.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ImageExtractionConfig")]
pub struct ImageExtractionConfig {
    extract_images: bool,
    target_dpi: i32,
    max_image_dimension: i32,
    inject_placeholders: bool,
    auto_adjust_dpi: bool,
    min_dpi: i32,
    max_dpi: i32,
    max_images_per_page: Option<u32>,
    classify: bool,
    include_page_rasters: bool,
    run_ocr_on_images: bool,
    ocr_text_only: bool,
    append_ocr_text: bool,
}

unsafe impl IntoValueFromNative for ImageExtractionConfig {}

impl magnus::TryConvert for ImageExtractionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ImageExtractionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ImageExtractionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<ImageExtractionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ImageExtractionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ImageExtractionConfig {}

impl Default for ImageExtractionConfig {
    fn default() -> Self {
        kreuzberg::ImageExtractionConfig::default().into()
    }
}

impl ImageExtractionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            extract_images: kwargs
                .get(ruby.to_symbol("extract_images"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            target_dpi: kwargs
                .get(ruby.to_symbol("target_dpi"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(300),
            max_image_dimension: kwargs
                .get(ruby.to_symbol("max_image_dimension"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(4096),
            inject_placeholders: kwargs
                .get(ruby.to_symbol("inject_placeholders"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            auto_adjust_dpi: kwargs
                .get(ruby.to_symbol("auto_adjust_dpi"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            min_dpi: kwargs
                .get(ruby.to_symbol("min_dpi"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(72),
            max_dpi: kwargs
                .get(ruby.to_symbol("max_dpi"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(600),
            max_images_per_page: kwargs
                .get(ruby.to_symbol("max_images_per_page"))
                .and_then(|v| u32::try_convert(v).ok()),
            classify: kwargs
                .get(ruby.to_symbol("classify"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            include_page_rasters: kwargs
                .get(ruby.to_symbol("include_page_rasters"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            run_ocr_on_images: kwargs
                .get(ruby.to_symbol("run_ocr_on_images"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            ocr_text_only: kwargs
                .get(ruby.to_symbol("ocr_text_only"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            append_ocr_text: kwargs
                .get(ruby.to_symbol("append_ocr_text"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn extract_images(&self) -> bool {
        self.extract_images
    }

    fn target_dpi(&self) -> i32 {
        self.target_dpi
    }

    fn max_image_dimension(&self) -> i32 {
        self.max_image_dimension
    }

    fn inject_placeholders(&self) -> bool {
        self.inject_placeholders
    }

    fn auto_adjust_dpi(&self) -> bool {
        self.auto_adjust_dpi
    }

    fn min_dpi(&self) -> i32 {
        self.min_dpi
    }

    fn max_dpi(&self) -> i32 {
        self.max_dpi
    }

    fn max_images_per_page(&self) -> Option<u32> {
        self.max_images_per_page
    }

    fn classify(&self) -> bool {
        self.classify
    }

    fn include_page_rasters(&self) -> bool {
        self.include_page_rasters
    }

    fn run_ocr_on_images(&self) -> bool {
        self.run_ocr_on_images
    }

    fn ocr_text_only(&self) -> bool {
        self.ocr_text_only
    }

    fn append_ocr_text(&self) -> bool {
        self.append_ocr_text
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TokenReductionOptions")]
pub struct TokenReductionOptions {
    mode: String,
    preserve_important_words: bool,
}

unsafe impl IntoValueFromNative for TokenReductionOptions {}

impl magnus::TryConvert for TokenReductionOptions {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TokenReductionOptions as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TokenReductionOptions: {}", e),
                )
            })?
        };
        serde_json::from_str::<TokenReductionOptions>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TokenReductionOptions: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TokenReductionOptions {}

impl Default for TokenReductionOptions {
    fn default() -> Self {
        kreuzberg::TokenReductionOptions::default().into()
    }
}

impl TokenReductionOptions {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            mode: kwargs
                .get(ruby.to_symbol("mode"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            preserve_important_words: kwargs
                .get(ruby.to_symbol("preserve_important_words"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
        })
    }

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

    fn preserve_important_words(&self) -> bool {
        self.preserve_important_words
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::LanguageDetectionConfig")]
pub struct LanguageDetectionConfig {
    enabled: bool,
    min_confidence: f64,
    detect_multiple: bool,
}

unsafe impl IntoValueFromNative for LanguageDetectionConfig {}

impl magnus::TryConvert for LanguageDetectionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LanguageDetectionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LanguageDetectionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<LanguageDetectionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LanguageDetectionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LanguageDetectionConfig {}

impl Default for LanguageDetectionConfig {
    fn default() -> Self {
        kreuzberg::LanguageDetectionConfig::default().into()
    }
}

impl LanguageDetectionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            enabled: kwargs
                .get(ruby.to_symbol("enabled"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            min_confidence: kwargs
                .get(ruby.to_symbol("min_confidence"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.8),
            detect_multiple: kwargs
                .get(ruby.to_symbol("detect_multiple"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn enabled(&self) -> bool {
        self.enabled
    }

    fn min_confidence(&self) -> f64 {
        self.min_confidence
    }

    fn detect_multiple(&self) -> bool {
        self.detect_multiple
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::HtmlOutputConfig")]
pub struct HtmlOutputConfig {
    css: Option<String>,
    css_file: Option<String>,
    theme: HtmlTheme,
    class_prefix: String,
    embed_css: bool,
}

unsafe impl IntoValueFromNative for HtmlOutputConfig {}

impl magnus::TryConvert for HtmlOutputConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HtmlOutputConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HtmlOutputConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<HtmlOutputConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HtmlOutputConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HtmlOutputConfig {}

impl Default for HtmlOutputConfig {
    fn default() -> Self {
        Self {
            css: None,
            css_file: None,
            theme: HtmlTheme::Unstyled,
            class_prefix: String::new(),
            embed_css: true,
        }
    }
}

impl HtmlOutputConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            css: kwargs
                .get(ruby.to_symbol("css"))
                .and_then(|v| String::try_convert(v).ok()),
            css_file: kwargs
                .get(ruby.to_symbol("css_file"))
                .and_then(|v| String::try_convert(v).ok()),
            theme: kwargs
                .get(ruby.to_symbol("theme"))
                .and_then(|v| HtmlTheme::try_convert(v).ok())
                .unwrap_or(HtmlTheme::Unstyled),
            class_prefix: kwargs
                .get(ruby.to_symbol("class_prefix"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            embed_css: kwargs
                .get(ruby.to_symbol("embed_css"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
        })
    }

    fn css(&self) -> Option<String> {
        self.css.clone()
    }

    fn css_file(&self) -> Option<String> {
        self.css_file.clone()
    }

    fn theme(&self) -> HtmlTheme {
        self.theme.clone()
    }

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

    fn embed_css(&self) -> bool {
        self.embed_css
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::LayoutDetectionConfig")]
pub struct LayoutDetectionConfig {
    confidence_threshold: Option<f32>,
    apply_heuristics: bool,
    table_model: TableModel,
    acceleration: Option<AccelerationConfig>,
}

unsafe impl IntoValueFromNative for LayoutDetectionConfig {}

impl magnus::TryConvert for LayoutDetectionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LayoutDetectionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LayoutDetectionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<LayoutDetectionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LayoutDetectionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LayoutDetectionConfig {}

impl Default for LayoutDetectionConfig {
    fn default() -> Self {
        Self {
            confidence_threshold: None,
            apply_heuristics: true,
            table_model: Default::default(),
            acceleration: None,
        }
    }
}

impl LayoutDetectionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            confidence_threshold: kwargs
                .get(ruby.to_symbol("confidence_threshold"))
                .and_then(|v| f32::try_convert(v).ok()),
            apply_heuristics: kwargs
                .get(ruby.to_symbol("apply_heuristics"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            table_model: kwargs
                .get(ruby.to_symbol("table_model"))
                .and_then(|v| TableModel::try_convert(v).ok())
                .unwrap_or_default(),
            acceleration: kwargs
                .get(ruby.to_symbol("acceleration"))
                .and_then(|v| AccelerationConfig::try_convert(v).ok()),
        })
    }

    fn confidence_threshold(&self) -> Option<f32> {
        self.confidence_threshold
    }

    fn apply_heuristics(&self) -> bool {
        self.apply_heuristics
    }

    fn table_model(&self) -> TableModel {
        self.table_model.clone()
    }

    fn acceleration(&self) -> Option<AccelerationConfig> {
        self.acceleration.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::LlmConfig")]
pub struct LlmConfig {
    model: String,
    api_key: Option<String>,
    base_url: Option<String>,
    timeout_secs: Option<u64>,
    max_retries: Option<u32>,
    temperature: Option<f64>,
    max_tokens: Option<u64>,
}

unsafe impl IntoValueFromNative for LlmConfig {}

impl magnus::TryConvert for LlmConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LlmConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LlmConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<LlmConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LlmConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LlmConfig {}

impl Default for LlmConfig {
    fn default() -> Self {
        kreuzberg::LlmConfig::default().into()
    }
}

impl LlmConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            model: kwargs
                .get(ruby.to_symbol("model"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            api_key: kwargs
                .get(ruby.to_symbol("api_key"))
                .and_then(|v| String::try_convert(v).ok()),
            base_url: kwargs
                .get(ruby.to_symbol("base_url"))
                .and_then(|v| String::try_convert(v).ok()),
            timeout_secs: kwargs
                .get(ruby.to_symbol("timeout_secs"))
                .and_then(|v| u64::try_convert(v).ok()),
            max_retries: kwargs
                .get(ruby.to_symbol("max_retries"))
                .and_then(|v| u32::try_convert(v).ok()),
            temperature: kwargs
                .get(ruby.to_symbol("temperature"))
                .and_then(|v| f64::try_convert(v).ok()),
            max_tokens: kwargs
                .get(ruby.to_symbol("max_tokens"))
                .and_then(|v| u64::try_convert(v).ok()),
        })
    }

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

    fn api_key(&self) -> Option<String> {
        self.api_key.clone()
    }

    fn base_url(&self) -> Option<String> {
        self.base_url.clone()
    }

    fn timeout_secs(&self) -> Option<u64> {
        self.timeout_secs
    }

    fn max_retries(&self) -> Option<u32> {
        self.max_retries
    }

    fn temperature(&self) -> Option<f64> {
        self.temperature
    }

    fn max_tokens(&self) -> Option<u64> {
        self.max_tokens
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::StructuredExtractionConfig")]
pub struct StructuredExtractionConfig {
    schema: String,
    schema_name: String,
    schema_description: Option<String>,
    strict: bool,
    prompt: Option<String>,
    llm: LlmConfig,
}

unsafe impl IntoValueFromNative for StructuredExtractionConfig {}

impl magnus::TryConvert for StructuredExtractionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&StructuredExtractionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into StructuredExtractionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<StructuredExtractionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize StructuredExtractionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for StructuredExtractionConfig {}

impl Default for StructuredExtractionConfig {
    fn default() -> Self {
        Self {
            schema: String::new(),
            schema_name: String::new(),
            schema_description: None,
            strict: false,
            prompt: None,
            llm: LlmConfig::default(),
        }
    }
}

impl StructuredExtractionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            schema: kwargs
                .get(ruby.to_symbol("schema"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            schema_name: kwargs
                .get(ruby.to_symbol("schema_name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or(String::new()),
            schema_description: kwargs
                .get(ruby.to_symbol("schema_description"))
                .and_then(|v| String::try_convert(v).ok()),
            strict: kwargs
                .get(ruby.to_symbol("strict"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            prompt: kwargs
                .get(ruby.to_symbol("prompt"))
                .and_then(|v| String::try_convert(v).ok()),
            llm: kwargs
                .get(ruby.to_symbol("llm"))
                .and_then(|v| LlmConfig::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: llm",
                    )
                })?,
        })
    }

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

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

    fn schema_description(&self) -> Option<String> {
        self.schema_description.clone()
    }

    fn strict(&self) -> bool {
        self.strict
    }

    fn prompt(&self) -> Option<String> {
        self.prompt.clone()
    }

    fn llm(&self) -> LlmConfig {
        self.llm.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::OcrQualityThresholds")]
pub struct OcrQualityThresholds {
    min_total_non_whitespace: usize,
    min_non_whitespace_per_page: f64,
    min_meaningful_word_len: usize,
    min_meaningful_words: usize,
    min_alnum_ratio: f64,
    min_garbage_chars: usize,
    max_fragmented_word_ratio: f64,
    critical_fragmented_word_ratio: f64,
    min_avg_word_length: f64,
    min_words_for_avg_length_check: usize,
    min_consecutive_repeat_ratio: f64,
    min_words_for_repeat_check: usize,
    substantive_min_chars: usize,
    non_text_min_chars: usize,
    alnum_ws_ratio_threshold: f64,
    pipeline_min_quality: f64,
}

unsafe impl IntoValueFromNative for OcrQualityThresholds {}

impl magnus::TryConvert for OcrQualityThresholds {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrQualityThresholds as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrQualityThresholds: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrQualityThresholds>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrQualityThresholds: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrQualityThresholds {}

impl Default for OcrQualityThresholds {
    fn default() -> Self {
        kreuzberg::OcrQualityThresholds::default().into()
    }
}

impl OcrQualityThresholds {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            min_total_non_whitespace: kwargs
                .get(ruby.to_symbol("min_total_non_whitespace"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(64),
            min_non_whitespace_per_page: kwargs
                .get(ruby.to_symbol("min_non_whitespace_per_page"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(32.0),
            min_meaningful_word_len: kwargs
                .get(ruby.to_symbol("min_meaningful_word_len"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(4),
            min_meaningful_words: kwargs
                .get(ruby.to_symbol("min_meaningful_words"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(3),
            min_alnum_ratio: kwargs
                .get(ruby.to_symbol("min_alnum_ratio"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.3),
            min_garbage_chars: kwargs
                .get(ruby.to_symbol("min_garbage_chars"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(5),
            max_fragmented_word_ratio: kwargs
                .get(ruby.to_symbol("max_fragmented_word_ratio"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.6),
            critical_fragmented_word_ratio: kwargs
                .get(ruby.to_symbol("critical_fragmented_word_ratio"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.8),
            min_avg_word_length: kwargs
                .get(ruby.to_symbol("min_avg_word_length"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(2.0),
            min_words_for_avg_length_check: kwargs
                .get(ruby.to_symbol("min_words_for_avg_length_check"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(50),
            min_consecutive_repeat_ratio: kwargs
                .get(ruby.to_symbol("min_consecutive_repeat_ratio"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.08),
            min_words_for_repeat_check: kwargs
                .get(ruby.to_symbol("min_words_for_repeat_check"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(50),
            substantive_min_chars: kwargs
                .get(ruby.to_symbol("substantive_min_chars"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(100),
            non_text_min_chars: kwargs
                .get(ruby.to_symbol("non_text_min_chars"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(20),
            alnum_ws_ratio_threshold: kwargs
                .get(ruby.to_symbol("alnum_ws_ratio_threshold"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.4),
            pipeline_min_quality: kwargs
                .get(ruby.to_symbol("pipeline_min_quality"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.5),
        })
    }

    fn min_total_non_whitespace(&self) -> usize {
        self.min_total_non_whitespace
    }

    fn min_non_whitespace_per_page(&self) -> f64 {
        self.min_non_whitespace_per_page
    }

    fn min_meaningful_word_len(&self) -> usize {
        self.min_meaningful_word_len
    }

    fn min_meaningful_words(&self) -> usize {
        self.min_meaningful_words
    }

    fn min_alnum_ratio(&self) -> f64 {
        self.min_alnum_ratio
    }

    fn min_garbage_chars(&self) -> usize {
        self.min_garbage_chars
    }

    fn max_fragmented_word_ratio(&self) -> f64 {
        self.max_fragmented_word_ratio
    }

    fn critical_fragmented_word_ratio(&self) -> f64 {
        self.critical_fragmented_word_ratio
    }

    fn min_avg_word_length(&self) -> f64 {
        self.min_avg_word_length
    }

    fn min_words_for_avg_length_check(&self) -> usize {
        self.min_words_for_avg_length_check
    }

    fn min_consecutive_repeat_ratio(&self) -> f64 {
        self.min_consecutive_repeat_ratio
    }

    fn min_words_for_repeat_check(&self) -> usize {
        self.min_words_for_repeat_check
    }

    fn substantive_min_chars(&self) -> usize {
        self.substantive_min_chars
    }

    fn non_text_min_chars(&self) -> usize {
        self.non_text_min_chars
    }

    fn alnum_ws_ratio_threshold(&self) -> f64 {
        self.alnum_ws_ratio_threshold
    }

    fn pipeline_min_quality(&self) -> f64 {
        self.pipeline_min_quality
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OcrPipelineStage")]
pub struct OcrPipelineStage {
    backend: String,
    priority: u32,
    language: Option<String>,
    tesseract_config: Option<TesseractConfig>,
    paddle_ocr_config: Option<String>,
    vlm_config: Option<LlmConfig>,
    backend_options: Option<String>,
}

unsafe impl IntoValueFromNative for OcrPipelineStage {}

impl magnus::TryConvert for OcrPipelineStage {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrPipelineStage as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrPipelineStage: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrPipelineStage>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrPipelineStage: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrPipelineStage {}

impl Default for OcrPipelineStage {
    fn default() -> Self {
        Self {
            backend: String::new(),
            priority: 0,
            language: None,
            tesseract_config: None,
            paddle_ocr_config: None,
            vlm_config: None,
            backend_options: None,
        }
    }
}

impl OcrPipelineStage {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            backend: kwargs
                .get(ruby.to_symbol("backend"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            priority: kwargs
                .get(ruby.to_symbol("priority"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or(0),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok()),
            tesseract_config: kwargs
                .get(ruby.to_symbol("tesseract_config"))
                .and_then(|v| TesseractConfig::try_convert(v).ok()),
            paddle_ocr_config: kwargs
                .get(ruby.to_symbol("paddle_ocr_config"))
                .and_then(|v| String::try_convert(v).ok()),
            vlm_config: kwargs
                .get(ruby.to_symbol("vlm_config"))
                .and_then(|v| LlmConfig::try_convert(v).ok()),
            backend_options: kwargs
                .get(ruby.to_symbol("backend_options"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

    fn priority(&self) -> u32 {
        self.priority
    }

    fn language(&self) -> Option<String> {
        self.language.clone()
    }

    fn tesseract_config(&self) -> Option<TesseractConfig> {
        self.tesseract_config.clone()
    }

    fn paddle_ocr_config(&self) -> Option<String> {
        self.paddle_ocr_config.clone()
    }

    fn vlm_config(&self) -> Option<LlmConfig> {
        self.vlm_config.clone()
    }

    fn backend_options(&self) -> Option<String> {
        self.backend_options.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OcrPipelineConfig")]
pub struct OcrPipelineConfig {
    stages: Vec<OcrPipelineStage>,
    quality_thresholds: OcrQualityThresholds,
}

unsafe impl IntoValueFromNative for OcrPipelineConfig {}

impl magnus::TryConvert for OcrPipelineConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrPipelineConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrPipelineConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrPipelineConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrPipelineConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrPipelineConfig {}

impl Default for OcrPipelineConfig {
    fn default() -> Self {
        Self {
            stages: vec![],
            quality_thresholds: OcrQualityThresholds::default(),
        }
    }
}

impl OcrPipelineConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            stages: kwargs
                .get(ruby.to_symbol("stages"))
                .and_then(|v| <Vec<OcrPipelineStage>>::try_convert(v).ok())
                .unwrap_or_default(),
            quality_thresholds: kwargs
                .get(ruby.to_symbol("quality_thresholds"))
                .and_then(|v| OcrQualityThresholds::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: quality_thresholds",
                    )
                })?,
        })
    }

    fn stages(&self) -> Vec<OcrPipelineStage> {
        self.stages.clone()
    }

    fn quality_thresholds(&self) -> OcrQualityThresholds {
        self.quality_thresholds.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::OcrConfig")]
pub struct OcrConfig {
    enabled: bool,
    backend: String,
    language: String,
    tesseract_config: Option<TesseractConfig>,
    output_format: Option<OutputFormat>,
    paddle_ocr_config: Option<String>,
    backend_options: Option<String>,
    element_config: Option<OcrElementConfig>,
    quality_thresholds: Option<OcrQualityThresholds>,
    pipeline: Option<OcrPipelineConfig>,
    auto_rotate: bool,
    vlm_config: Option<LlmConfig>,
    vlm_prompt: Option<String>,
    acceleration: Option<AccelerationConfig>,
    tessdata_bytes: Option<HashMap<String, Vec<u8>>>,
}

unsafe impl IntoValueFromNative for OcrConfig {}

impl magnus::TryConvert for OcrConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrConfig {}

impl Default for OcrConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            backend: String::new(),
            language: String::new(),
            tesseract_config: None,
            output_format: None,
            paddle_ocr_config: None,
            backend_options: None,
            element_config: None,
            quality_thresholds: None,
            pipeline: None,
            auto_rotate: false,
            vlm_config: None,
            vlm_prompt: None,
            acceleration: None,
            tessdata_bytes: None,
        }
    }
}

impl OcrConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            enabled: kwargs
                .get(ruby.to_symbol("enabled"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            backend: kwargs
                .get(ruby.to_symbol("backend"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            tesseract_config: kwargs
                .get(ruby.to_symbol("tesseract_config"))
                .and_then(|v| TesseractConfig::try_convert(v).ok()),
            output_format: kwargs
                .get(ruby.to_symbol("output_format"))
                .and_then(|v| OutputFormat::try_convert(v).ok()),
            paddle_ocr_config: kwargs
                .get(ruby.to_symbol("paddle_ocr_config"))
                .and_then(|v| String::try_convert(v).ok()),
            backend_options: kwargs
                .get(ruby.to_symbol("backend_options"))
                .and_then(|v| String::try_convert(v).ok()),
            element_config: kwargs
                .get(ruby.to_symbol("element_config"))
                .and_then(|v| OcrElementConfig::try_convert(v).ok()),
            quality_thresholds: kwargs
                .get(ruby.to_symbol("quality_thresholds"))
                .and_then(|v| OcrQualityThresholds::try_convert(v).ok()),
            pipeline: kwargs
                .get(ruby.to_symbol("pipeline"))
                .and_then(|v| OcrPipelineConfig::try_convert(v).ok()),
            auto_rotate: kwargs
                .get(ruby.to_symbol("auto_rotate"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            vlm_config: kwargs
                .get(ruby.to_symbol("vlm_config"))
                .and_then(|v| LlmConfig::try_convert(v).ok()),
            vlm_prompt: kwargs
                .get(ruby.to_symbol("vlm_prompt"))
                .and_then(|v| String::try_convert(v).ok()),
            acceleration: kwargs
                .get(ruby.to_symbol("acceleration"))
                .and_then(|v| AccelerationConfig::try_convert(v).ok()),
            tessdata_bytes: kwargs
                .get(ruby.to_symbol("tessdata_bytes"))
                .and_then(|v| <HashMap<String, Vec<u8>>>::try_convert(v).ok()),
        })
    }

    fn enabled(&self) -> bool {
        self.enabled
    }

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

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

    fn tesseract_config(&self) -> Option<TesseractConfig> {
        self.tesseract_config.clone()
    }

    fn output_format(&self) -> Option<OutputFormat> {
        self.output_format.clone()
    }

    fn paddle_ocr_config(&self) -> Option<String> {
        self.paddle_ocr_config.clone()
    }

    fn backend_options(&self) -> Option<String> {
        self.backend_options.clone()
    }

    fn element_config(&self) -> Option<OcrElementConfig> {
        self.element_config.clone()
    }

    fn quality_thresholds(&self) -> Option<OcrQualityThresholds> {
        self.quality_thresholds.clone()
    }

    fn pipeline(&self) -> Option<OcrPipelineConfig> {
        self.pipeline.clone()
    }

    fn auto_rotate(&self) -> bool {
        self.auto_rotate
    }

    fn vlm_config(&self) -> Option<LlmConfig> {
        self.vlm_config.clone()
    }

    fn vlm_prompt(&self) -> Option<String> {
        self.vlm_prompt.clone()
    }

    fn acceleration(&self) -> Option<AccelerationConfig> {
        self.acceleration.clone()
    }

    fn tessdata_bytes(&self) -> Option<HashMap<String, Vec<u8>>> {
        self.tessdata_bytes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PageConfig")]
pub struct PageConfig {
    extract_pages: bool,
    insert_page_markers: bool,
    marker_format: String,
}

unsafe impl IntoValueFromNative for PageConfig {}

impl magnus::TryConvert for PageConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PageConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PageConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<PageConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PageConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PageConfig {}

impl Default for PageConfig {
    fn default() -> Self {
        kreuzberg::PageConfig::default().into()
    }
}

impl PageConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            extract_pages: kwargs
                .get(ruby.to_symbol("extract_pages"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            insert_page_markers: kwargs
                .get(ruby.to_symbol("insert_page_markers"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            marker_format: kwargs
                .get(ruby.to_symbol("marker_format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or(
                    "

    <!-- PAGE {page_num} -->

    "
                    .to_string(),
                ),
        })
    }

    fn extract_pages(&self) -> bool {
        self.extract_pages
    }

    fn insert_page_markers(&self) -> bool {
        self.insert_page_markers
    }

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PdfConfig")]
pub struct PdfConfig {
    extract_images: bool,
    extract_tables: bool,
    passwords: Option<Vec<String>>,
    extract_metadata: bool,
    hierarchy: Option<HierarchyConfig>,
    extract_annotations: bool,
    top_margin_fraction: Option<f32>,
    bottom_margin_fraction: Option<f32>,
    allow_single_column_tables: bool,
    ocr_inline_images: bool,
}

unsafe impl IntoValueFromNative for PdfConfig {}

impl magnus::TryConvert for PdfConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PdfConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PdfConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<PdfConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PdfConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PdfConfig {}

impl Default for PdfConfig {
    fn default() -> Self {
        kreuzberg::PdfConfig::default().into()
    }
}

impl PdfConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            extract_images: kwargs
                .get(ruby.to_symbol("extract_images"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            extract_tables: kwargs
                .get(ruby.to_symbol("extract_tables"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            passwords: kwargs
                .get(ruby.to_symbol("passwords"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            extract_metadata: kwargs
                .get(ruby.to_symbol("extract_metadata"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            hierarchy: kwargs
                .get(ruby.to_symbol("hierarchy"))
                .and_then(|v| HierarchyConfig::try_convert(v).ok()),
            extract_annotations: kwargs
                .get(ruby.to_symbol("extract_annotations"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            top_margin_fraction: kwargs
                .get(ruby.to_symbol("top_margin_fraction"))
                .and_then(|v| f32::try_convert(v).ok()),
            bottom_margin_fraction: kwargs
                .get(ruby.to_symbol("bottom_margin_fraction"))
                .and_then(|v| f32::try_convert(v).ok()),
            allow_single_column_tables: kwargs
                .get(ruby.to_symbol("allow_single_column_tables"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            ocr_inline_images: kwargs
                .get(ruby.to_symbol("ocr_inline_images"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn extract_images(&self) -> bool {
        self.extract_images
    }

    fn extract_tables(&self) -> bool {
        self.extract_tables
    }

    fn passwords(&self) -> Option<Vec<String>> {
        self.passwords.clone()
    }

    fn extract_metadata(&self) -> bool {
        self.extract_metadata
    }

    fn hierarchy(&self) -> Option<HierarchyConfig> {
        self.hierarchy.clone()
    }

    fn extract_annotations(&self) -> bool {
        self.extract_annotations
    }

    fn top_margin_fraction(&self) -> Option<f32> {
        self.top_margin_fraction
    }

    fn bottom_margin_fraction(&self) -> Option<f32> {
        self.bottom_margin_fraction
    }

    fn allow_single_column_tables(&self) -> bool {
        self.allow_single_column_tables
    }

    fn ocr_inline_images(&self) -> bool {
        self.ocr_inline_images
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::HierarchyConfig")]
pub struct HierarchyConfig {
    enabled: bool,
    k_clusters: usize,
    include_bbox: bool,
    ocr_coverage_threshold: Option<f32>,
}

unsafe impl IntoValueFromNative for HierarchyConfig {}

impl magnus::TryConvert for HierarchyConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HierarchyConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HierarchyConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<HierarchyConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HierarchyConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HierarchyConfig {}

impl Default for HierarchyConfig {
    fn default() -> Self {
        kreuzberg::HierarchyConfig::default().into()
    }
}

impl HierarchyConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            enabled: kwargs
                .get(ruby.to_symbol("enabled"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            k_clusters: kwargs
                .get(ruby.to_symbol("k_clusters"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(3),
            include_bbox: kwargs
                .get(ruby.to_symbol("include_bbox"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            ocr_coverage_threshold: kwargs
                .get(ruby.to_symbol("ocr_coverage_threshold"))
                .and_then(|v| f32::try_convert(v).ok()),
        })
    }

    fn enabled(&self) -> bool {
        self.enabled
    }

    fn k_clusters(&self) -> usize {
        self.k_clusters
    }

    fn include_bbox(&self) -> bool {
        self.include_bbox
    }

    fn ocr_coverage_threshold(&self) -> Option<f32> {
        self.ocr_coverage_threshold
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PostProcessorConfig")]
pub struct PostProcessorConfig {
    enabled: bool,
    enabled_processors: Option<Vec<String>>,
    disabled_processors: Option<Vec<String>>,
    enabled_set: Option<Vec<String>>,
    disabled_set: Option<Vec<String>>,
}

unsafe impl IntoValueFromNative for PostProcessorConfig {}

impl magnus::TryConvert for PostProcessorConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PostProcessorConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PostProcessorConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<PostProcessorConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PostProcessorConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PostProcessorConfig {}

impl Default for PostProcessorConfig {
    fn default() -> Self {
        kreuzberg::PostProcessorConfig::default().into()
    }
}

impl PostProcessorConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            enabled: kwargs
                .get(ruby.to_symbol("enabled"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            enabled_processors: kwargs
                .get(ruby.to_symbol("enabled_processors"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            disabled_processors: kwargs
                .get(ruby.to_symbol("disabled_processors"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            enabled_set: kwargs
                .get(ruby.to_symbol("enabled_set"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            disabled_set: kwargs
                .get(ruby.to_symbol("disabled_set"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
        })
    }

    fn enabled(&self) -> bool {
        self.enabled
    }

    fn enabled_processors(&self) -> Option<Vec<String>> {
        self.enabled_processors.clone()
    }

    fn disabled_processors(&self) -> Option<Vec<String>> {
        self.disabled_processors.clone()
    }

    fn enabled_set(&self) -> Option<Vec<String>> {
        self.enabled_set.clone()
    }

    fn disabled_set(&self) -> Option<Vec<String>> {
        self.disabled_set.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ChunkingConfig")]
pub struct ChunkingConfig {
    max_characters: usize,
    overlap: usize,
    trim: bool,
    chunker_type: ChunkerType,
    embedding: Option<EmbeddingConfig>,
    preset: Option<String>,
    sizing: ChunkSizing,
    prepend_heading_context: bool,
    topic_threshold: Option<f32>,
}

unsafe impl IntoValueFromNative for ChunkingConfig {}

impl magnus::TryConvert for ChunkingConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ChunkingConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ChunkingConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<ChunkingConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ChunkingConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ChunkingConfig {}

impl Default for ChunkingConfig {
    fn default() -> Self {
        Self {
            max_characters: 1000,
            overlap: 200,
            trim: true,
            chunker_type: ChunkerType::Text,
            embedding: None,
            preset: None,
            sizing: Default::default(),
            prepend_heading_context: false,
            topic_threshold: None,
        }
    }
}

impl ChunkingConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            max_characters: kwargs
                .get(ruby.to_symbol("max_characters"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(1000),
            overlap: kwargs
                .get(ruby.to_symbol("overlap"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(200),
            trim: kwargs
                .get(ruby.to_symbol("trim"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            chunker_type: kwargs
                .get(ruby.to_symbol("chunker_type"))
                .and_then(|v| ChunkerType::try_convert(v).ok())
                .unwrap_or(ChunkerType::Text),
            embedding: kwargs
                .get(ruby.to_symbol("embedding"))
                .and_then(|v| EmbeddingConfig::try_convert(v).ok()),
            preset: kwargs
                .get(ruby.to_symbol("preset"))
                .and_then(|v| String::try_convert(v).ok()),
            sizing: kwargs
                .get(ruby.to_symbol("sizing"))
                .and_then(|v| ChunkSizing::try_convert(v).ok())
                .unwrap_or_default(),
            prepend_heading_context: kwargs
                .get(ruby.to_symbol("prepend_heading_context"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            topic_threshold: kwargs
                .get(ruby.to_symbol("topic_threshold"))
                .and_then(|v| f32::try_convert(v).ok()),
        })
    }

    fn max_characters(&self) -> usize {
        self.max_characters
    }

    fn overlap(&self) -> usize {
        self.overlap
    }

    fn trim(&self) -> bool {
        self.trim
    }

    fn chunker_type(&self) -> ChunkerType {
        self.chunker_type.clone()
    }

    fn embedding(&self) -> Option<EmbeddingConfig> {
        self.embedding.clone()
    }

    fn preset(&self) -> Option<String> {
        self.preset.clone()
    }

    fn sizing(&self) -> ChunkSizing {
        self.sizing.clone()
    }

    fn prepend_heading_context(&self) -> bool {
        self.prepend_heading_context
    }

    fn topic_threshold(&self) -> Option<f32> {
        self.topic_threshold
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::EmbeddingConfig")]
pub struct EmbeddingConfig {
    model: EmbeddingModelType,
    normalize: bool,
    batch_size: usize,
    show_download_progress: bool,
    cache_dir: Option<String>,
    acceleration: Option<AccelerationConfig>,
    max_embed_duration_secs: Option<u64>,
}

unsafe impl IntoValueFromNative for EmbeddingConfig {}

impl magnus::TryConvert for EmbeddingConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmbeddingConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmbeddingConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmbeddingConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmbeddingConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmbeddingConfig {}

impl Default for EmbeddingConfig {
    fn default() -> Self {
        Self {
            model: Default::default(),
            normalize: true,
            batch_size: 32,
            show_download_progress: false,
            cache_dir: None,
            acceleration: None,
            max_embed_duration_secs: None,
        }
    }
}

impl EmbeddingConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            model: kwargs
                .get(ruby.to_symbol("model"))
                .and_then(|v| EmbeddingModelType::try_convert(v).ok())
                .unwrap_or_default(),
            normalize: kwargs
                .get(ruby.to_symbol("normalize"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            batch_size: kwargs
                .get(ruby.to_symbol("batch_size"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(32),
            show_download_progress: kwargs
                .get(ruby.to_symbol("show_download_progress"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            cache_dir: kwargs
                .get(ruby.to_symbol("cache_dir"))
                .and_then(|v| String::try_convert(v).ok()),
            acceleration: kwargs
                .get(ruby.to_symbol("acceleration"))
                .and_then(|v| AccelerationConfig::try_convert(v).ok()),
            max_embed_duration_secs: kwargs
                .get(ruby.to_symbol("max_embed_duration_secs"))
                .and_then(|v| u64::try_convert(v).ok()),
        })
    }

    fn model(&self) -> EmbeddingModelType {
        self.model.clone()
    }

    fn normalize(&self) -> bool {
        self.normalize
    }

    fn batch_size(&self) -> usize {
        self.batch_size
    }

    fn show_download_progress(&self) -> bool {
        self.show_download_progress
    }

    fn cache_dir(&self) -> Option<String> {
        self.cache_dir.clone()
    }

    fn acceleration(&self) -> Option<AccelerationConfig> {
        self.acceleration.clone()
    }

    fn max_embed_duration_secs(&self) -> Option<u64> {
        self.max_embed_duration_secs
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TreeSitterConfig")]
pub struct TreeSitterConfig {
    enabled: bool,
    cache_dir: Option<String>,
    languages: Option<Vec<String>>,
    groups: Option<Vec<String>>,
    process: TreeSitterProcessConfig,
}

unsafe impl IntoValueFromNative for TreeSitterConfig {}

impl magnus::TryConvert for TreeSitterConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TreeSitterConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TreeSitterConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<TreeSitterConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TreeSitterConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TreeSitterConfig {}

impl Default for TreeSitterConfig {
    fn default() -> Self {
        kreuzberg::TreeSitterConfig::default().into()
    }
}

impl TreeSitterConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            enabled: kwargs
                .get(ruby.to_symbol("enabled"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            cache_dir: kwargs
                .get(ruby.to_symbol("cache_dir"))
                .and_then(|v| String::try_convert(v).ok()),
            languages: kwargs
                .get(ruby.to_symbol("languages"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            groups: kwargs
                .get(ruby.to_symbol("groups"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            process: kwargs
                .get(ruby.to_symbol("process"))
                .and_then(|v| TreeSitterProcessConfig::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn enabled(&self) -> bool {
        self.enabled
    }

    fn cache_dir(&self) -> Option<String> {
        self.cache_dir.clone()
    }

    fn languages(&self) -> Option<Vec<String>> {
        self.languages.clone()
    }

    fn groups(&self) -> Option<Vec<String>> {
        self.groups.clone()
    }

    fn process(&self) -> TreeSitterProcessConfig {
        self.process.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TreeSitterProcessConfig")]
pub struct TreeSitterProcessConfig {
    structure: bool,
    imports: bool,
    exports: bool,
    comments: bool,
    docstrings: bool,
    symbols: bool,
    diagnostics: bool,
    chunk_max_size: Option<usize>,
    content_mode: CodeContentMode,
}

unsafe impl IntoValueFromNative for TreeSitterProcessConfig {}

impl magnus::TryConvert for TreeSitterProcessConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TreeSitterProcessConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TreeSitterProcessConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<TreeSitterProcessConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TreeSitterProcessConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TreeSitterProcessConfig {}

impl Default for TreeSitterProcessConfig {
    fn default() -> Self {
        Self {
            structure: true,
            imports: true,
            exports: true,
            comments: false,
            docstrings: false,
            symbols: false,
            diagnostics: false,
            chunk_max_size: None,
            content_mode: Default::default(),
        }
    }
}

impl TreeSitterProcessConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            structure: kwargs
                .get(ruby.to_symbol("structure"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            imports: kwargs
                .get(ruby.to_symbol("imports"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            exports: kwargs
                .get(ruby.to_symbol("exports"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            comments: kwargs
                .get(ruby.to_symbol("comments"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            docstrings: kwargs
                .get(ruby.to_symbol("docstrings"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            symbols: kwargs
                .get(ruby.to_symbol("symbols"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            diagnostics: kwargs
                .get(ruby.to_symbol("diagnostics"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            chunk_max_size: kwargs
                .get(ruby.to_symbol("chunk_max_size"))
                .and_then(|v| usize::try_convert(v).ok()),
            content_mode: kwargs
                .get(ruby.to_symbol("content_mode"))
                .and_then(|v| CodeContentMode::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn structure(&self) -> bool {
        self.structure
    }

    fn imports(&self) -> bool {
        self.imports
    }

    fn exports(&self) -> bool {
        self.exports
    }

    fn comments(&self) -> bool {
        self.comments
    }

    fn docstrings(&self) -> bool {
        self.docstrings
    }

    fn symbols(&self) -> bool {
        self.symbols
    }

    fn diagnostics(&self) -> bool {
        self.diagnostics
    }

    fn chunk_max_size(&self) -> Option<usize> {
        self.chunk_max_size
    }

    fn content_mode(&self) -> CodeContentMode {
        self.content_mode.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::SupportedFormat")]
pub struct SupportedFormat {
    extension: String,
    mime_type: String,
}

unsafe impl IntoValueFromNative for SupportedFormat {}

impl magnus::TryConvert for SupportedFormat {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&SupportedFormat as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into SupportedFormat: {}", e),
                )
            })?
        };
        serde_json::from_str::<SupportedFormat>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize SupportedFormat: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for SupportedFormat {}

impl SupportedFormat {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            extension: kwargs
                .get(ruby.to_symbol("extension"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ServerConfig")]
pub struct ServerConfig {
    host: String,
    port: u16,
    cors_origins: Vec<String>,
    max_request_body_bytes: usize,
    max_multipart_field_bytes: usize,
}

unsafe impl IntoValueFromNative for ServerConfig {}

impl magnus::TryConvert for ServerConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ServerConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ServerConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<ServerConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ServerConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ServerConfig {}

impl Default for ServerConfig {
    fn default() -> Self {
        kreuzberg::ServerConfig::default().into()
    }
}

impl ServerConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            host: kwargs
                .get(ruby.to_symbol("host"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            port: kwargs
                .get(ruby.to_symbol("port"))
                .and_then(|v| u16::try_convert(v).ok())
                .unwrap_or_default(),
            cors_origins: kwargs
                .get(ruby.to_symbol("cors_origins"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            max_request_body_bytes: kwargs
                .get(ruby.to_symbol("max_request_body_bytes"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            max_multipart_field_bytes: kwargs
                .get(ruby.to_symbol("max_multipart_field_bytes"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn port(&self) -> u16 {
        self.port
    }

    fn cors_origins(&self) -> Vec<String> {
        self.cors_origins.clone()
    }

    fn max_request_body_bytes(&self) -> usize {
        self.max_request_body_bytes
    }

    fn max_multipart_field_bytes(&self) -> usize {
        self.max_multipart_field_bytes
    }

    fn listen_addr(&self) -> String {
        let core_self = kreuzberg::ServerConfig {
            host: self.host.clone(),

            port: self.port,

            cors_origins: self.cors_origins.clone(),

            max_request_body_bytes: self.max_request_body_bytes,

            max_multipart_field_bytes: self.max_multipart_field_bytes,
        };
        core_self.listen_addr().into()
    }

    fn cors_allows_all(&self) -> bool {
        let core_self = kreuzberg::ServerConfig {
            host: self.host.clone(),

            port: self.port,

            cors_origins: self.cors_origins.clone(),

            max_request_body_bytes: self.max_request_body_bytes,

            max_multipart_field_bytes: self.max_multipart_field_bytes,
        };
        core_self.cors_allows_all()
    }

    fn is_origin_allowed(&self, origin: String) -> bool {
        let core_self = kreuzberg::ServerConfig {
            host: self.host.clone(),

            port: self.port,

            cors_origins: self.cors_origins.clone(),

            max_request_body_bytes: self.max_request_body_bytes,

            max_multipart_field_bytes: self.max_multipart_field_bytes,
        };
        core_self.is_origin_allowed(&origin)
    }

    fn max_request_body_mb(&self) -> usize {
        let core_self = kreuzberg::ServerConfig {
            host: self.host.clone(),

            port: self.port,

            cors_origins: self.cors_origins.clone(),

            max_request_body_bytes: self.max_request_body_bytes,

            max_multipart_field_bytes: self.max_multipart_field_bytes,
        };
        core_self.max_request_body_mb()
    }

    fn max_multipart_field_mb(&self) -> usize {
        let core_self = kreuzberg::ServerConfig {
            host: self.host.clone(),

            port: self.port,

            cors_origins: self.cors_origins.clone(),

            max_request_body_bytes: self.max_request_body_bytes,

            max_multipart_field_bytes: self.max_multipart_field_bytes,
        };
        core_self.max_multipart_field_mb()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::StructuredDataResult")]
pub struct StructuredDataResult {
    content: String,
    format: String,
    metadata: HashMap<String, String>,
    text_fields: Vec<String>,
}

unsafe impl IntoValueFromNative for StructuredDataResult {}

impl magnus::TryConvert for StructuredDataResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&StructuredDataResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into StructuredDataResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<StructuredDataResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize StructuredDataResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for StructuredDataResult {}

impl StructuredDataResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            format: kwargs
                .get(ruby.to_symbol("format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            text_fields: kwargs
                .get(ruby.to_symbol("text_fields"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

    fn metadata(&self) -> HashMap<String, String> {
        self.metadata.clone()
    }

    fn text_fields(&self) -> Vec<String> {
        self.text_fields.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::DocxAppProperties")]
pub struct DocxAppProperties {
    application: Option<String>,
    app_version: Option<String>,
    template: Option<String>,
    total_time: Option<i32>,
    pages: Option<i32>,
    words: Option<i32>,
    characters: Option<i32>,
    characters_with_spaces: Option<i32>,
    lines: Option<i32>,
    paragraphs: Option<i32>,
    company: Option<String>,
    doc_security: Option<i32>,
    scale_crop: Option<bool>,
    links_up_to_date: Option<bool>,
    shared_doc: Option<bool>,
    hyperlinks_changed: Option<bool>,
}

unsafe impl IntoValueFromNative for DocxAppProperties {}

impl magnus::TryConvert for DocxAppProperties {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DocxAppProperties as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DocxAppProperties: {}", e),
                )
            })?
        };
        serde_json::from_str::<DocxAppProperties>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DocxAppProperties: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DocxAppProperties {}

impl Default for DocxAppProperties {
    fn default() -> Self {
        kreuzberg::DocxAppProperties::default().into()
    }
}

impl DocxAppProperties {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            application: kwargs
                .get(ruby.to_symbol("application"))
                .and_then(|v| String::try_convert(v).ok()),
            app_version: kwargs
                .get(ruby.to_symbol("app_version"))
                .and_then(|v| String::try_convert(v).ok()),
            template: kwargs
                .get(ruby.to_symbol("template"))
                .and_then(|v| String::try_convert(v).ok()),
            total_time: kwargs
                .get(ruby.to_symbol("total_time"))
                .and_then(|v| i32::try_convert(v).ok()),
            pages: kwargs
                .get(ruby.to_symbol("pages"))
                .and_then(|v| i32::try_convert(v).ok()),
            words: kwargs
                .get(ruby.to_symbol("words"))
                .and_then(|v| i32::try_convert(v).ok()),
            characters: kwargs
                .get(ruby.to_symbol("characters"))
                .and_then(|v| i32::try_convert(v).ok()),
            characters_with_spaces: kwargs
                .get(ruby.to_symbol("characters_with_spaces"))
                .and_then(|v| i32::try_convert(v).ok()),
            lines: kwargs
                .get(ruby.to_symbol("lines"))
                .and_then(|v| i32::try_convert(v).ok()),
            paragraphs: kwargs
                .get(ruby.to_symbol("paragraphs"))
                .and_then(|v| i32::try_convert(v).ok()),
            company: kwargs
                .get(ruby.to_symbol("company"))
                .and_then(|v| String::try_convert(v).ok()),
            doc_security: kwargs
                .get(ruby.to_symbol("doc_security"))
                .and_then(|v| i32::try_convert(v).ok()),
            scale_crop: kwargs
                .get(ruby.to_symbol("scale_crop"))
                .and_then(|v| bool::try_convert(v).ok()),
            links_up_to_date: kwargs
                .get(ruby.to_symbol("links_up_to_date"))
                .and_then(|v| bool::try_convert(v).ok()),
            shared_doc: kwargs
                .get(ruby.to_symbol("shared_doc"))
                .and_then(|v| bool::try_convert(v).ok()),
            hyperlinks_changed: kwargs
                .get(ruby.to_symbol("hyperlinks_changed"))
                .and_then(|v| bool::try_convert(v).ok()),
        })
    }

    fn application(&self) -> Option<String> {
        self.application.clone()
    }

    fn app_version(&self) -> Option<String> {
        self.app_version.clone()
    }

    fn template(&self) -> Option<String> {
        self.template.clone()
    }

    fn total_time(&self) -> Option<i32> {
        self.total_time
    }

    fn pages(&self) -> Option<i32> {
        self.pages
    }

    fn words(&self) -> Option<i32> {
        self.words
    }

    fn characters(&self) -> Option<i32> {
        self.characters
    }

    fn characters_with_spaces(&self) -> Option<i32> {
        self.characters_with_spaces
    }

    fn lines(&self) -> Option<i32> {
        self.lines
    }

    fn paragraphs(&self) -> Option<i32> {
        self.paragraphs
    }

    fn company(&self) -> Option<String> {
        self.company.clone()
    }

    fn doc_security(&self) -> Option<i32> {
        self.doc_security
    }

    fn scale_crop(&self) -> Option<bool> {
        self.scale_crop
    }

    fn links_up_to_date(&self) -> Option<bool> {
        self.links_up_to_date
    }

    fn shared_doc(&self) -> Option<bool> {
        self.shared_doc
    }

    fn hyperlinks_changed(&self) -> Option<bool> {
        self.hyperlinks_changed
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::XlsxAppProperties")]
pub struct XlsxAppProperties {
    application: Option<String>,
    app_version: Option<String>,
    doc_security: Option<i32>,
    scale_crop: Option<bool>,
    links_up_to_date: Option<bool>,
    shared_doc: Option<bool>,
    hyperlinks_changed: Option<bool>,
    company: Option<String>,
    worksheet_names: Vec<String>,
}

unsafe impl IntoValueFromNative for XlsxAppProperties {}

impl magnus::TryConvert for XlsxAppProperties {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&XlsxAppProperties as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into XlsxAppProperties: {}", e),
                )
            })?
        };
        serde_json::from_str::<XlsxAppProperties>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize XlsxAppProperties: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for XlsxAppProperties {}

impl Default for XlsxAppProperties {
    fn default() -> Self {
        kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties::default().into()
    }
}

impl XlsxAppProperties {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            application: kwargs
                .get(ruby.to_symbol("application"))
                .and_then(|v| String::try_convert(v).ok()),
            app_version: kwargs
                .get(ruby.to_symbol("app_version"))
                .and_then(|v| String::try_convert(v).ok()),
            doc_security: kwargs
                .get(ruby.to_symbol("doc_security"))
                .and_then(|v| i32::try_convert(v).ok()),
            scale_crop: kwargs
                .get(ruby.to_symbol("scale_crop"))
                .and_then(|v| bool::try_convert(v).ok()),
            links_up_to_date: kwargs
                .get(ruby.to_symbol("links_up_to_date"))
                .and_then(|v| bool::try_convert(v).ok()),
            shared_doc: kwargs
                .get(ruby.to_symbol("shared_doc"))
                .and_then(|v| bool::try_convert(v).ok()),
            hyperlinks_changed: kwargs
                .get(ruby.to_symbol("hyperlinks_changed"))
                .and_then(|v| bool::try_convert(v).ok()),
            company: kwargs
                .get(ruby.to_symbol("company"))
                .and_then(|v| String::try_convert(v).ok()),
            worksheet_names: kwargs
                .get(ruby.to_symbol("worksheet_names"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn application(&self) -> Option<String> {
        self.application.clone()
    }

    fn app_version(&self) -> Option<String> {
        self.app_version.clone()
    }

    fn doc_security(&self) -> Option<i32> {
        self.doc_security
    }

    fn scale_crop(&self) -> Option<bool> {
        self.scale_crop
    }

    fn links_up_to_date(&self) -> Option<bool> {
        self.links_up_to_date
    }

    fn shared_doc(&self) -> Option<bool> {
        self.shared_doc
    }

    fn hyperlinks_changed(&self) -> Option<bool> {
        self.hyperlinks_changed
    }

    fn company(&self) -> Option<String> {
        self.company.clone()
    }

    fn worksheet_names(&self) -> Vec<String> {
        self.worksheet_names.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PptxAppProperties")]
pub struct PptxAppProperties {
    application: Option<String>,
    app_version: Option<String>,
    total_time: Option<i32>,
    company: Option<String>,
    doc_security: Option<i32>,
    scale_crop: Option<bool>,
    links_up_to_date: Option<bool>,
    shared_doc: Option<bool>,
    hyperlinks_changed: Option<bool>,
    slides: Option<i32>,
    notes: Option<i32>,
    hidden_slides: Option<i32>,
    multimedia_clips: Option<i32>,
    presentation_format: Option<String>,
    slide_titles: Vec<String>,
}

unsafe impl IntoValueFromNative for PptxAppProperties {}

impl magnus::TryConvert for PptxAppProperties {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PptxAppProperties as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PptxAppProperties: {}", e),
                )
            })?
        };
        serde_json::from_str::<PptxAppProperties>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PptxAppProperties: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PptxAppProperties {}

impl Default for PptxAppProperties {
    fn default() -> Self {
        kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties::default().into()
    }
}

impl PptxAppProperties {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            application: kwargs
                .get(ruby.to_symbol("application"))
                .and_then(|v| String::try_convert(v).ok()),
            app_version: kwargs
                .get(ruby.to_symbol("app_version"))
                .and_then(|v| String::try_convert(v).ok()),
            total_time: kwargs
                .get(ruby.to_symbol("total_time"))
                .and_then(|v| i32::try_convert(v).ok()),
            company: kwargs
                .get(ruby.to_symbol("company"))
                .and_then(|v| String::try_convert(v).ok()),
            doc_security: kwargs
                .get(ruby.to_symbol("doc_security"))
                .and_then(|v| i32::try_convert(v).ok()),
            scale_crop: kwargs
                .get(ruby.to_symbol("scale_crop"))
                .and_then(|v| bool::try_convert(v).ok()),
            links_up_to_date: kwargs
                .get(ruby.to_symbol("links_up_to_date"))
                .and_then(|v| bool::try_convert(v).ok()),
            shared_doc: kwargs
                .get(ruby.to_symbol("shared_doc"))
                .and_then(|v| bool::try_convert(v).ok()),
            hyperlinks_changed: kwargs
                .get(ruby.to_symbol("hyperlinks_changed"))
                .and_then(|v| bool::try_convert(v).ok()),
            slides: kwargs
                .get(ruby.to_symbol("slides"))
                .and_then(|v| i32::try_convert(v).ok()),
            notes: kwargs
                .get(ruby.to_symbol("notes"))
                .and_then(|v| i32::try_convert(v).ok()),
            hidden_slides: kwargs
                .get(ruby.to_symbol("hidden_slides"))
                .and_then(|v| i32::try_convert(v).ok()),
            multimedia_clips: kwargs
                .get(ruby.to_symbol("multimedia_clips"))
                .and_then(|v| i32::try_convert(v).ok()),
            presentation_format: kwargs
                .get(ruby.to_symbol("presentation_format"))
                .and_then(|v| String::try_convert(v).ok()),
            slide_titles: kwargs
                .get(ruby.to_symbol("slide_titles"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn application(&self) -> Option<String> {
        self.application.clone()
    }

    fn app_version(&self) -> Option<String> {
        self.app_version.clone()
    }

    fn total_time(&self) -> Option<i32> {
        self.total_time
    }

    fn company(&self) -> Option<String> {
        self.company.clone()
    }

    fn doc_security(&self) -> Option<i32> {
        self.doc_security
    }

    fn scale_crop(&self) -> Option<bool> {
        self.scale_crop
    }

    fn links_up_to_date(&self) -> Option<bool> {
        self.links_up_to_date
    }

    fn shared_doc(&self) -> Option<bool> {
        self.shared_doc
    }

    fn hyperlinks_changed(&self) -> Option<bool> {
        self.hyperlinks_changed
    }

    fn slides(&self) -> Option<i32> {
        self.slides
    }

    fn notes(&self) -> Option<i32> {
        self.notes
    }

    fn hidden_slides(&self) -> Option<i32> {
        self.hidden_slides
    }

    fn multimedia_clips(&self) -> Option<i32> {
        self.multimedia_clips
    }

    fn presentation_format(&self) -> Option<String> {
        self.presentation_format.clone()
    }

    fn slide_titles(&self) -> Vec<String> {
        self.slide_titles.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::CoreProperties")]
pub struct CoreProperties {
    title: Option<String>,
    subject: Option<String>,
    creator: Option<String>,
    keywords: Option<String>,
    description: Option<String>,
    last_modified_by: Option<String>,
    revision: Option<String>,
    created: Option<String>,
    modified: Option<String>,
    category: Option<String>,
    content_status: Option<String>,
    language: Option<String>,
    identifier: Option<String>,
    version: Option<String>,
    last_printed: Option<String>,
}

unsafe impl IntoValueFromNative for CoreProperties {}

impl magnus::TryConvert for CoreProperties {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&CoreProperties as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into CoreProperties: {}", e),
                )
            })?
        };
        serde_json::from_str::<CoreProperties>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize CoreProperties: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for CoreProperties {}

impl Default for CoreProperties {
    fn default() -> Self {
        kreuzberg::CoreProperties::default().into()
    }
}

impl CoreProperties {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            subject: kwargs
                .get(ruby.to_symbol("subject"))
                .and_then(|v| String::try_convert(v).ok()),
            creator: kwargs
                .get(ruby.to_symbol("creator"))
                .and_then(|v| String::try_convert(v).ok()),
            keywords: kwargs
                .get(ruby.to_symbol("keywords"))
                .and_then(|v| String::try_convert(v).ok()),
            description: kwargs
                .get(ruby.to_symbol("description"))
                .and_then(|v| String::try_convert(v).ok()),
            last_modified_by: kwargs
                .get(ruby.to_symbol("last_modified_by"))
                .and_then(|v| String::try_convert(v).ok()),
            revision: kwargs
                .get(ruby.to_symbol("revision"))
                .and_then(|v| String::try_convert(v).ok()),
            created: kwargs
                .get(ruby.to_symbol("created"))
                .and_then(|v| String::try_convert(v).ok()),
            modified: kwargs
                .get(ruby.to_symbol("modified"))
                .and_then(|v| String::try_convert(v).ok()),
            category: kwargs
                .get(ruby.to_symbol("category"))
                .and_then(|v| String::try_convert(v).ok()),
            content_status: kwargs
                .get(ruby.to_symbol("content_status"))
                .and_then(|v| String::try_convert(v).ok()),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok()),
            identifier: kwargs
                .get(ruby.to_symbol("identifier"))
                .and_then(|v| String::try_convert(v).ok()),
            version: kwargs
                .get(ruby.to_symbol("version"))
                .and_then(|v| String::try_convert(v).ok()),
            last_printed: kwargs
                .get(ruby.to_symbol("last_printed"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn subject(&self) -> Option<String> {
        self.subject.clone()
    }

    fn creator(&self) -> Option<String> {
        self.creator.clone()
    }

    fn keywords(&self) -> Option<String> {
        self.keywords.clone()
    }

    fn description(&self) -> Option<String> {
        self.description.clone()
    }

    fn last_modified_by(&self) -> Option<String> {
        self.last_modified_by.clone()
    }

    fn revision(&self) -> Option<String> {
        self.revision.clone()
    }

    fn created(&self) -> Option<String> {
        self.created.clone()
    }

    fn modified(&self) -> Option<String> {
        self.modified.clone()
    }

    fn category(&self) -> Option<String> {
        self.category.clone()
    }

    fn content_status(&self) -> Option<String> {
        self.content_status.clone()
    }

    fn language(&self) -> Option<String> {
        self.language.clone()
    }

    fn identifier(&self) -> Option<String> {
        self.identifier.clone()
    }

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

    fn last_printed(&self) -> Option<String> {
        self.last_printed.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::SecurityLimits")]
pub struct SecurityLimits {
    max_archive_size: usize,
    max_compression_ratio: usize,
    max_files_in_archive: usize,
    max_nesting_depth: usize,
    max_entity_length: usize,
    max_content_size: usize,
    max_iterations: usize,
    max_xml_depth: usize,
    max_table_cells: usize,
}

unsafe impl IntoValueFromNative for SecurityLimits {}

impl magnus::TryConvert for SecurityLimits {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&SecurityLimits as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into SecurityLimits: {}", e),
                )
            })?
        };
        serde_json::from_str::<SecurityLimits>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize SecurityLimits: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for SecurityLimits {}

impl Default for SecurityLimits {
    fn default() -> Self {
        kreuzberg::SecurityLimits::default().into()
    }
}

impl SecurityLimits {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            max_archive_size: kwargs
                .get(ruby.to_symbol("max_archive_size"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(524288000),
            max_compression_ratio: kwargs
                .get(ruby.to_symbol("max_compression_ratio"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(100),
            max_files_in_archive: kwargs
                .get(ruby.to_symbol("max_files_in_archive"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(10000),
            max_nesting_depth: kwargs
                .get(ruby.to_symbol("max_nesting_depth"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(1024),
            max_entity_length: kwargs
                .get(ruby.to_symbol("max_entity_length"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(1048576),
            max_content_size: kwargs
                .get(ruby.to_symbol("max_content_size"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(104857600),
            max_iterations: kwargs
                .get(ruby.to_symbol("max_iterations"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(10000000),
            max_xml_depth: kwargs
                .get(ruby.to_symbol("max_xml_depth"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(1024),
            max_table_cells: kwargs
                .get(ruby.to_symbol("max_table_cells"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(100000),
        })
    }

    fn max_archive_size(&self) -> usize {
        self.max_archive_size
    }

    fn max_compression_ratio(&self) -> usize {
        self.max_compression_ratio
    }

    fn max_files_in_archive(&self) -> usize {
        self.max_files_in_archive
    }

    fn max_nesting_depth(&self) -> usize {
        self.max_nesting_depth
    }

    fn max_entity_length(&self) -> usize {
        self.max_entity_length
    }

    fn max_content_size(&self) -> usize {
        self.max_content_size
    }

    fn max_iterations(&self) -> usize {
        self.max_iterations
    }

    fn max_xml_depth(&self) -> usize {
        self.max_xml_depth
    }

    fn max_table_cells(&self) -> usize {
        self.max_table_cells
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TokenReductionConfig")]
pub struct TokenReductionConfig {
    level: ReductionLevel,
    language_hint: Option<String>,
    preserve_markdown: bool,
    preserve_code: bool,
    semantic_threshold: f32,
    enable_parallel: bool,
    use_simd: bool,
    custom_stopwords: Option<HashMap<String, Vec<String>>>,
    preserve_patterns: Vec<String>,
    target_reduction: Option<f32>,
    enable_semantic_clustering: bool,
}

unsafe impl IntoValueFromNative for TokenReductionConfig {}

impl magnus::TryConvert for TokenReductionConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TokenReductionConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TokenReductionConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<TokenReductionConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TokenReductionConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TokenReductionConfig {}

impl Default for TokenReductionConfig {
    fn default() -> Self {
        Self {
            level: ReductionLevel::Moderate,
            language_hint: None,
            preserve_markdown: false,
            preserve_code: true,
            semantic_threshold: 0.3,
            enable_parallel: true,
            use_simd: true,
            custom_stopwords: None,
            preserve_patterns: vec![],
            target_reduction: None,
            enable_semantic_clustering: false,
        }
    }
}

impl TokenReductionConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            level: kwargs
                .get(ruby.to_symbol("level"))
                .and_then(|v| ReductionLevel::try_convert(v).ok())
                .unwrap_or(ReductionLevel::Moderate),
            language_hint: kwargs
                .get(ruby.to_symbol("language_hint"))
                .and_then(|v| String::try_convert(v).ok()),
            preserve_markdown: kwargs
                .get(ruby.to_symbol("preserve_markdown"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            preserve_code: kwargs
                .get(ruby.to_symbol("preserve_code"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            semantic_threshold: kwargs
                .get(ruby.to_symbol("semantic_threshold"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or(0.3),
            enable_parallel: kwargs
                .get(ruby.to_symbol("enable_parallel"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            use_simd: kwargs
                .get(ruby.to_symbol("use_simd"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            custom_stopwords: kwargs
                .get(ruby.to_symbol("custom_stopwords"))
                .and_then(|v| <HashMap<String, Vec<String>>>::try_convert(v).ok()),
            preserve_patterns: kwargs
                .get(ruby.to_symbol("preserve_patterns"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            target_reduction: kwargs
                .get(ruby.to_symbol("target_reduction"))
                .and_then(|v| f32::try_convert(v).ok()),
            enable_semantic_clustering: kwargs
                .get(ruby.to_symbol("enable_semantic_clustering"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn level(&self) -> ReductionLevel {
        self.level.clone()
    }

    fn language_hint(&self) -> Option<String> {
        self.language_hint.clone()
    }

    fn preserve_markdown(&self) -> bool {
        self.preserve_markdown
    }

    fn preserve_code(&self) -> bool {
        self.preserve_code
    }

    fn semantic_threshold(&self) -> f32 {
        self.semantic_threshold
    }

    fn enable_parallel(&self) -> bool {
        self.enable_parallel
    }

    fn use_simd(&self) -> bool {
        self.use_simd
    }

    fn custom_stopwords(&self) -> Option<HashMap<String, Vec<String>>> {
        self.custom_stopwords.clone()
    }

    fn preserve_patterns(&self) -> Vec<String> {
        self.preserve_patterns.clone()
    }

    fn target_reduction(&self) -> Option<f32> {
        self.target_reduction
    }

    fn enable_semantic_clustering(&self) -> bool {
        self.enable_semantic_clustering
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PdfAnnotation")]
pub struct PdfAnnotation {
    annotation_type: PdfAnnotationType,
    content: Option<String>,
    page_number: u32,
    bounding_box: Option<BoundingBox>,
}

unsafe impl IntoValueFromNative for PdfAnnotation {}

impl magnus::TryConvert for PdfAnnotation {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PdfAnnotation as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PdfAnnotation: {}", e),
                )
            })?
        };
        serde_json::from_str::<PdfAnnotation>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PdfAnnotation: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PdfAnnotation {}

impl PdfAnnotation {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            annotation_type: kwargs
                .get(ruby.to_symbol("annotation_type"))
                .and_then(|v| PdfAnnotationType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: annotation_type",
                    )
                })?,
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok()),
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            bounding_box: kwargs
                .get(ruby.to_symbol("bounding_box"))
                .and_then(|v| BoundingBox::try_convert(v).ok()),
        })
    }

    fn annotation_type(&self) -> PdfAnnotationType {
        self.annotation_type.clone()
    }

    fn content(&self) -> Option<String> {
        self.content.clone()
    }

    fn page_number(&self) -> u32 {
        self.page_number
    }

    fn bounding_box(&self) -> Option<BoundingBox> {
        self.bounding_box.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone().unwrap_or_default()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DjotContent")]
pub struct DjotContent {
    plain_text: String,
    blocks: Vec<FormattedBlock>,
    metadata: Metadata,
    tables: Vec<Table>,
    images: Vec<DjotImage>,
    links: Vec<DjotLink>,
    footnotes: Vec<Footnote>,
    attributes: Vec<String>,
}

unsafe impl IntoValueFromNative for DjotContent {}

impl magnus::TryConvert for DjotContent {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DjotContent as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DjotContent: {}", e),
                )
            })?
        };
        serde_json::from_str::<DjotContent>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DjotContent: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DjotContent {}

impl Default for DjotContent {
    fn default() -> Self {
        Self {
            plain_text: String::new(),
            blocks: vec![],
            metadata: Metadata::default(),
            tables: vec![],
            images: vec![],
            links: vec![],
            footnotes: vec![],
            attributes: vec![],
        }
    }
}

impl DjotContent {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            plain_text: kwargs
                .get(ruby.to_symbol("plain_text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            blocks: kwargs
                .get(ruby.to_symbol("blocks"))
                .and_then(|v| <Vec<FormattedBlock>>::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| Metadata::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: metadata",
                    )
                })?,
            tables: kwargs
                .get(ruby.to_symbol("tables"))
                .and_then(|v| <Vec<Table>>::try_convert(v).ok())
                .unwrap_or_default(),
            images: kwargs
                .get(ruby.to_symbol("images"))
                .and_then(|v| <Vec<DjotImage>>::try_convert(v).ok())
                .unwrap_or_default(),
            links: kwargs
                .get(ruby.to_symbol("links"))
                .and_then(|v| <Vec<DjotLink>>::try_convert(v).ok())
                .unwrap_or_default(),
            footnotes: kwargs
                .get(ruby.to_symbol("footnotes"))
                .and_then(|v| <Vec<Footnote>>::try_convert(v).ok())
                .unwrap_or_default(),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or(vec![]),
        })
    }

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

    fn blocks(&self) -> Vec<FormattedBlock> {
        self.blocks.clone()
    }

    fn metadata(&self) -> Metadata {
        self.metadata.clone()
    }

    fn tables(&self) -> Vec<Table> {
        self.tables.clone()
    }

    fn images(&self) -> Vec<DjotImage> {
        self.images.clone()
    }

    fn links(&self) -> Vec<DjotLink> {
        self.links.clone()
    }

    fn footnotes(&self) -> Vec<Footnote> {
        self.footnotes.clone()
    }

    fn attributes(&self) -> Vec<String> {
        self.attributes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::FormattedBlock")]
pub struct FormattedBlock {
    block_type: BlockType,
    level: Option<usize>,
    inline_content: Vec<InlineElement>,
    attributes: Option<String>,
    language: Option<String>,
    code: Option<String>,
    children: Vec<FormattedBlock>,
}

unsafe impl IntoValueFromNative for FormattedBlock {}

impl magnus::TryConvert for FormattedBlock {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&FormattedBlock as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into FormattedBlock: {}", e),
                )
            })?
        };
        serde_json::from_str::<FormattedBlock>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize FormattedBlock: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for FormattedBlock {}

impl Default for FormattedBlock {
    fn default() -> Self {
        Self {
            block_type: BlockType::default(),
            level: None,
            inline_content: vec![],
            attributes: None,
            language: None,
            code: None,
            children: vec![],
        }
    }
}

impl FormattedBlock {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            block_type: kwargs
                .get(ruby.to_symbol("block_type"))
                .and_then(|v| BlockType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: block_type",
                    )
                })?,
            level: kwargs
                .get(ruby.to_symbol("level"))
                .and_then(|v| usize::try_convert(v).ok()),
            inline_content: kwargs
                .get(ruby.to_symbol("inline_content"))
                .and_then(|v| <Vec<InlineElement>>::try_convert(v).ok())
                .unwrap_or_default(),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| String::try_convert(v).ok()),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok()),
            code: kwargs
                .get(ruby.to_symbol("code"))
                .and_then(|v| String::try_convert(v).ok()),
            children: kwargs
                .get(ruby.to_symbol("children"))
                .and_then(|v| <Vec<FormattedBlock>>::try_convert(v).ok())
                .unwrap_or(vec![]),
        })
    }

    fn block_type(&self) -> BlockType {
        self.block_type.clone()
    }

    fn level(&self) -> Option<usize> {
        self.level
    }

    fn inline_content(&self) -> Vec<InlineElement> {
        self.inline_content.clone()
    }

    fn attributes(&self) -> Option<String> {
        self.attributes.clone()
    }

    fn language(&self) -> Option<String> {
        self.language.clone()
    }

    fn code(&self) -> Option<String> {
        self.code.clone()
    }

    fn children(&self) -> Vec<FormattedBlock> {
        self.children.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::InlineElement")]
pub struct InlineElement {
    element_type: InlineType,
    content: String,
    attributes: Option<String>,
    metadata: Option<HashMap<String, String>>,
}

unsafe impl IntoValueFromNative for InlineElement {}

impl magnus::TryConvert for InlineElement {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&InlineElement as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into InlineElement: {}", e),
                )
            })?
        };
        serde_json::from_str::<InlineElement>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize InlineElement: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for InlineElement {}

impl InlineElement {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            element_type: kwargs
                .get(ruby.to_symbol("element_type"))
                .and_then(|v| InlineType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: element_type",
                    )
                })?,
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| String::try_convert(v).ok()),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok()),
        })
    }

    fn element_type(&self) -> InlineType {
        self.element_type.clone()
    }

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

    fn attributes(&self) -> Option<String> {
        self.attributes.clone()
    }

    fn metadata(&self) -> Option<HashMap<String, String>> {
        self.metadata.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DjotImage")]
pub struct DjotImage {
    src: String,
    alt: String,
    title: Option<String>,
    attributes: Option<String>,
}

unsafe impl IntoValueFromNative for DjotImage {}

impl magnus::TryConvert for DjotImage {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DjotImage as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DjotImage: {}", e),
                )
            })?
        };
        serde_json::from_str::<DjotImage>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DjotImage: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DjotImage {}

impl DjotImage {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            src: kwargs
                .get(ruby.to_symbol("src"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            alt: kwargs
                .get(ruby.to_symbol("alt"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

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

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn attributes(&self) -> Option<String> {
        self.attributes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DjotLink")]
pub struct DjotLink {
    url: String,
    text: String,
    title: Option<String>,
    attributes: Option<String>,
}

unsafe impl IntoValueFromNative for DjotLink {}

impl magnus::TryConvert for DjotLink {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DjotLink as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DjotLink: {}", e),
                )
            })?
        };
        serde_json::from_str::<DjotLink>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DjotLink: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DjotLink {}

impl DjotLink {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            url: kwargs
                .get(ruby.to_symbol("url"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

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

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn attributes(&self) -> Option<String> {
        self.attributes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::Footnote")]
pub struct Footnote {
    label: String,
    content: Vec<FormattedBlock>,
}

unsafe impl IntoValueFromNative for Footnote {}

impl magnus::TryConvert for Footnote {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&Footnote as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into Footnote: {}", e),
                )
            })?
        };
        serde_json::from_str::<Footnote>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize Footnote: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for Footnote {}

impl Footnote {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            label: kwargs
                .get(ruby.to_symbol("label"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| <Vec<FormattedBlock>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn content(&self) -> Vec<FormattedBlock> {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::DocumentStructure")]
pub struct DocumentStructure {
    nodes: Vec<DocumentNode>,
    source_format: Option<String>,
    relationships: Vec<DocumentRelationship>,
    node_types: Vec<String>,
}

unsafe impl IntoValueFromNative for DocumentStructure {}

impl magnus::TryConvert for DocumentStructure {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DocumentStructure as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DocumentStructure: {}", e),
                )
            })?
        };
        serde_json::from_str::<DocumentStructure>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DocumentStructure: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DocumentStructure {}

impl Default for DocumentStructure {
    fn default() -> Self {
        Self {
            nodes: vec![],
            source_format: None,
            relationships: vec![],
            node_types: vec![],
        }
    }
}

impl DocumentStructure {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            nodes: kwargs
                .get(ruby.to_symbol("nodes"))
                .and_then(|v| <Vec<DocumentNode>>::try_convert(v).ok())
                .unwrap_or_default(),
            source_format: kwargs
                .get(ruby.to_symbol("source_format"))
                .and_then(|v| String::try_convert(v).ok()),
            relationships: kwargs
                .get(ruby.to_symbol("relationships"))
                .and_then(|v| <Vec<DocumentRelationship>>::try_convert(v).ok())
                .unwrap_or_default(),
            node_types: kwargs
                .get(ruby.to_symbol("node_types"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn nodes(&self) -> Vec<DocumentNode> {
        self.nodes.clone()
    }

    fn source_format(&self) -> Option<String> {
        self.source_format.clone()
    }

    fn relationships(&self) -> Vec<DocumentRelationship> {
        self.relationships.clone()
    }

    fn node_types(&self) -> Vec<String> {
        self.node_types.clone()
    }

    fn finalize_node_types(&mut self) -> () {
        let mut core_self = kreuzberg::DocumentStructure {
            nodes: self.nodes.clone().into_iter().map(Into::into).collect(),

            source_format: self.source_format.clone(),

            relationships: self.relationships.clone().into_iter().map(Into::into).collect(),

            node_types: self.node_types.clone(),
        };
        core_self.finalize_node_types()
    }

    fn is_empty(&self) -> bool {
        let core_self = kreuzberg::DocumentStructure {
            nodes: self.nodes.clone().into_iter().map(Into::into).collect(),

            source_format: self.source_format.clone(),

            relationships: self.relationships.clone().into_iter().map(Into::into).collect(),

            node_types: self.node_types.clone(),
        };
        core_self.is_empty()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DocumentRelationship")]
pub struct DocumentRelationship {
    source: u32,
    target: u32,
    kind: RelationshipKind,
}

unsafe impl IntoValueFromNative for DocumentRelationship {}

impl magnus::TryConvert for DocumentRelationship {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DocumentRelationship as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DocumentRelationship: {}", e),
                )
            })?
        };
        serde_json::from_str::<DocumentRelationship>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DocumentRelationship: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DocumentRelationship {}

impl DocumentRelationship {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            source: kwargs
                .get(ruby.to_symbol("source"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            target: kwargs
                .get(ruby.to_symbol("target"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            kind: kwargs
                .get(ruby.to_symbol("kind"))
                .and_then(|v| RelationshipKind::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: kind",
                    )
                })?,
        })
    }

    fn source(&self) -> u32 {
        self.source
    }

    fn target(&self) -> u32 {
        self.target
    }

    fn kind(&self) -> RelationshipKind {
        self.kind.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DocumentNode")]
pub struct DocumentNode {
    id: String,
    content: NodeContent,
    parent: Option<u32>,
    children: Vec<u32>,
    content_layer: ContentLayer,
    page: Option<u32>,
    page_end: Option<u32>,
    bbox: Option<BoundingBox>,
    annotations: Vec<TextAnnotation>,
    attributes: Option<HashMap<String, String>>,
}

unsafe impl IntoValueFromNative for DocumentNode {}

impl magnus::TryConvert for DocumentNode {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DocumentNode as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DocumentNode: {}", e),
                )
            })?
        };
        serde_json::from_str::<DocumentNode>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DocumentNode: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DocumentNode {}

impl Default for DocumentNode {
    fn default() -> Self {
        Self {
            id: String::new(),
            content: NodeContent::default(),
            parent: None,
            children: vec![],
            content_layer: ContentLayer::default(),
            page: None,
            page_end: None,
            bbox: None,
            annotations: vec![],
            attributes: None,
        }
    }
}

impl DocumentNode {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            id: kwargs
                .get(ruby.to_symbol("id"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| NodeContent::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: content",
                    )
                })?,
            parent: kwargs
                .get(ruby.to_symbol("parent"))
                .and_then(|v| u32::try_convert(v).ok()),
            children: kwargs
                .get(ruby.to_symbol("children"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok())
                .unwrap_or(vec![]),
            content_layer: kwargs
                .get(ruby.to_symbol("content_layer"))
                .and_then(|v| ContentLayer::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: content_layer",
                    )
                })?,
            page: kwargs
                .get(ruby.to_symbol("page"))
                .and_then(|v| u32::try_convert(v).ok()),
            page_end: kwargs
                .get(ruby.to_symbol("page_end"))
                .and_then(|v| u32::try_convert(v).ok()),
            bbox: kwargs
                .get(ruby.to_symbol("bbox"))
                .and_then(|v| BoundingBox::try_convert(v).ok()),
            annotations: kwargs
                .get(ruby.to_symbol("annotations"))
                .and_then(|v| <Vec<TextAnnotation>>::try_convert(v).ok())
                .unwrap_or(vec![]),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok()),
        })
    }

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

    fn content(&self) -> NodeContent {
        self.content.clone()
    }

    fn parent(&self) -> Option<u32> {
        self.parent
    }

    fn children(&self) -> Vec<u32> {
        self.children.clone()
    }

    fn content_layer(&self) -> ContentLayer {
        self.content_layer.clone()
    }

    fn page(&self) -> Option<u32> {
        self.page
    }

    fn page_end(&self) -> Option<u32> {
        self.page_end
    }

    fn bbox(&self) -> Option<BoundingBox> {
        self.bbox.clone()
    }

    fn annotations(&self) -> Vec<TextAnnotation> {
        self.annotations.clone()
    }

    fn attributes(&self) -> Option<HashMap<String, String>> {
        self.attributes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TableGrid")]
pub struct TableGrid {
    rows: u32,
    cols: u32,
    cells: Vec<GridCell>,
}

unsafe impl IntoValueFromNative for TableGrid {}

impl magnus::TryConvert for TableGrid {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TableGrid as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TableGrid: {}", e),
                )
            })?
        };
        serde_json::from_str::<TableGrid>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TableGrid: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TableGrid {}

impl Default for TableGrid {
    fn default() -> Self {
        Self {
            rows: 0,
            cols: 0,
            cells: vec![],
        }
    }
}

impl TableGrid {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            rows: kwargs
                .get(ruby.to_symbol("rows"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            cols: kwargs
                .get(ruby.to_symbol("cols"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            cells: kwargs
                .get(ruby.to_symbol("cells"))
                .and_then(|v| <Vec<GridCell>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn rows(&self) -> u32 {
        self.rows
    }

    fn cols(&self) -> u32 {
        self.cols
    }

    fn cells(&self) -> Vec<GridCell> {
        self.cells.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::GridCell")]
pub struct GridCell {
    content: String,
    row: u32,
    col: u32,
    row_span: u32,
    col_span: u32,
    is_header: bool,
    bbox: Option<BoundingBox>,
}

unsafe impl IntoValueFromNative for GridCell {}

impl magnus::TryConvert for GridCell {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&GridCell as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into GridCell: {}", e),
                )
            })?
        };
        serde_json::from_str::<GridCell>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize GridCell: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for GridCell {}

impl Default for GridCell {
    fn default() -> Self {
        Self {
            content: String::new(),
            row: 0,
            col: 0,
            row_span: 0,
            col_span: 0,
            is_header: false,
            bbox: None,
        }
    }
}

impl GridCell {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            row: kwargs
                .get(ruby.to_symbol("row"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            col: kwargs
                .get(ruby.to_symbol("col"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            row_span: kwargs
                .get(ruby.to_symbol("row_span"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or(0),
            col_span: kwargs
                .get(ruby.to_symbol("col_span"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or(0),
            is_header: kwargs
                .get(ruby.to_symbol("is_header"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            bbox: kwargs
                .get(ruby.to_symbol("bbox"))
                .and_then(|v| BoundingBox::try_convert(v).ok()),
        })
    }

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

    fn row(&self) -> u32 {
        self.row
    }

    fn col(&self) -> u32 {
        self.col
    }

    fn row_span(&self) -> u32 {
        self.row_span
    }

    fn col_span(&self) -> u32 {
        self.col_span
    }

    fn is_header(&self) -> bool {
        self.is_header
    }

    fn bbox(&self) -> Option<BoundingBox> {
        self.bbox.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::TextAnnotation")]
pub struct TextAnnotation {
    start: u32,
    end: u32,
    kind: AnnotationKind,
}

unsafe impl IntoValueFromNative for TextAnnotation {}

impl magnus::TryConvert for TextAnnotation {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TextAnnotation as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TextAnnotation: {}", e),
                )
            })?
        };
        serde_json::from_str::<TextAnnotation>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TextAnnotation: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TextAnnotation {}

impl TextAnnotation {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            start: kwargs
                .get(ruby.to_symbol("start"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            end: kwargs
                .get(ruby.to_symbol("end"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            kind: kwargs
                .get(ruby.to_symbol("kind"))
                .and_then(|v| AnnotationKind::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: kind",
                    )
                })?,
        })
    }

    fn start(&self) -> u32 {
        self.start
    }

    fn end(&self) -> u32 {
        self.end
    }

    fn kind(&self) -> AnnotationKind {
        self.kind.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ExtractionResult")]
pub struct ExtractionResult {
    content: String,
    mime_type: String,
    metadata: Metadata,
    extraction_method: Option<ExtractionMethod>,
    tables: Vec<Table>,
    detected_languages: Option<Vec<String>>,
    chunks: Option<Vec<Chunk>>,
    images: Option<Vec<ExtractedImage>>,
    pages: Option<Vec<PageContent>>,
    elements: Option<Vec<Element>>,
    djot_content: Option<DjotContent>,
    ocr_elements: Option<Vec<OcrElement>>,
    document: Option<DocumentStructure>,
    extracted_keywords: Option<Vec<Keyword>>,
    quality_score: Option<f64>,
    processing_warnings: Vec<ProcessingWarning>,
    annotations: Option<Vec<PdfAnnotation>>,
    children: Option<Vec<ArchiveEntry>>,
    uris: Option<Vec<ExtractedUri>>,
    revisions: Option<Vec<DocumentRevision>>,
    structured_output: Option<String>,
    code_intelligence: Option<String>,
    llm_usage: Option<Vec<LlmUsage>>,
    formatted_content: Option<String>,
    ocr_internal_document: Option<String>,
}

unsafe impl IntoValueFromNative for ExtractionResult {}

impl magnus::TryConvert for ExtractionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExtractionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExtractionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExtractionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExtractionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExtractionResult {}

impl Default for ExtractionResult {
    fn default() -> Self {
        Self {
            content: String::new(),
            mime_type: String::new(),
            metadata: Default::default(),
            extraction_method: None,
            tables: vec![],
            detected_languages: None,
            chunks: None,
            images: None,
            pages: None,
            elements: None,
            djot_content: None,
            ocr_elements: None,
            document: None,
            extracted_keywords: None,
            quality_score: None,
            processing_warnings: vec![],
            annotations: None,
            children: None,
            uris: None,
            revisions: None,
            structured_output: None,
            code_intelligence: None,
            llm_usage: None,
            formatted_content: None,
            ocr_internal_document: None,
        }
    }
}

impl ExtractionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| Metadata::try_convert(v).ok())
                .unwrap_or_default(),
            extraction_method: kwargs
                .get(ruby.to_symbol("extraction_method"))
                .and_then(|v| ExtractionMethod::try_convert(v).ok()),
            tables: kwargs
                .get(ruby.to_symbol("tables"))
                .and_then(|v| <Vec<Table>>::try_convert(v).ok())
                .unwrap_or_default(),
            detected_languages: kwargs
                .get(ruby.to_symbol("detected_languages"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            chunks: kwargs
                .get(ruby.to_symbol("chunks"))
                .and_then(|v| <Vec<Chunk>>::try_convert(v).ok()),
            images: kwargs
                .get(ruby.to_symbol("images"))
                .and_then(|v| <Vec<ExtractedImage>>::try_convert(v).ok()),
            pages: kwargs
                .get(ruby.to_symbol("pages"))
                .and_then(|v| <Vec<PageContent>>::try_convert(v).ok()),
            elements: kwargs
                .get(ruby.to_symbol("elements"))
                .and_then(|v| <Vec<Element>>::try_convert(v).ok()),
            djot_content: kwargs
                .get(ruby.to_symbol("djot_content"))
                .and_then(|v| DjotContent::try_convert(v).ok()),
            ocr_elements: kwargs
                .get(ruby.to_symbol("ocr_elements"))
                .and_then(|v| <Vec<OcrElement>>::try_convert(v).ok()),
            document: kwargs
                .get(ruby.to_symbol("document"))
                .and_then(|v| DocumentStructure::try_convert(v).ok()),
            extracted_keywords: kwargs
                .get(ruby.to_symbol("extracted_keywords"))
                .and_then(|v| <Vec<Keyword>>::try_convert(v).ok()),
            quality_score: kwargs
                .get(ruby.to_symbol("quality_score"))
                .and_then(|v| f64::try_convert(v).ok()),
            processing_warnings: kwargs
                .get(ruby.to_symbol("processing_warnings"))
                .and_then(|v| <Vec<ProcessingWarning>>::try_convert(v).ok())
                .unwrap_or_default(),
            annotations: kwargs
                .get(ruby.to_symbol("annotations"))
                .and_then(|v| <Vec<PdfAnnotation>>::try_convert(v).ok()),
            children: kwargs
                .get(ruby.to_symbol("children"))
                .and_then(|v| <Vec<ArchiveEntry>>::try_convert(v).ok()),
            uris: kwargs
                .get(ruby.to_symbol("uris"))
                .and_then(|v| <Vec<ExtractedUri>>::try_convert(v).ok()),
            revisions: kwargs
                .get(ruby.to_symbol("revisions"))
                .and_then(|v| <Vec<DocumentRevision>>::try_convert(v).ok()),
            structured_output: kwargs
                .get(ruby.to_symbol("structured_output"))
                .and_then(|v| String::try_convert(v).ok()),
            code_intelligence: kwargs
                .get(ruby.to_symbol("code_intelligence"))
                .and_then(|v| String::try_convert(v).ok()),
            llm_usage: kwargs
                .get(ruby.to_symbol("llm_usage"))
                .and_then(|v| <Vec<LlmUsage>>::try_convert(v).ok()),
            formatted_content: kwargs
                .get(ruby.to_symbol("formatted_content"))
                .and_then(|v| String::try_convert(v).ok()),
            ocr_internal_document: kwargs
                .get(ruby.to_symbol("ocr_internal_document"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

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

    fn metadata(&self) -> Metadata {
        self.metadata.clone()
    }

    fn extraction_method(&self) -> Option<ExtractionMethod> {
        self.extraction_method.clone()
    }

    fn tables(&self) -> Vec<Table> {
        self.tables.clone()
    }

    fn detected_languages(&self) -> Option<Vec<String>> {
        self.detected_languages.clone()
    }

    fn chunks(&self) -> Option<Vec<Chunk>> {
        self.chunks.clone()
    }

    fn images(&self) -> Option<Vec<ExtractedImage>> {
        self.images.clone()
    }

    fn pages(&self) -> Option<Vec<PageContent>> {
        self.pages.clone()
    }

    fn elements(&self) -> Option<Vec<Element>> {
        self.elements.clone()
    }

    fn djot_content(&self) -> Option<DjotContent> {
        self.djot_content.clone()
    }

    fn ocr_elements(&self) -> Option<Vec<OcrElement>> {
        self.ocr_elements.clone()
    }

    fn document(&self) -> Option<DocumentStructure> {
        self.document.clone()
    }

    fn extracted_keywords(&self) -> Option<Vec<Keyword>> {
        self.extracted_keywords.clone()
    }

    fn quality_score(&self) -> Option<f64> {
        self.quality_score
    }

    fn processing_warnings(&self) -> Vec<ProcessingWarning> {
        self.processing_warnings.clone()
    }

    fn annotations(&self) -> Option<Vec<PdfAnnotation>> {
        self.annotations.clone()
    }

    fn children(&self) -> Option<Vec<ArchiveEntry>> {
        self.children.clone()
    }

    fn uris(&self) -> Option<Vec<ExtractedUri>> {
        self.uris.clone()
    }

    fn revisions(&self) -> Option<Vec<DocumentRevision>> {
        self.revisions.clone()
    }

    fn structured_output(&self) -> Option<String> {
        self.structured_output.clone()
    }

    fn code_intelligence(&self) -> Option<String> {
        self.code_intelligence.clone()
    }

    fn llm_usage(&self) -> Option<Vec<LlmUsage>> {
        self.llm_usage.clone()
    }

    fn formatted_content(&self) -> Option<String> {
        self.formatted_content.clone()
    }

    fn ocr_internal_document(&self) -> Option<String> {
        self.ocr_internal_document.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ArchiveEntry")]
pub struct ArchiveEntry {
    path: String,
    mime_type: String,
    result: ExtractionResult,
}

unsafe impl IntoValueFromNative for ArchiveEntry {}

impl magnus::TryConvert for ArchiveEntry {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ArchiveEntry as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ArchiveEntry: {}", e),
                )
            })?
        };
        serde_json::from_str::<ArchiveEntry>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ArchiveEntry: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ArchiveEntry {}

impl ArchiveEntry {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            path: kwargs
                .get(ruby.to_symbol("path"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            result: kwargs
                .get(ruby.to_symbol("result"))
                .and_then(|v| ExtractionResult::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: result",
                    )
                })?,
        })
    }

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

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

    fn result(&self) -> ExtractionResult {
        self.result.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ProcessingWarning")]
pub struct ProcessingWarning {
    source: String,
    message: String,
}

unsafe impl IntoValueFromNative for ProcessingWarning {}

impl magnus::TryConvert for ProcessingWarning {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ProcessingWarning as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ProcessingWarning: {}", e),
                )
            })?
        };
        serde_json::from_str::<ProcessingWarning>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ProcessingWarning: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ProcessingWarning {}

impl ProcessingWarning {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            source: kwargs
                .get(ruby.to_symbol("source"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            message: kwargs
                .get(ruby.to_symbol("message"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::LlmUsage")]
pub struct LlmUsage {
    model: String,
    source: String,
    input_tokens: Option<u64>,
    output_tokens: Option<u64>,
    total_tokens: Option<u64>,
    estimated_cost: Option<f64>,
    finish_reason: Option<String>,
}

unsafe impl IntoValueFromNative for LlmUsage {}

impl magnus::TryConvert for LlmUsage {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LlmUsage as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LlmUsage: {}", e),
                )
            })?
        };
        serde_json::from_str::<LlmUsage>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LlmUsage: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LlmUsage {}

impl Default for LlmUsage {
    fn default() -> Self {
        kreuzberg::LlmUsage::default().into()
    }
}

impl LlmUsage {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            model: kwargs
                .get(ruby.to_symbol("model"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            source: kwargs
                .get(ruby.to_symbol("source"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            input_tokens: kwargs
                .get(ruby.to_symbol("input_tokens"))
                .and_then(|v| u64::try_convert(v).ok()),
            output_tokens: kwargs
                .get(ruby.to_symbol("output_tokens"))
                .and_then(|v| u64::try_convert(v).ok()),
            total_tokens: kwargs
                .get(ruby.to_symbol("total_tokens"))
                .and_then(|v| u64::try_convert(v).ok()),
            estimated_cost: kwargs
                .get(ruby.to_symbol("estimated_cost"))
                .and_then(|v| f64::try_convert(v).ok()),
            finish_reason: kwargs
                .get(ruby.to_symbol("finish_reason"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

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

    fn input_tokens(&self) -> Option<u64> {
        self.input_tokens
    }

    fn output_tokens(&self) -> Option<u64> {
        self.output_tokens
    }

    fn total_tokens(&self) -> Option<u64> {
        self.total_tokens
    }

    fn estimated_cost(&self) -> Option<f64> {
        self.estimated_cost
    }

    fn finish_reason(&self) -> Option<String> {
        self.finish_reason.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::Chunk")]
pub struct Chunk {
    content: String,
    chunk_type: ChunkType,
    embedding: Option<Vec<f32>>,
    metadata: ChunkMetadata,
}

unsafe impl IntoValueFromNative for Chunk {}

impl magnus::TryConvert for Chunk {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&Chunk as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into Chunk: {}", e),
                )
            })?
        };
        serde_json::from_str::<Chunk>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize Chunk: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for Chunk {}

impl Default for Chunk {
    fn default() -> Self {
        Self {
            content: String::new(),
            chunk_type: ChunkType::default(),
            embedding: None,
            metadata: ChunkMetadata::default(),
        }
    }
}

impl Chunk {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            chunk_type: kwargs
                .get(ruby.to_symbol("chunk_type"))
                .and_then(|v| ChunkType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: chunk_type",
                    )
                })?,
            embedding: kwargs
                .get(ruby.to_symbol("embedding"))
                .and_then(|v| <Vec<f32>>::try_convert(v).ok()),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| ChunkMetadata::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: metadata",
                    )
                })?,
        })
    }

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

    fn chunk_type(&self) -> ChunkType {
        self.chunk_type.clone()
    }

    fn embedding(&self) -> Option<Vec<f32>> {
        self.embedding.clone()
    }

    fn metadata(&self) -> ChunkMetadata {
        self.metadata.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::HeadingContext")]
pub struct HeadingContext {
    headings: Vec<HeadingLevel>,
}

unsafe impl IntoValueFromNative for HeadingContext {}

impl magnus::TryConvert for HeadingContext {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HeadingContext as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HeadingContext: {}", e),
                )
            })?
        };
        serde_json::from_str::<HeadingContext>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HeadingContext: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HeadingContext {}

impl HeadingContext {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            headings: kwargs
                .get(ruby.to_symbol("headings"))
                .and_then(|v| <Vec<HeadingLevel>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn headings(&self) -> Vec<HeadingLevel> {
        self.headings.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::HeadingLevel")]
pub struct HeadingLevel {
    level: u8,
    text: String,
}

unsafe impl IntoValueFromNative for HeadingLevel {}

impl magnus::TryConvert for HeadingLevel {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HeadingLevel as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HeadingLevel: {}", e),
                )
            })?
        };
        serde_json::from_str::<HeadingLevel>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HeadingLevel: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HeadingLevel {}

impl HeadingLevel {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            level: kwargs
                .get(ruby.to_symbol("level"))
                .and_then(|v| u8::try_convert(v).ok())
                .unwrap_or_default(),
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn level(&self) -> u8 {
        self.level
    }

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ChunkMetadata")]
pub struct ChunkMetadata {
    byte_start: usize,
    byte_end: usize,
    token_count: Option<usize>,
    chunk_index: usize,
    total_chunks: usize,
    first_page: Option<u32>,
    last_page: Option<u32>,
    heading_context: Option<HeadingContext>,
    image_indices: Vec<u32>,
}

unsafe impl IntoValueFromNative for ChunkMetadata {}

impl magnus::TryConvert for ChunkMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ChunkMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ChunkMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ChunkMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ChunkMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ChunkMetadata {}

impl Default for ChunkMetadata {
    fn default() -> Self {
        Self {
            byte_start: 0,
            byte_end: 0,
            token_count: None,
            chunk_index: 0,
            total_chunks: 0,
            first_page: None,
            last_page: None,
            heading_context: None,
            image_indices: vec![],
        }
    }
}

impl ChunkMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            byte_start: kwargs
                .get(ruby.to_symbol("byte_start"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            byte_end: kwargs
                .get(ruby.to_symbol("byte_end"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            token_count: kwargs
                .get(ruby.to_symbol("token_count"))
                .and_then(|v| usize::try_convert(v).ok()),
            chunk_index: kwargs
                .get(ruby.to_symbol("chunk_index"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            total_chunks: kwargs
                .get(ruby.to_symbol("total_chunks"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            first_page: kwargs
                .get(ruby.to_symbol("first_page"))
                .and_then(|v| u32::try_convert(v).ok()),
            last_page: kwargs
                .get(ruby.to_symbol("last_page"))
                .and_then(|v| u32::try_convert(v).ok()),
            heading_context: kwargs
                .get(ruby.to_symbol("heading_context"))
                .and_then(|v| HeadingContext::try_convert(v).ok()),
            image_indices: kwargs
                .get(ruby.to_symbol("image_indices"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok())
                .unwrap_or(vec![]),
        })
    }

    fn byte_start(&self) -> usize {
        self.byte_start
    }

    fn byte_end(&self) -> usize {
        self.byte_end
    }

    fn token_count(&self) -> Option<usize> {
        self.token_count
    }

    fn chunk_index(&self) -> usize {
        self.chunk_index
    }

    fn total_chunks(&self) -> usize {
        self.total_chunks
    }

    fn first_page(&self) -> Option<u32> {
        self.first_page
    }

    fn last_page(&self) -> Option<u32> {
        self.last_page
    }

    fn heading_context(&self) -> Option<HeadingContext> {
        self.heading_context.clone()
    }

    fn image_indices(&self) -> Vec<u32> {
        self.image_indices.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ExtractedImage")]
pub struct ExtractedImage {
    data: Vec<u8>,
    format: String,
    image_index: u32,
    page_number: Option<u32>,
    width: Option<u32>,
    height: Option<u32>,
    colorspace: Option<String>,
    bits_per_component: Option<u32>,
    is_mask: bool,
    description: Option<String>,
    ocr_result: Option<ExtractionResult>,
    bounding_box: Option<BoundingBox>,
    source_path: Option<String>,
    image_kind: Option<ImageKind>,
    kind_confidence: Option<f32>,
    cluster_id: Option<u32>,
}

unsafe impl IntoValueFromNative for ExtractedImage {}

impl magnus::TryConvert for ExtractedImage {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExtractedImage as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExtractedImage: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExtractedImage>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExtractedImage: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExtractedImage {}

impl Default for ExtractedImage {
    fn default() -> Self {
        Self {
            data: vec![],
            format: String::new(),
            image_index: 0,
            page_number: None,
            width: None,
            height: None,
            colorspace: None,
            bits_per_component: None,
            is_mask: false,
            description: None,
            ocr_result: None,
            bounding_box: None,
            source_path: None,
            image_kind: None,
            kind_confidence: None,
            cluster_id: None,
        }
    }
}

impl ExtractedImage {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            data: kwargs
                .get(ruby.to_symbol("data"))
                .and_then(|v| <Vec<u8>>::try_convert(v).ok())
                .unwrap_or_default(),
            format: kwargs
                .get(ruby.to_symbol("format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            image_index: kwargs
                .get(ruby.to_symbol("image_index"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok()),
            width: kwargs
                .get(ruby.to_symbol("width"))
                .and_then(|v| u32::try_convert(v).ok()),
            height: kwargs
                .get(ruby.to_symbol("height"))
                .and_then(|v| u32::try_convert(v).ok()),
            colorspace: kwargs
                .get(ruby.to_symbol("colorspace"))
                .and_then(|v| String::try_convert(v).ok()),
            bits_per_component: kwargs
                .get(ruby.to_symbol("bits_per_component"))
                .and_then(|v| u32::try_convert(v).ok()),
            is_mask: kwargs
                .get(ruby.to_symbol("is_mask"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            description: kwargs
                .get(ruby.to_symbol("description"))
                .and_then(|v| String::try_convert(v).ok()),
            ocr_result: kwargs
                .get(ruby.to_symbol("ocr_result"))
                .and_then(|v| ExtractionResult::try_convert(v).ok()),
            bounding_box: kwargs
                .get(ruby.to_symbol("bounding_box"))
                .and_then(|v| BoundingBox::try_convert(v).ok()),
            source_path: kwargs
                .get(ruby.to_symbol("source_path"))
                .and_then(|v| String::try_convert(v).ok()),
            image_kind: kwargs
                .get(ruby.to_symbol("image_kind"))
                .and_then(|v| ImageKind::try_convert(v).ok()),
            kind_confidence: kwargs
                .get(ruby.to_symbol("kind_confidence"))
                .and_then(|v| f32::try_convert(v).ok()),
            cluster_id: kwargs
                .get(ruby.to_symbol("cluster_id"))
                .and_then(|v| u32::try_convert(v).ok()),
        })
    }

    fn data(&self) -> Vec<u8> {
        self.data.clone()
    }

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

    fn image_index(&self) -> u32 {
        self.image_index
    }

    fn page_number(&self) -> Option<u32> {
        self.page_number
    }

    fn width(&self) -> Option<u32> {
        self.width
    }

    fn height(&self) -> Option<u32> {
        self.height
    }

    fn colorspace(&self) -> Option<String> {
        self.colorspace.clone()
    }

    fn bits_per_component(&self) -> Option<u32> {
        self.bits_per_component
    }

    fn is_mask(&self) -> bool {
        self.is_mask
    }

    fn description(&self) -> Option<String> {
        self.description.clone()
    }

    fn ocr_result(&self) -> Option<ExtractionResult> {
        self.ocr_result.clone()
    }

    fn bounding_box(&self) -> Option<BoundingBox> {
        self.bounding_box.clone()
    }

    fn source_path(&self) -> Option<String> {
        self.source_path.clone()
    }

    fn image_kind(&self) -> Option<ImageKind> {
        self.image_kind.clone()
    }

    fn kind_confidence(&self) -> Option<f32> {
        self.kind_confidence
    }

    fn cluster_id(&self) -> Option<u32> {
        self.cluster_id
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::BoundingBox")]
pub struct BoundingBox {
    x0: f64,
    y0: f64,
    x1: f64,
    y1: f64,
}

unsafe impl IntoValueFromNative for BoundingBox {}

impl magnus::TryConvert for BoundingBox {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&BoundingBox as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into BoundingBox: {}", e),
                )
            })?
        };
        serde_json::from_str::<BoundingBox>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize BoundingBox: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for BoundingBox {}

impl Default for BoundingBox {
    fn default() -> Self {
        kreuzberg::BoundingBox::default().into()
    }
}

impl BoundingBox {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            x0: kwargs
                .get(ruby.to_symbol("x0"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            y0: kwargs
                .get(ruby.to_symbol("y0"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            x1: kwargs
                .get(ruby.to_symbol("x1"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            y1: kwargs
                .get(ruby.to_symbol("y1"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn x0(&self) -> f64 {
        self.x0
    }

    fn y0(&self) -> f64 {
        self.y0
    }

    fn x1(&self) -> f64 {
        self.x1
    }

    fn y1(&self) -> f64 {
        self.y1
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ElementMetadata")]
pub struct ElementMetadata {
    page_number: Option<u32>,
    filename: Option<String>,
    coordinates: Option<BoundingBox>,
    element_index: Option<usize>,
    additional: HashMap<String, String>,
}

unsafe impl IntoValueFromNative for ElementMetadata {}

impl magnus::TryConvert for ElementMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ElementMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ElementMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ElementMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ElementMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ElementMetadata {}

impl ElementMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok()),
            filename: kwargs
                .get(ruby.to_symbol("filename"))
                .and_then(|v| String::try_convert(v).ok()),
            coordinates: kwargs
                .get(ruby.to_symbol("coordinates"))
                .and_then(|v| BoundingBox::try_convert(v).ok()),
            element_index: kwargs
                .get(ruby.to_symbol("element_index"))
                .and_then(|v| usize::try_convert(v).ok()),
            additional: kwargs
                .get(ruby.to_symbol("additional"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn page_number(&self) -> Option<u32> {
        self.page_number
    }

    fn filename(&self) -> Option<String> {
        self.filename.clone()
    }

    fn coordinates(&self) -> Option<BoundingBox> {
        self.coordinates.clone()
    }

    fn element_index(&self) -> Option<usize> {
        self.element_index
    }

    fn additional(&self) -> HashMap<String, String> {
        self.additional.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::Element")]
pub struct Element {
    element_id: String,
    element_type: ElementType,
    text: String,
    metadata: ElementMetadata,
}

unsafe impl IntoValueFromNative for Element {}

impl magnus::TryConvert for Element {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&Element as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into Element: {}", e),
                )
            })?
        };
        serde_json::from_str::<Element>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize Element: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for Element {}

impl Element {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            element_id: kwargs
                .get(ruby.to_symbol("element_id"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            element_type: kwargs
                .get(ruby.to_symbol("element_type"))
                .and_then(|v| ElementType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: element_type",
                    )
                })?,
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| ElementMetadata::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: metadata",
                    )
                })?,
        })
    }

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

    fn element_type(&self) -> ElementType {
        self.element_type.clone()
    }

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

    fn metadata(&self) -> ElementMetadata {
        self.metadata.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ExcelWorkbook")]
pub struct ExcelWorkbook {
    sheets: Vec<ExcelSheet>,
    metadata: HashMap<String, String>,
    revisions: Option<Vec<DocumentRevision>>,
}

unsafe impl IntoValueFromNative for ExcelWorkbook {}

impl magnus::TryConvert for ExcelWorkbook {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExcelWorkbook as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExcelWorkbook: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExcelWorkbook>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExcelWorkbook: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExcelWorkbook {}

impl Default for ExcelWorkbook {
    fn default() -> Self {
        Self {
            sheets: vec![],
            metadata: std::collections::HashMap::new(),
            revisions: None,
        }
    }
}

impl ExcelWorkbook {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            sheets: kwargs
                .get(ruby.to_symbol("sheets"))
                .and_then(|v| <Vec<ExcelSheet>>::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            revisions: kwargs
                .get(ruby.to_symbol("revisions"))
                .and_then(|v| <Vec<DocumentRevision>>::try_convert(v).ok()),
        })
    }

    fn sheets(&self) -> Vec<ExcelSheet> {
        self.sheets.clone()
    }

    fn metadata(&self) -> HashMap<String, String> {
        self.metadata.clone()
    }

    fn revisions(&self) -> Option<Vec<DocumentRevision>> {
        self.revisions.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ExcelSheet")]
pub struct ExcelSheet {
    name: String,
    markdown: String,
    row_count: usize,
    col_count: usize,
    cell_count: usize,
    table_cells: Option<Vec<Vec<String>>>,
}

unsafe impl IntoValueFromNative for ExcelSheet {}

impl magnus::TryConvert for ExcelSheet {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExcelSheet as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExcelSheet: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExcelSheet>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExcelSheet: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExcelSheet {}

impl ExcelSheet {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            name: kwargs
                .get(ruby.to_symbol("name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            markdown: kwargs
                .get(ruby.to_symbol("markdown"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            row_count: kwargs
                .get(ruby.to_symbol("row_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            col_count: kwargs
                .get(ruby.to_symbol("col_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            cell_count: kwargs
                .get(ruby.to_symbol("cell_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            table_cells: kwargs
                .get(ruby.to_symbol("table_cells"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok()),
        })
    }

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

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

    fn row_count(&self) -> usize {
        self.row_count
    }

    fn col_count(&self) -> usize {
        self.col_count
    }

    fn cell_count(&self) -> usize {
        self.cell_count
    }

    fn table_cells(&self) -> Option<Vec<Vec<String>>> {
        self.table_cells.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::XmlExtractionResult")]
pub struct XmlExtractionResult {
    content: String,
    element_count: usize,
    unique_elements: Vec<String>,
}

unsafe impl IntoValueFromNative for XmlExtractionResult {}

impl magnus::TryConvert for XmlExtractionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&XmlExtractionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into XmlExtractionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<XmlExtractionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize XmlExtractionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for XmlExtractionResult {}

impl XmlExtractionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            element_count: kwargs
                .get(ruby.to_symbol("element_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            unique_elements: kwargs
                .get(ruby.to_symbol("unique_elements"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn element_count(&self) -> usize {
        self.element_count
    }

    fn unique_elements(&self) -> Vec<String> {
        self.unique_elements.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::TextExtractionResult")]
pub struct TextExtractionResult {
    content: String,
    line_count: usize,
    word_count: usize,
    character_count: usize,
    headers: Option<Vec<String>>,
    links: Option<Vec<Vec<String>>>,
    code_blocks: Option<Vec<Vec<String>>>,
}

unsafe impl IntoValueFromNative for TextExtractionResult {}

impl magnus::TryConvert for TextExtractionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TextExtractionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TextExtractionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<TextExtractionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TextExtractionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TextExtractionResult {}

impl TextExtractionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            line_count: kwargs
                .get(ruby.to_symbol("line_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            word_count: kwargs
                .get(ruby.to_symbol("word_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            character_count: kwargs
                .get(ruby.to_symbol("character_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            headers: kwargs
                .get(ruby.to_symbol("headers"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            links: kwargs
                .get(ruby.to_symbol("links"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok()),
            code_blocks: kwargs
                .get(ruby.to_symbol("code_blocks"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok()),
        })
    }

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

    fn line_count(&self) -> usize {
        self.line_count
    }

    fn word_count(&self) -> usize {
        self.word_count
    }

    fn character_count(&self) -> usize {
        self.character_count
    }

    fn headers(&self) -> Option<Vec<String>> {
        self.headers.clone()
    }

    fn links(&self) -> Option<Vec<Vec<String>>> {
        self.links.clone()
    }

    fn code_blocks(&self) -> Option<Vec<Vec<String>>> {
        self.code_blocks.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PptxExtractionResult")]
pub struct PptxExtractionResult {
    content: String,
    metadata: PptxMetadata,
    slide_count: usize,
    image_count: usize,
    table_count: usize,
    images: Vec<ExtractedImage>,
    page_structure: Option<PageStructure>,
    page_contents: Option<Vec<PageContent>>,
    document: Option<DocumentStructure>,
    hyperlinks: Vec<String>,
    office_metadata: HashMap<String, String>,
    revisions: Option<Vec<DocumentRevision>>,
}

unsafe impl IntoValueFromNative for PptxExtractionResult {}

impl magnus::TryConvert for PptxExtractionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PptxExtractionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PptxExtractionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<PptxExtractionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PptxExtractionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PptxExtractionResult {}

impl Default for PptxExtractionResult {
    fn default() -> Self {
        Self {
            content: String::new(),
            metadata: PptxMetadata::default(),
            slide_count: 0,
            image_count: 0,
            table_count: 0,
            images: vec![],
            page_structure: None,
            page_contents: None,
            document: None,
            hyperlinks: vec![],
            office_metadata: std::collections::HashMap::new(),
            revisions: None,
        }
    }
}

impl PptxExtractionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| PptxMetadata::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: metadata",
                    )
                })?,
            slide_count: kwargs
                .get(ruby.to_symbol("slide_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            image_count: kwargs
                .get(ruby.to_symbol("image_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            table_count: kwargs
                .get(ruby.to_symbol("table_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            images: kwargs
                .get(ruby.to_symbol("images"))
                .and_then(|v| <Vec<ExtractedImage>>::try_convert(v).ok())
                .unwrap_or_default(),
            page_structure: kwargs
                .get(ruby.to_symbol("page_structure"))
                .and_then(|v| PageStructure::try_convert(v).ok()),
            page_contents: kwargs
                .get(ruby.to_symbol("page_contents"))
                .and_then(|v| <Vec<PageContent>>::try_convert(v).ok()),
            document: kwargs
                .get(ruby.to_symbol("document"))
                .and_then(|v| DocumentStructure::try_convert(v).ok()),
            hyperlinks: kwargs
                .get(ruby.to_symbol("hyperlinks"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or(vec![]),
            office_metadata: kwargs
                .get(ruby.to_symbol("office_metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or(std::collections::HashMap::new()),
            revisions: kwargs
                .get(ruby.to_symbol("revisions"))
                .and_then(|v| <Vec<DocumentRevision>>::try_convert(v).ok()),
        })
    }

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

    fn metadata(&self) -> PptxMetadata {
        self.metadata.clone()
    }

    fn slide_count(&self) -> usize {
        self.slide_count
    }

    fn image_count(&self) -> usize {
        self.image_count
    }

    fn table_count(&self) -> usize {
        self.table_count
    }

    fn images(&self) -> Vec<ExtractedImage> {
        self.images.clone()
    }

    fn page_structure(&self) -> Option<PageStructure> {
        self.page_structure.clone()
    }

    fn page_contents(&self) -> Option<Vec<PageContent>> {
        self.page_contents.clone()
    }

    fn document(&self) -> Option<DocumentStructure> {
        self.document.clone()
    }

    fn hyperlinks(&self) -> Vec<String> {
        self.hyperlinks.clone()
    }

    fn office_metadata(&self) -> HashMap<String, String> {
        self.office_metadata.clone()
    }

    fn revisions(&self) -> Option<Vec<DocumentRevision>> {
        self.revisions.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::EmailExtractionResult")]
pub struct EmailExtractionResult {
    subject: Option<String>,
    from_email: Option<String>,
    to_emails: Vec<String>,
    cc_emails: Vec<String>,
    bcc_emails: Vec<String>,
    date: Option<String>,
    message_id: Option<String>,
    plain_text: Option<String>,
    html_content: Option<String>,
    content: String,
    attachments: Vec<EmailAttachment>,
    metadata: HashMap<String, String>,
}

unsafe impl IntoValueFromNative for EmailExtractionResult {}

impl magnus::TryConvert for EmailExtractionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmailExtractionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmailExtractionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmailExtractionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmailExtractionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmailExtractionResult {}

impl EmailExtractionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            subject: kwargs
                .get(ruby.to_symbol("subject"))
                .and_then(|v| String::try_convert(v).ok()),
            from_email: kwargs
                .get(ruby.to_symbol("from_email"))
                .and_then(|v| String::try_convert(v).ok()),
            to_emails: kwargs
                .get(ruby.to_symbol("to_emails"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            cc_emails: kwargs
                .get(ruby.to_symbol("cc_emails"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            bcc_emails: kwargs
                .get(ruby.to_symbol("bcc_emails"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            date: kwargs
                .get(ruby.to_symbol("date"))
                .and_then(|v| String::try_convert(v).ok()),
            message_id: kwargs
                .get(ruby.to_symbol("message_id"))
                .and_then(|v| String::try_convert(v).ok()),
            plain_text: kwargs
                .get(ruby.to_symbol("plain_text"))
                .and_then(|v| String::try_convert(v).ok()),
            html_content: kwargs
                .get(ruby.to_symbol("html_content"))
                .and_then(|v| String::try_convert(v).ok()),
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            attachments: kwargs
                .get(ruby.to_symbol("attachments"))
                .and_then(|v| <Vec<EmailAttachment>>::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn subject(&self) -> Option<String> {
        self.subject.clone()
    }

    fn from_email(&self) -> Option<String> {
        self.from_email.clone()
    }

    fn to_emails(&self) -> Vec<String> {
        self.to_emails.clone()
    }

    fn cc_emails(&self) -> Vec<String> {
        self.cc_emails.clone()
    }

    fn bcc_emails(&self) -> Vec<String> {
        self.bcc_emails.clone()
    }

    fn date(&self) -> Option<String> {
        self.date.clone()
    }

    fn message_id(&self) -> Option<String> {
        self.message_id.clone()
    }

    fn plain_text(&self) -> Option<String> {
        self.plain_text.clone()
    }

    fn html_content(&self) -> Option<String> {
        self.html_content.clone()
    }

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

    fn attachments(&self) -> Vec<EmailAttachment> {
        self.attachments.clone()
    }

    fn metadata(&self) -> HashMap<String, String> {
        self.metadata.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::EmailAttachment")]
pub struct EmailAttachment {
    name: Option<String>,
    filename: Option<String>,
    mime_type: Option<String>,
    size: Option<usize>,
    is_image: bool,
    data: Option<Vec<u8>>,
}

unsafe impl IntoValueFromNative for EmailAttachment {}

impl magnus::TryConvert for EmailAttachment {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmailAttachment as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmailAttachment: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmailAttachment>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmailAttachment: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmailAttachment {}

impl EmailAttachment {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            name: kwargs
                .get(ruby.to_symbol("name"))
                .and_then(|v| String::try_convert(v).ok()),
            filename: kwargs
                .get(ruby.to_symbol("filename"))
                .and_then(|v| String::try_convert(v).ok()),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok()),
            size: kwargs
                .get(ruby.to_symbol("size"))
                .and_then(|v| usize::try_convert(v).ok()),
            is_image: kwargs
                .get(ruby.to_symbol("is_image"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            data: kwargs
                .get(ruby.to_symbol("data"))
                .and_then(|v| <Vec<u8>>::try_convert(v).ok()),
        })
    }

    fn name(&self) -> Option<String> {
        self.name.clone()
    }

    fn filename(&self) -> Option<String> {
        self.filename.clone()
    }

    fn mime_type(&self) -> Option<String> {
        self.mime_type.clone()
    }

    fn size(&self) -> Option<usize> {
        self.size
    }

    fn is_image(&self) -> bool {
        self.is_image
    }

    fn data(&self) -> Option<Vec<u8>> {
        self.data.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OcrExtractionResult")]
pub struct OcrExtractionResult {
    content: String,
    mime_type: String,
    metadata: HashMap<String, String>,
    tables: Vec<OcrTable>,
    ocr_elements: Option<Vec<OcrElement>>,
    internal_document: Option<String>,
}

unsafe impl IntoValueFromNative for OcrExtractionResult {}

impl magnus::TryConvert for OcrExtractionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrExtractionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrExtractionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrExtractionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrExtractionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrExtractionResult {}

impl Default for OcrExtractionResult {
    fn default() -> Self {
        Self {
            content: String::new(),
            mime_type: String::new(),
            metadata: std::collections::HashMap::new(),
            tables: vec![],
            ocr_elements: None,
            internal_document: None,
        }
    }
}

impl OcrExtractionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            metadata: kwargs
                .get(ruby.to_symbol("metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            tables: kwargs
                .get(ruby.to_symbol("tables"))
                .and_then(|v| <Vec<OcrTable>>::try_convert(v).ok())
                .unwrap_or_default(),
            ocr_elements: kwargs
                .get(ruby.to_symbol("ocr_elements"))
                .and_then(|v| <Vec<OcrElement>>::try_convert(v).ok()),
            internal_document: kwargs
                .get(ruby.to_symbol("internal_document"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

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

    fn metadata(&self) -> HashMap<String, String> {
        self.metadata.clone()
    }

    fn tables(&self) -> Vec<OcrTable> {
        self.tables.clone()
    }

    fn ocr_elements(&self) -> Option<Vec<OcrElement>> {
        self.ocr_elements.clone()
    }

    fn internal_document(&self) -> Option<String> {
        self.internal_document.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OcrTable")]
pub struct OcrTable {
    cells: Vec<Vec<String>>,
    markdown: String,
    page_number: u32,
    bounding_box: Option<OcrTableBoundingBox>,
}

unsafe impl IntoValueFromNative for OcrTable {}

impl magnus::TryConvert for OcrTable {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrTable as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrTable: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrTable>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrTable: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrTable {}

impl Default for OcrTable {
    fn default() -> Self {
        Self {
            cells: vec![],
            markdown: String::new(),
            page_number: 0,
            bounding_box: None,
        }
    }
}

impl OcrTable {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            cells: kwargs
                .get(ruby.to_symbol("cells"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok())
                .unwrap_or_default(),
            markdown: kwargs
                .get(ruby.to_symbol("markdown"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            bounding_box: kwargs
                .get(ruby.to_symbol("bounding_box"))
                .and_then(|v| OcrTableBoundingBox::try_convert(v).ok()),
        })
    }

    fn cells(&self) -> Vec<Vec<String>> {
        self.cells.clone()
    }

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

    fn page_number(&self) -> u32 {
        self.page_number
    }

    fn bounding_box(&self) -> Option<OcrTableBoundingBox> {
        self.bounding_box.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OcrTableBoundingBox")]
pub struct OcrTableBoundingBox {
    left: u32,
    top: u32,
    right: u32,
    bottom: u32,
}

unsafe impl IntoValueFromNative for OcrTableBoundingBox {}

impl magnus::TryConvert for OcrTableBoundingBox {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrTableBoundingBox as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrTableBoundingBox: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrTableBoundingBox>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrTableBoundingBox: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrTableBoundingBox {}

impl OcrTableBoundingBox {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            left: kwargs
                .get(ruby.to_symbol("left"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            top: kwargs
                .get(ruby.to_symbol("top"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            right: kwargs
                .get(ruby.to_symbol("right"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            bottom: kwargs
                .get(ruby.to_symbol("bottom"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn left(&self) -> u32 {
        self.left
    }

    fn top(&self) -> u32 {
        self.top
    }

    fn right(&self) -> u32 {
        self.right
    }

    fn bottom(&self) -> u32 {
        self.bottom
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ImagePreprocessingConfig")]
pub struct ImagePreprocessingConfig {
    target_dpi: i32,
    auto_rotate: bool,
    deskew: bool,
    denoise: bool,
    contrast_enhance: bool,
    binarization_method: String,
    invert_colors: bool,
}

unsafe impl IntoValueFromNative for ImagePreprocessingConfig {}

impl magnus::TryConvert for ImagePreprocessingConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ImagePreprocessingConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ImagePreprocessingConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<ImagePreprocessingConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ImagePreprocessingConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ImagePreprocessingConfig {}

impl Default for ImagePreprocessingConfig {
    fn default() -> Self {
        kreuzberg::ImagePreprocessingConfig::default().into()
    }
}

impl ImagePreprocessingConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            target_dpi: kwargs
                .get(ruby.to_symbol("target_dpi"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(300),
            auto_rotate: kwargs
                .get(ruby.to_symbol("auto_rotate"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            deskew: kwargs
                .get(ruby.to_symbol("deskew"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            denoise: kwargs
                .get(ruby.to_symbol("denoise"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            contrast_enhance: kwargs
                .get(ruby.to_symbol("contrast_enhance"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            binarization_method: kwargs
                .get(ruby.to_symbol("binarization_method"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or("otsu".to_string()),
            invert_colors: kwargs
                .get(ruby.to_symbol("invert_colors"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn target_dpi(&self) -> i32 {
        self.target_dpi
    }

    fn auto_rotate(&self) -> bool {
        self.auto_rotate
    }

    fn deskew(&self) -> bool {
        self.deskew
    }

    fn denoise(&self) -> bool {
        self.denoise
    }

    fn contrast_enhance(&self) -> bool {
        self.contrast_enhance
    }

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

    fn invert_colors(&self) -> bool {
        self.invert_colors
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TesseractConfig")]
pub struct TesseractConfig {
    language: String,
    psm: i32,
    output_format: String,
    oem: i32,
    min_confidence: f64,
    preprocessing: Option<ImagePreprocessingConfig>,
    enable_table_detection: bool,
    table_min_confidence: f64,
    table_column_threshold: i32,
    table_row_threshold_ratio: f64,
    use_cache: bool,
    classify_use_pre_adapted_templates: bool,
    language_model_ngram_on: bool,
    tessedit_dont_blkrej_good_wds: bool,
    tessedit_dont_rowrej_good_wds: bool,
    tessedit_enable_dict_correction: bool,
    tessedit_char_whitelist: String,
    tessedit_char_blacklist: String,
    tessedit_use_primary_params_model: bool,
    textord_space_size_is_variable: bool,
    thresholding_method: bool,
}

unsafe impl IntoValueFromNative for TesseractConfig {}

impl magnus::TryConvert for TesseractConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TesseractConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TesseractConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<TesseractConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TesseractConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TesseractConfig {}

impl Default for TesseractConfig {
    fn default() -> Self {
        kreuzberg::TesseractConfig::default().into()
    }
}

impl TesseractConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or("eng".to_string()),
            psm: kwargs
                .get(ruby.to_symbol("psm"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(3),
            output_format: kwargs
                .get(ruby.to_symbol("output_format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or("markdown".to_string()),
            oem: kwargs
                .get(ruby.to_symbol("oem"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(3),
            min_confidence: kwargs
                .get(ruby.to_symbol("min_confidence"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.0),
            preprocessing: kwargs
                .get(ruby.to_symbol("preprocessing"))
                .and_then(|v| ImagePreprocessingConfig::try_convert(v).ok()),
            enable_table_detection: kwargs
                .get(ruby.to_symbol("enable_table_detection"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            table_min_confidence: kwargs
                .get(ruby.to_symbol("table_min_confidence"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.0),
            table_column_threshold: kwargs
                .get(ruby.to_symbol("table_column_threshold"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or(50),
            table_row_threshold_ratio: kwargs
                .get(ruby.to_symbol("table_row_threshold_ratio"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or(0.5),
            use_cache: kwargs
                .get(ruby.to_symbol("use_cache"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            classify_use_pre_adapted_templates: kwargs
                .get(ruby.to_symbol("classify_use_pre_adapted_templates"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            language_model_ngram_on: kwargs
                .get(ruby.to_symbol("language_model_ngram_on"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
            tessedit_dont_blkrej_good_wds: kwargs
                .get(ruby.to_symbol("tessedit_dont_blkrej_good_wds"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            tessedit_dont_rowrej_good_wds: kwargs
                .get(ruby.to_symbol("tessedit_dont_rowrej_good_wds"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            tessedit_enable_dict_correction: kwargs
                .get(ruby.to_symbol("tessedit_enable_dict_correction"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            tessedit_char_whitelist: kwargs
                .get(ruby.to_symbol("tessedit_char_whitelist"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or("".to_string()),
            tessedit_char_blacklist: kwargs
                .get(ruby.to_symbol("tessedit_char_blacklist"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or("".to_string()),
            tessedit_use_primary_params_model: kwargs
                .get(ruby.to_symbol("tessedit_use_primary_params_model"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            textord_space_size_is_variable: kwargs
                .get(ruby.to_symbol("textord_space_size_is_variable"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            thresholding_method: kwargs
                .get(ruby.to_symbol("thresholding_method"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

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

    fn psm(&self) -> i32 {
        self.psm
    }

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

    fn oem(&self) -> i32 {
        self.oem
    }

    fn min_confidence(&self) -> f64 {
        self.min_confidence
    }

    fn preprocessing(&self) -> Option<ImagePreprocessingConfig> {
        self.preprocessing.clone()
    }

    fn enable_table_detection(&self) -> bool {
        self.enable_table_detection
    }

    fn table_min_confidence(&self) -> f64 {
        self.table_min_confidence
    }

    fn table_column_threshold(&self) -> i32 {
        self.table_column_threshold
    }

    fn table_row_threshold_ratio(&self) -> f64 {
        self.table_row_threshold_ratio
    }

    fn use_cache(&self) -> bool {
        self.use_cache
    }

    fn classify_use_pre_adapted_templates(&self) -> bool {
        self.classify_use_pre_adapted_templates
    }

    fn language_model_ngram_on(&self) -> bool {
        self.language_model_ngram_on
    }

    fn tessedit_dont_blkrej_good_wds(&self) -> bool {
        self.tessedit_dont_blkrej_good_wds
    }

    fn tessedit_dont_rowrej_good_wds(&self) -> bool {
        self.tessedit_dont_rowrej_good_wds
    }

    fn tessedit_enable_dict_correction(&self) -> bool {
        self.tessedit_enable_dict_correction
    }

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

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

    fn tessedit_use_primary_params_model(&self) -> bool {
        self.tessedit_use_primary_params_model
    }

    fn textord_space_size_is_variable(&self) -> bool {
        self.textord_space_size_is_variable
    }

    fn thresholding_method(&self) -> bool {
        self.thresholding_method
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ImagePreprocessingMetadata")]
pub struct ImagePreprocessingMetadata {
    original_dimensions: Vec<usize>,
    original_dpi: Vec<f64>,
    target_dpi: i32,
    scale_factor: f64,
    auto_adjusted: bool,
    final_dpi: i32,
    new_dimensions: Option<Vec<usize>>,
    resample_method: String,
    dimension_clamped: bool,
    calculated_dpi: Option<i32>,
    skipped_resize: bool,
    resize_error: Option<String>,
}

unsafe impl IntoValueFromNative for ImagePreprocessingMetadata {}

impl magnus::TryConvert for ImagePreprocessingMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ImagePreprocessingMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ImagePreprocessingMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ImagePreprocessingMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ImagePreprocessingMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ImagePreprocessingMetadata {}

impl ImagePreprocessingMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            original_dimensions: kwargs
                .get(ruby.to_symbol("original_dimensions"))
                .and_then(|v| <Vec<usize>>::try_convert(v).ok())
                .unwrap_or_default(),
            original_dpi: kwargs
                .get(ruby.to_symbol("original_dpi"))
                .and_then(|v| <Vec<f64>>::try_convert(v).ok())
                .unwrap_or_default(),
            target_dpi: kwargs
                .get(ruby.to_symbol("target_dpi"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or_default(),
            scale_factor: kwargs
                .get(ruby.to_symbol("scale_factor"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            auto_adjusted: kwargs
                .get(ruby.to_symbol("auto_adjusted"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            final_dpi: kwargs
                .get(ruby.to_symbol("final_dpi"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or_default(),
            new_dimensions: kwargs
                .get(ruby.to_symbol("new_dimensions"))
                .and_then(|v| <Vec<usize>>::try_convert(v).ok()),
            resample_method: kwargs
                .get(ruby.to_symbol("resample_method"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            dimension_clamped: kwargs
                .get(ruby.to_symbol("dimension_clamped"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            calculated_dpi: kwargs
                .get(ruby.to_symbol("calculated_dpi"))
                .and_then(|v| i32::try_convert(v).ok()),
            skipped_resize: kwargs
                .get(ruby.to_symbol("skipped_resize"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            resize_error: kwargs
                .get(ruby.to_symbol("resize_error"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn original_dimensions(&self) -> Vec<usize> {
        self.original_dimensions.clone()
    }

    fn original_dpi(&self) -> Vec<f64> {
        self.original_dpi.clone()
    }

    fn target_dpi(&self) -> i32 {
        self.target_dpi
    }

    fn scale_factor(&self) -> f64 {
        self.scale_factor
    }

    fn auto_adjusted(&self) -> bool {
        self.auto_adjusted
    }

    fn final_dpi(&self) -> i32 {
        self.final_dpi
    }

    fn new_dimensions(&self) -> Option<Vec<usize>> {
        self.new_dimensions.clone()
    }

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

    fn dimension_clamped(&self) -> bool {
        self.dimension_clamped
    }

    fn calculated_dpi(&self) -> Option<i32> {
        self.calculated_dpi
    }

    fn skipped_resize(&self) -> bool {
        self.skipped_resize
    }

    fn resize_error(&self) -> Option<String> {
        self.resize_error.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::Metadata")]
pub struct Metadata {
    title: Option<String>,
    subject: Option<String>,
    authors: Option<Vec<String>>,
    keywords: Option<Vec<String>>,
    language: Option<String>,
    created_at: Option<String>,
    modified_at: Option<String>,
    created_by: Option<String>,
    modified_by: Option<String>,
    pages: Option<PageStructure>,
    format: Option<FormatMetadata>,
    image_preprocessing: Option<ImagePreprocessingMetadata>,
    json_schema: Option<String>,
    error: Option<ErrorMetadata>,
    extraction_duration_ms: Option<u64>,
    category: Option<String>,
    tags: Option<Vec<String>>,
    document_version: Option<String>,
    abstract_text: Option<String>,
    output_format: Option<String>,
    ocr_used: bool,
    additional: HashMap<String, String>,
}

unsafe impl IntoValueFromNative for Metadata {}

impl magnus::TryConvert for Metadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&Metadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into Metadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<Metadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize Metadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for Metadata {}

impl Default for Metadata {
    fn default() -> Self {
        Self {
            title: None,
            subject: None,
            authors: None,
            keywords: None,
            language: None,
            created_at: None,
            modified_at: None,
            created_by: None,
            modified_by: None,
            pages: None,
            format: None,
            image_preprocessing: None,
            json_schema: None,
            error: None,
            extraction_duration_ms: None,
            category: None,
            tags: None,
            document_version: None,
            abstract_text: None,
            output_format: None,
            ocr_used: false,
            additional: Default::default(),
        }
    }
}

impl Metadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            subject: kwargs
                .get(ruby.to_symbol("subject"))
                .and_then(|v| String::try_convert(v).ok()),
            authors: kwargs
                .get(ruby.to_symbol("authors"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            keywords: kwargs
                .get(ruby.to_symbol("keywords"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok()),
            created_at: kwargs
                .get(ruby.to_symbol("created_at"))
                .and_then(|v| String::try_convert(v).ok()),
            modified_at: kwargs
                .get(ruby.to_symbol("modified_at"))
                .and_then(|v| String::try_convert(v).ok()),
            created_by: kwargs
                .get(ruby.to_symbol("created_by"))
                .and_then(|v| String::try_convert(v).ok()),
            modified_by: kwargs
                .get(ruby.to_symbol("modified_by"))
                .and_then(|v| String::try_convert(v).ok()),
            pages: kwargs
                .get(ruby.to_symbol("pages"))
                .and_then(|v| PageStructure::try_convert(v).ok()),
            format: kwargs
                .get(ruby.to_symbol("format"))
                .and_then(|v| FormatMetadata::try_convert(v).ok()),
            image_preprocessing: kwargs
                .get(ruby.to_symbol("image_preprocessing"))
                .and_then(|v| ImagePreprocessingMetadata::try_convert(v).ok()),
            json_schema: kwargs
                .get(ruby.to_symbol("json_schema"))
                .and_then(|v| String::try_convert(v).ok()),
            error: kwargs
                .get(ruby.to_symbol("error"))
                .and_then(|v| ErrorMetadata::try_convert(v).ok()),
            extraction_duration_ms: kwargs
                .get(ruby.to_symbol("extraction_duration_ms"))
                .and_then(|v| u64::try_convert(v).ok()),
            category: kwargs
                .get(ruby.to_symbol("category"))
                .and_then(|v| String::try_convert(v).ok()),
            tags: kwargs
                .get(ruby.to_symbol("tags"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            document_version: kwargs
                .get(ruby.to_symbol("document_version"))
                .and_then(|v| String::try_convert(v).ok()),
            abstract_text: kwargs
                .get(ruby.to_symbol("abstract_text"))
                .and_then(|v| String::try_convert(v).ok()),
            output_format: kwargs
                .get(ruby.to_symbol("output_format"))
                .and_then(|v| String::try_convert(v).ok()),
            ocr_used: kwargs
                .get(ruby.to_symbol("ocr_used"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            additional: kwargs
                .get(ruby.to_symbol("additional"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn subject(&self) -> Option<String> {
        self.subject.clone()
    }

    fn authors(&self) -> Option<Vec<String>> {
        self.authors.clone()
    }

    fn keywords(&self) -> Option<Vec<String>> {
        self.keywords.clone()
    }

    fn language(&self) -> Option<String> {
        self.language.clone()
    }

    fn created_at(&self) -> Option<String> {
        self.created_at.clone()
    }

    fn modified_at(&self) -> Option<String> {
        self.modified_at.clone()
    }

    fn created_by(&self) -> Option<String> {
        self.created_by.clone()
    }

    fn modified_by(&self) -> Option<String> {
        self.modified_by.clone()
    }

    fn pages(&self) -> Option<PageStructure> {
        self.pages.clone()
    }

    fn format(&self) -> Option<FormatMetadata> {
        self.format.clone()
    }

    fn image_preprocessing(&self) -> Option<ImagePreprocessingMetadata> {
        self.image_preprocessing.clone()
    }

    fn json_schema(&self) -> Option<String> {
        self.json_schema.clone()
    }

    fn error(&self) -> Option<ErrorMetadata> {
        self.error.clone()
    }

    fn extraction_duration_ms(&self) -> Option<u64> {
        self.extraction_duration_ms
    }

    fn category(&self) -> Option<String> {
        self.category.clone()
    }

    fn tags(&self) -> Option<Vec<String>> {
        self.tags.clone()
    }

    fn document_version(&self) -> Option<String> {
        self.document_version.clone()
    }

    fn abstract_text(&self) -> Option<String> {
        self.abstract_text.clone()
    }

    fn output_format(&self) -> Option<String> {
        self.output_format.clone()
    }

    fn ocr_used(&self) -> bool {
        self.ocr_used
    }

    fn additional(&self) -> HashMap<String, String> {
        self.additional.clone()
    }

    fn is_empty(&self) -> bool {
        let core_self = kreuzberg::Metadata {
            title: self.title.clone(),

            subject: self.subject.clone(),

            authors: self.authors.clone(),

            keywords: self.keywords.clone(),

            language: self.language.clone(),

            created_at: self.created_at.clone(),

            modified_at: self.modified_at.clone(),

            created_by: self.created_by.clone(),

            modified_by: self.modified_by.clone(),

            pages: self.pages.clone().map(Into::into),

            format: self.format.clone().map(Into::into),

            image_preprocessing: self.image_preprocessing.clone().map(Into::into),

            json_schema: self.json_schema.as_ref().and_then(|s| serde_json::from_str(s).ok()),

            error: self.error.clone().map(Into::into),

            extraction_duration_ms: self.extraction_duration_ms,

            category: self.category.clone(),

            tags: self.tags.clone(),

            document_version: self.document_version.clone(),

            abstract_text: self.abstract_text.clone(),

            output_format: self.output_format.clone(),

            ocr_used: self.ocr_used,

            additional: self
                .additional
                .clone()
                .into_iter()
                .map(|(k, v)| {
                    (
                        k.into(),
                        serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)),
                    )
                })
                .collect(),
        };
        core_self.is_empty()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ExcelMetadata")]
pub struct ExcelMetadata {
    sheet_count: Option<u32>,
    sheet_names: Option<Vec<String>>,
}

unsafe impl IntoValueFromNative for ExcelMetadata {}

impl magnus::TryConvert for ExcelMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExcelMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExcelMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExcelMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExcelMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExcelMetadata {}

impl Default for ExcelMetadata {
    fn default() -> Self {
        kreuzberg::ExcelMetadata::default().into()
    }
}

impl ExcelMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            sheet_count: kwargs
                .get(ruby.to_symbol("sheet_count"))
                .and_then(|v| u32::try_convert(v).ok()),
            sheet_names: kwargs
                .get(ruby.to_symbol("sheet_names"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
        })
    }

    fn sheet_count(&self) -> Option<u32> {
        self.sheet_count
    }

    fn sheet_names(&self) -> Option<Vec<String>> {
        self.sheet_names.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::EmailMetadata")]
pub struct EmailMetadata {
    from_email: Option<String>,
    from_name: Option<String>,
    to_emails: Vec<String>,
    cc_emails: Vec<String>,
    bcc_emails: Vec<String>,
    message_id: Option<String>,
    attachments: Vec<String>,
}

unsafe impl IntoValueFromNative for EmailMetadata {}

impl magnus::TryConvert for EmailMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmailMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmailMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmailMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmailMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmailMetadata {}

impl Default for EmailMetadata {
    fn default() -> Self {
        kreuzberg::EmailMetadata::default().into()
    }
}

impl EmailMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            from_email: kwargs
                .get(ruby.to_symbol("from_email"))
                .and_then(|v| String::try_convert(v).ok()),
            from_name: kwargs
                .get(ruby.to_symbol("from_name"))
                .and_then(|v| String::try_convert(v).ok()),
            to_emails: kwargs
                .get(ruby.to_symbol("to_emails"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            cc_emails: kwargs
                .get(ruby.to_symbol("cc_emails"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            bcc_emails: kwargs
                .get(ruby.to_symbol("bcc_emails"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            message_id: kwargs
                .get(ruby.to_symbol("message_id"))
                .and_then(|v| String::try_convert(v).ok()),
            attachments: kwargs
                .get(ruby.to_symbol("attachments"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn from_email(&self) -> Option<String> {
        self.from_email.clone()
    }

    fn from_name(&self) -> Option<String> {
        self.from_name.clone()
    }

    fn to_emails(&self) -> Vec<String> {
        self.to_emails.clone()
    }

    fn cc_emails(&self) -> Vec<String> {
        self.cc_emails.clone()
    }

    fn bcc_emails(&self) -> Vec<String> {
        self.bcc_emails.clone()
    }

    fn message_id(&self) -> Option<String> {
        self.message_id.clone()
    }

    fn attachments(&self) -> Vec<String> {
        self.attachments.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ArchiveMetadata")]
pub struct ArchiveMetadata {
    format: String,
    file_count: u32,
    file_list: Vec<String>,
    total_size: u64,
    compressed_size: Option<u64>,
}

unsafe impl IntoValueFromNative for ArchiveMetadata {}

impl magnus::TryConvert for ArchiveMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ArchiveMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ArchiveMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ArchiveMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ArchiveMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ArchiveMetadata {}

impl Default for ArchiveMetadata {
    fn default() -> Self {
        kreuzberg::ArchiveMetadata::default().into()
    }
}

impl ArchiveMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            format: kwargs
                .get(ruby.to_symbol("format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            file_count: kwargs
                .get(ruby.to_symbol("file_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            file_list: kwargs
                .get(ruby.to_symbol("file_list"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            total_size: kwargs
                .get(ruby.to_symbol("total_size"))
                .and_then(|v| u64::try_convert(v).ok())
                .unwrap_or_default(),
            compressed_size: kwargs
                .get(ruby.to_symbol("compressed_size"))
                .and_then(|v| u64::try_convert(v).ok()),
        })
    }

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

    fn file_count(&self) -> u32 {
        self.file_count
    }

    fn file_list(&self) -> Vec<String> {
        self.file_list.clone()
    }

    fn total_size(&self) -> u64 {
        self.total_size
    }

    fn compressed_size(&self) -> Option<u64> {
        self.compressed_size
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::ImageMetadata")]
pub struct ImageMetadata {
    width: u32,
    height: u32,
    format: String,
    exif: HashMap<String, String>,
}

unsafe impl IntoValueFromNative for ImageMetadata {}

impl magnus::TryConvert for ImageMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ImageMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ImageMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ImageMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ImageMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ImageMetadata {}

impl Default for ImageMetadata {
    fn default() -> Self {
        kreuzberg::ImageMetadata::default().into()
    }
}

impl ImageMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            width: kwargs
                .get(ruby.to_symbol("width"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            height: kwargs
                .get(ruby.to_symbol("height"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            format: kwargs
                .get(ruby.to_symbol("format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            exif: kwargs
                .get(ruby.to_symbol("exif"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn width(&self) -> u32 {
        self.width
    }

    fn height(&self) -> u32 {
        self.height
    }

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

    fn exif(&self) -> HashMap<String, String> {
        self.exif.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::XmlMetadata")]
pub struct XmlMetadata {
    element_count: u32,
    unique_elements: Vec<String>,
}

unsafe impl IntoValueFromNative for XmlMetadata {}

impl magnus::TryConvert for XmlMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&XmlMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into XmlMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<XmlMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize XmlMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for XmlMetadata {}

impl Default for XmlMetadata {
    fn default() -> Self {
        kreuzberg::XmlMetadata::default().into()
    }
}

impl XmlMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            element_count: kwargs
                .get(ruby.to_symbol("element_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            unique_elements: kwargs
                .get(ruby.to_symbol("unique_elements"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn element_count(&self) -> u32 {
        self.element_count
    }

    fn unique_elements(&self) -> Vec<String> {
        self.unique_elements.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TextMetadata")]
pub struct TextMetadata {
    line_count: u32,
    word_count: u32,
    character_count: u32,
    headers: Option<Vec<String>>,
    links: Option<Vec<Vec<String>>>,
    code_blocks: Option<Vec<Vec<String>>>,
}

unsafe impl IntoValueFromNative for TextMetadata {}

impl magnus::TryConvert for TextMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TextMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TextMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<TextMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TextMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TextMetadata {}

impl Default for TextMetadata {
    fn default() -> Self {
        kreuzberg::TextMetadata::default().into()
    }
}

impl TextMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            line_count: kwargs
                .get(ruby.to_symbol("line_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            word_count: kwargs
                .get(ruby.to_symbol("word_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            character_count: kwargs
                .get(ruby.to_symbol("character_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            headers: kwargs
                .get(ruby.to_symbol("headers"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
            links: kwargs
                .get(ruby.to_symbol("links"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok()),
            code_blocks: kwargs
                .get(ruby.to_symbol("code_blocks"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok()),
        })
    }

    fn line_count(&self) -> u32 {
        self.line_count
    }

    fn word_count(&self) -> u32 {
        self.word_count
    }

    fn character_count(&self) -> u32 {
        self.character_count
    }

    fn headers(&self) -> Option<Vec<String>> {
        self.headers.clone()
    }

    fn links(&self) -> Option<Vec<Vec<String>>> {
        self.links.clone()
    }

    fn code_blocks(&self) -> Option<Vec<Vec<String>>> {
        self.code_blocks.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::HeaderMetadata")]
pub struct HeaderMetadata {
    level: u8,
    text: String,
    id: Option<String>,
    depth: u32,
    html_offset: u32,
}

unsafe impl IntoValueFromNative for HeaderMetadata {}

impl magnus::TryConvert for HeaderMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HeaderMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HeaderMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<HeaderMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HeaderMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HeaderMetadata {}

impl HeaderMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            level: kwargs
                .get(ruby.to_symbol("level"))
                .and_then(|v| u8::try_convert(v).ok())
                .unwrap_or_default(),
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            id: kwargs
                .get(ruby.to_symbol("id"))
                .and_then(|v| String::try_convert(v).ok()),
            depth: kwargs
                .get(ruby.to_symbol("depth"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            html_offset: kwargs
                .get(ruby.to_symbol("html_offset"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn level(&self) -> u8 {
        self.level
    }

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

    fn id(&self) -> Option<String> {
        self.id.clone()
    }

    fn depth(&self) -> u32 {
        self.depth
    }

    fn html_offset(&self) -> u32 {
        self.html_offset
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::LinkMetadata")]
pub struct LinkMetadata {
    href: String,
    text: String,
    title: Option<String>,
    link_type: LinkType,
    rel: Vec<String>,
    attributes: Vec<Vec<String>>,
}

unsafe impl IntoValueFromNative for LinkMetadata {}

impl magnus::TryConvert for LinkMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LinkMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LinkMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<LinkMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LinkMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LinkMetadata {}

impl LinkMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            href: kwargs
                .get(ruby.to_symbol("href"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            link_type: kwargs
                .get(ruby.to_symbol("link_type"))
                .and_then(|v| LinkType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: link_type",
                    )
                })?,
            rel: kwargs
                .get(ruby.to_symbol("rel"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn link_type(&self) -> LinkType {
        self.link_type.clone()
    }

    fn rel(&self) -> Vec<String> {
        self.rel.clone()
    }

    fn attributes(&self) -> Vec<Vec<String>> {
        self.attributes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ImageMetadataType")]
pub struct ImageMetadataType {
    src: String,
    alt: Option<String>,
    title: Option<String>,
    dimensions: Option<Vec<u32>>,
    image_type: ImageType,
    attributes: Vec<Vec<String>>,
}

unsafe impl IntoValueFromNative for ImageMetadataType {}

impl magnus::TryConvert for ImageMetadataType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ImageMetadataType as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ImageMetadataType: {}", e),
                )
            })?
        };
        serde_json::from_str::<ImageMetadataType>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ImageMetadataType: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ImageMetadataType {}

impl ImageMetadataType {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            src: kwargs
                .get(ruby.to_symbol("src"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            alt: kwargs
                .get(ruby.to_symbol("alt"))
                .and_then(|v| String::try_convert(v).ok()),
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            dimensions: kwargs
                .get(ruby.to_symbol("dimensions"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok()),
            image_type: kwargs
                .get(ruby.to_symbol("image_type"))
                .and_then(|v| ImageType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: image_type",
                    )
                })?,
            attributes: kwargs
                .get(ruby.to_symbol("attributes"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn alt(&self) -> Option<String> {
        self.alt.clone()
    }

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn dimensions(&self) -> Option<Vec<u32>> {
        self.dimensions.clone()
    }

    fn image_type(&self) -> ImageType {
        self.image_type.clone()
    }

    fn attributes(&self) -> Vec<Vec<String>> {
        self.attributes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::StructuredData")]
pub struct StructuredData {
    data_type: StructuredDataType,
    raw_json: String,
    schema_type: Option<String>,
}

unsafe impl IntoValueFromNative for StructuredData {}

impl magnus::TryConvert for StructuredData {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&StructuredData as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into StructuredData: {}", e),
                )
            })?
        };
        serde_json::from_str::<StructuredData>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize StructuredData: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for StructuredData {}

impl StructuredData {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            data_type: kwargs
                .get(ruby.to_symbol("data_type"))
                .and_then(|v| StructuredDataType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: data_type",
                    )
                })?,
            raw_json: kwargs
                .get(ruby.to_symbol("raw_json"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            schema_type: kwargs
                .get(ruby.to_symbol("schema_type"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn data_type(&self) -> StructuredDataType {
        self.data_type.clone()
    }

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

    fn schema_type(&self) -> Option<String> {
        self.schema_type.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::HtmlMetadata")]
pub struct HtmlMetadata {
    title: Option<String>,
    description: Option<String>,
    keywords: Vec<String>,
    author: Option<String>,
    canonical_url: Option<String>,
    base_href: Option<String>,
    language: Option<String>,
    text_direction: Option<TextDirection>,
    open_graph: HashMap<String, String>,
    twitter_card: HashMap<String, String>,
    meta_tags: HashMap<String, String>,
    headers: Vec<HeaderMetadata>,
    links: Vec<LinkMetadata>,
    images: Vec<ImageMetadataType>,
    structured_data: Vec<StructuredData>,
}

unsafe impl IntoValueFromNative for HtmlMetadata {}

impl magnus::TryConvert for HtmlMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HtmlMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HtmlMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<HtmlMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HtmlMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HtmlMetadata {}

impl Default for HtmlMetadata {
    fn default() -> Self {
        Self {
            title: None,
            description: None,
            keywords: vec![],
            author: None,
            canonical_url: None,
            base_href: None,
            language: None,
            text_direction: None,
            open_graph: Default::default(),
            twitter_card: Default::default(),
            meta_tags: Default::default(),
            headers: vec![],
            links: vec![],
            images: vec![],
            structured_data: vec![],
        }
    }
}

impl HtmlMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            description: kwargs
                .get(ruby.to_symbol("description"))
                .and_then(|v| String::try_convert(v).ok()),
            keywords: kwargs
                .get(ruby.to_symbol("keywords"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            author: kwargs
                .get(ruby.to_symbol("author"))
                .and_then(|v| String::try_convert(v).ok()),
            canonical_url: kwargs
                .get(ruby.to_symbol("canonical_url"))
                .and_then(|v| String::try_convert(v).ok()),
            base_href: kwargs
                .get(ruby.to_symbol("base_href"))
                .and_then(|v| String::try_convert(v).ok()),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok()),
            text_direction: kwargs
                .get(ruby.to_symbol("text_direction"))
                .and_then(|v| TextDirection::try_convert(v).ok()),
            open_graph: kwargs
                .get(ruby.to_symbol("open_graph"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            twitter_card: kwargs
                .get(ruby.to_symbol("twitter_card"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            meta_tags: kwargs
                .get(ruby.to_symbol("meta_tags"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            headers: kwargs
                .get(ruby.to_symbol("headers"))
                .and_then(|v| <Vec<HeaderMetadata>>::try_convert(v).ok())
                .unwrap_or_default(),
            links: kwargs
                .get(ruby.to_symbol("links"))
                .and_then(|v| <Vec<LinkMetadata>>::try_convert(v).ok())
                .unwrap_or_default(),
            images: kwargs
                .get(ruby.to_symbol("images"))
                .and_then(|v| <Vec<ImageMetadataType>>::try_convert(v).ok())
                .unwrap_or_default(),
            structured_data: kwargs
                .get(ruby.to_symbol("structured_data"))
                .and_then(|v| <Vec<StructuredData>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn description(&self) -> Option<String> {
        self.description.clone()
    }

    fn keywords(&self) -> Vec<String> {
        self.keywords.clone()
    }

    fn author(&self) -> Option<String> {
        self.author.clone()
    }

    fn canonical_url(&self) -> Option<String> {
        self.canonical_url.clone()
    }

    fn base_href(&self) -> Option<String> {
        self.base_href.clone()
    }

    fn language(&self) -> Option<String> {
        self.language.clone()
    }

    fn text_direction(&self) -> Option<TextDirection> {
        self.text_direction.clone()
    }

    fn open_graph(&self) -> HashMap<String, String> {
        self.open_graph.clone()
    }

    fn twitter_card(&self) -> HashMap<String, String> {
        self.twitter_card.clone()
    }

    fn meta_tags(&self) -> HashMap<String, String> {
        self.meta_tags.clone()
    }

    fn headers(&self) -> Vec<HeaderMetadata> {
        self.headers.clone()
    }

    fn links(&self) -> Vec<LinkMetadata> {
        self.links.clone()
    }

    fn images(&self) -> Vec<ImageMetadataType> {
        self.images.clone()
    }

    fn structured_data(&self) -> Vec<StructuredData> {
        self.structured_data.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::OcrMetadata")]
pub struct OcrMetadata {
    language: String,
    psm: i32,
    output_format: String,
    table_count: u32,
    table_rows: Option<u32>,
    table_cols: Option<u32>,
}

unsafe impl IntoValueFromNative for OcrMetadata {}

impl magnus::TryConvert for OcrMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrMetadata {}

impl Default for OcrMetadata {
    fn default() -> Self {
        kreuzberg::OcrMetadata::default().into()
    }
}

impl OcrMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            psm: kwargs
                .get(ruby.to_symbol("psm"))
                .and_then(|v| i32::try_convert(v).ok())
                .unwrap_or_default(),
            output_format: kwargs
                .get(ruby.to_symbol("output_format"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            table_count: kwargs
                .get(ruby.to_symbol("table_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            table_rows: kwargs
                .get(ruby.to_symbol("table_rows"))
                .and_then(|v| u32::try_convert(v).ok()),
            table_cols: kwargs
                .get(ruby.to_symbol("table_cols"))
                .and_then(|v| u32::try_convert(v).ok()),
        })
    }

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

    fn psm(&self) -> i32 {
        self.psm
    }

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

    fn table_count(&self) -> u32 {
        self.table_count
    }

    fn table_rows(&self) -> Option<u32> {
        self.table_rows
    }

    fn table_cols(&self) -> Option<u32> {
        self.table_cols
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ErrorMetadata")]
pub struct ErrorMetadata {
    error_type: String,
    message: String,
}

unsafe impl IntoValueFromNative for ErrorMetadata {}

impl magnus::TryConvert for ErrorMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ErrorMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ErrorMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<ErrorMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ErrorMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ErrorMetadata {}

impl ErrorMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            error_type: kwargs
                .get(ruby.to_symbol("error_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            message: kwargs
                .get(ruby.to_symbol("message"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PptxMetadata")]
pub struct PptxMetadata {
    slide_count: u32,
    slide_names: Vec<String>,
    image_count: Option<u32>,
    table_count: Option<u32>,
}

unsafe impl IntoValueFromNative for PptxMetadata {}

impl magnus::TryConvert for PptxMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PptxMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PptxMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<PptxMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PptxMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PptxMetadata {}

impl Default for PptxMetadata {
    fn default() -> Self {
        kreuzberg::PptxMetadata::default().into()
    }
}

impl PptxMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            slide_count: kwargs
                .get(ruby.to_symbol("slide_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            slide_names: kwargs
                .get(ruby.to_symbol("slide_names"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            image_count: kwargs
                .get(ruby.to_symbol("image_count"))
                .and_then(|v| u32::try_convert(v).ok()),
            table_count: kwargs
                .get(ruby.to_symbol("table_count"))
                .and_then(|v| u32::try_convert(v).ok()),
        })
    }

    fn slide_count(&self) -> u32 {
        self.slide_count
    }

    fn slide_names(&self) -> Vec<String> {
        self.slide_names.clone()
    }

    fn image_count(&self) -> Option<u32> {
        self.image_count
    }

    fn table_count(&self) -> Option<u32> {
        self.table_count
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::DocxMetadata")]
pub struct DocxMetadata {
    core_properties: Option<CoreProperties>,
    app_properties: Option<DocxAppProperties>,
    custom_properties: Option<HashMap<String, String>>,
}

unsafe impl IntoValueFromNative for DocxMetadata {}

impl magnus::TryConvert for DocxMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DocxMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DocxMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<DocxMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DocxMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DocxMetadata {}

impl Default for DocxMetadata {
    fn default() -> Self {
        kreuzberg::DocxMetadata::default().into()
    }
}

impl DocxMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            core_properties: kwargs
                .get(ruby.to_symbol("core_properties"))
                .and_then(|v| CoreProperties::try_convert(v).ok()),
            app_properties: kwargs
                .get(ruby.to_symbol("app_properties"))
                .and_then(|v| DocxAppProperties::try_convert(v).ok()),
            custom_properties: kwargs
                .get(ruby.to_symbol("custom_properties"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok()),
        })
    }

    fn core_properties(&self) -> Option<CoreProperties> {
        self.core_properties.clone()
    }

    fn app_properties(&self) -> Option<DocxAppProperties> {
        self.app_properties.clone()
    }

    fn custom_properties(&self) -> Option<HashMap<String, String>> {
        self.custom_properties.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::CsvMetadata")]
pub struct CsvMetadata {
    row_count: u32,
    column_count: u32,
    delimiter: Option<String>,
    has_header: bool,
    column_types: Option<Vec<String>>,
}

unsafe impl IntoValueFromNative for CsvMetadata {}

impl magnus::TryConvert for CsvMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&CsvMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into CsvMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<CsvMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize CsvMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for CsvMetadata {}

impl Default for CsvMetadata {
    fn default() -> Self {
        kreuzberg::CsvMetadata::default().into()
    }
}

impl CsvMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            row_count: kwargs
                .get(ruby.to_symbol("row_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            column_count: kwargs
                .get(ruby.to_symbol("column_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            delimiter: kwargs
                .get(ruby.to_symbol("delimiter"))
                .and_then(|v| String::try_convert(v).ok()),
            has_header: kwargs
                .get(ruby.to_symbol("has_header"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            column_types: kwargs
                .get(ruby.to_symbol("column_types"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok()),
        })
    }

    fn row_count(&self) -> u32 {
        self.row_count
    }

    fn column_count(&self) -> u32 {
        self.column_count
    }

    fn delimiter(&self) -> Option<String> {
        self.delimiter.clone()
    }

    fn has_header(&self) -> bool {
        self.has_header
    }

    fn column_types(&self) -> Option<Vec<String>> {
        self.column_types.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::BibtexMetadata")]
pub struct BibtexMetadata {
    entry_count: usize,
    citation_keys: Vec<String>,
    authors: Vec<String>,
    year_range: Option<YearRange>,
    entry_types: Option<HashMap<String, usize>>,
}

unsafe impl IntoValueFromNative for BibtexMetadata {}

impl magnus::TryConvert for BibtexMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&BibtexMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into BibtexMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<BibtexMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize BibtexMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for BibtexMetadata {}

impl Default for BibtexMetadata {
    fn default() -> Self {
        Self {
            entry_count: 0,
            citation_keys: vec![],
            authors: vec![],
            year_range: None,
            entry_types: None,
        }
    }
}

impl BibtexMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            entry_count: kwargs
                .get(ruby.to_symbol("entry_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            citation_keys: kwargs
                .get(ruby.to_symbol("citation_keys"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            authors: kwargs
                .get(ruby.to_symbol("authors"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            year_range: kwargs
                .get(ruby.to_symbol("year_range"))
                .and_then(|v| YearRange::try_convert(v).ok()),
            entry_types: kwargs
                .get(ruby.to_symbol("entry_types"))
                .and_then(|v| <HashMap<String, usize>>::try_convert(v).ok()),
        })
    }

    fn entry_count(&self) -> usize {
        self.entry_count
    }

    fn citation_keys(&self) -> Vec<String> {
        self.citation_keys.clone()
    }

    fn authors(&self) -> Vec<String> {
        self.authors.clone()
    }

    fn year_range(&self) -> Option<YearRange> {
        self.year_range.clone()
    }

    fn entry_types(&self) -> Option<HashMap<String, usize>> {
        self.entry_types.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::CitationMetadata")]
pub struct CitationMetadata {
    citation_count: usize,
    format: Option<String>,
    authors: Vec<String>,
    year_range: Option<YearRange>,
    dois: Vec<String>,
    keywords: Vec<String>,
}

unsafe impl IntoValueFromNative for CitationMetadata {}

impl magnus::TryConvert for CitationMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&CitationMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into CitationMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<CitationMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize CitationMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for CitationMetadata {}

impl Default for CitationMetadata {
    fn default() -> Self {
        Self {
            citation_count: 0,
            format: None,
            authors: vec![],
            year_range: None,
            dois: vec![],
            keywords: vec![],
        }
    }
}

impl CitationMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            citation_count: kwargs
                .get(ruby.to_symbol("citation_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            format: kwargs
                .get(ruby.to_symbol("format"))
                .and_then(|v| String::try_convert(v).ok()),
            authors: kwargs
                .get(ruby.to_symbol("authors"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            year_range: kwargs
                .get(ruby.to_symbol("year_range"))
                .and_then(|v| YearRange::try_convert(v).ok()),
            dois: kwargs
                .get(ruby.to_symbol("dois"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            keywords: kwargs
                .get(ruby.to_symbol("keywords"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn citation_count(&self) -> usize {
        self.citation_count
    }

    fn format(&self) -> Option<String> {
        self.format.clone()
    }

    fn authors(&self) -> Vec<String> {
        self.authors.clone()
    }

    fn year_range(&self) -> Option<YearRange> {
        self.year_range.clone()
    }

    fn dois(&self) -> Vec<String> {
        self.dois.clone()
    }

    fn keywords(&self) -> Vec<String> {
        self.keywords.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::YearRange")]
pub struct YearRange {
    min: Option<u32>,
    max: Option<u32>,
    years: Vec<u32>,
}

unsafe impl IntoValueFromNative for YearRange {}

impl magnus::TryConvert for YearRange {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&YearRange as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into YearRange: {}", e),
                )
            })?
        };
        serde_json::from_str::<YearRange>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize YearRange: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for YearRange {}

impl Default for YearRange {
    fn default() -> Self {
        Self {
            min: None,
            max: None,
            years: vec![],
        }
    }
}

impl YearRange {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            min: kwargs.get(ruby.to_symbol("min")).and_then(|v| u32::try_convert(v).ok()),
            max: kwargs.get(ruby.to_symbol("max")).and_then(|v| u32::try_convert(v).ok()),
            years: kwargs
                .get(ruby.to_symbol("years"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok())
                .unwrap_or(vec![]),
        })
    }

    fn min(&self) -> Option<u32> {
        self.min
    }

    fn max(&self) -> Option<u32> {
        self.max
    }

    fn years(&self) -> Vec<u32> {
        self.years.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::FictionBookMetadata")]
pub struct FictionBookMetadata {
    genres: Vec<String>,
    sequences: Vec<String>,
    annotation: Option<String>,
}

unsafe impl IntoValueFromNative for FictionBookMetadata {}

impl magnus::TryConvert for FictionBookMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&FictionBookMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into FictionBookMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<FictionBookMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize FictionBookMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for FictionBookMetadata {}

impl Default for FictionBookMetadata {
    fn default() -> Self {
        kreuzberg::FictionBookMetadata::default().into()
    }
}

impl FictionBookMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            genres: kwargs
                .get(ruby.to_symbol("genres"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            sequences: kwargs
                .get(ruby.to_symbol("sequences"))
                .and_then(|v| <Vec<String>>::try_convert(v).ok())
                .unwrap_or_default(),
            annotation: kwargs
                .get(ruby.to_symbol("annotation"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn genres(&self) -> Vec<String> {
        self.genres.clone()
    }

    fn sequences(&self) -> Vec<String> {
        self.sequences.clone()
    }

    fn annotation(&self) -> Option<String> {
        self.annotation.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::DbfMetadata")]
pub struct DbfMetadata {
    record_count: usize,
    field_count: usize,
    fields: Vec<DbfFieldInfo>,
}

unsafe impl IntoValueFromNative for DbfMetadata {}

impl magnus::TryConvert for DbfMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DbfMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DbfMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<DbfMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DbfMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DbfMetadata {}

impl Default for DbfMetadata {
    fn default() -> Self {
        Self {
            record_count: 0,
            field_count: 0,
            fields: vec![],
        }
    }
}

impl DbfMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            record_count: kwargs
                .get(ruby.to_symbol("record_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            field_count: kwargs
                .get(ruby.to_symbol("field_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            fields: kwargs
                .get(ruby.to_symbol("fields"))
                .and_then(|v| <Vec<DbfFieldInfo>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn record_count(&self) -> usize {
        self.record_count
    }

    fn field_count(&self) -> usize {
        self.field_count
    }

    fn fields(&self) -> Vec<DbfFieldInfo> {
        self.fields.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DbfFieldInfo")]
pub struct DbfFieldInfo {
    name: String,
    field_type: String,
}

unsafe impl IntoValueFromNative for DbfFieldInfo {}

impl magnus::TryConvert for DbfFieldInfo {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DbfFieldInfo as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DbfFieldInfo: {}", e),
                )
            })?
        };
        serde_json::from_str::<DbfFieldInfo>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DbfFieldInfo: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DbfFieldInfo {}

impl DbfFieldInfo {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            name: kwargs
                .get(ruby.to_symbol("name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            field_type: kwargs
                .get(ruby.to_symbol("field_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::JatsMetadata")]
pub struct JatsMetadata {
    copyright: Option<String>,
    license: Option<String>,
    history_dates: HashMap<String, String>,
    contributor_roles: Vec<ContributorRole>,
}

unsafe impl IntoValueFromNative for JatsMetadata {}

impl magnus::TryConvert for JatsMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&JatsMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into JatsMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<JatsMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize JatsMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for JatsMetadata {}

impl Default for JatsMetadata {
    fn default() -> Self {
        Self {
            copyright: None,
            license: None,
            history_dates: Default::default(),
            contributor_roles: vec![],
        }
    }
}

impl JatsMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            copyright: kwargs
                .get(ruby.to_symbol("copyright"))
                .and_then(|v| String::try_convert(v).ok()),
            license: kwargs
                .get(ruby.to_symbol("license"))
                .and_then(|v| String::try_convert(v).ok()),
            history_dates: kwargs
                .get(ruby.to_symbol("history_dates"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
            contributor_roles: kwargs
                .get(ruby.to_symbol("contributor_roles"))
                .and_then(|v| <Vec<ContributorRole>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn copyright(&self) -> Option<String> {
        self.copyright.clone()
    }

    fn license(&self) -> Option<String> {
        self.license.clone()
    }

    fn history_dates(&self) -> HashMap<String, String> {
        self.history_dates.clone()
    }

    fn contributor_roles(&self) -> Vec<ContributorRole> {
        self.contributor_roles.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ContributorRole")]
pub struct ContributorRole {
    name: String,
    role: Option<String>,
}

unsafe impl IntoValueFromNative for ContributorRole {}

impl magnus::TryConvert for ContributorRole {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ContributorRole as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ContributorRole: {}", e),
                )
            })?
        };
        serde_json::from_str::<ContributorRole>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ContributorRole: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ContributorRole {}

impl ContributorRole {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            name: kwargs
                .get(ruby.to_symbol("name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            role: kwargs
                .get(ruby.to_symbol("role"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

    fn role(&self) -> Option<String> {
        self.role.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::EpubMetadata")]
pub struct EpubMetadata {
    coverage: Option<String>,
    dc_format: Option<String>,
    relation: Option<String>,
    source: Option<String>,
    dc_type: Option<String>,
    cover_image: Option<String>,
}

unsafe impl IntoValueFromNative for EpubMetadata {}

impl magnus::TryConvert for EpubMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EpubMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EpubMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<EpubMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EpubMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EpubMetadata {}

impl Default for EpubMetadata {
    fn default() -> Self {
        kreuzberg::EpubMetadata::default().into()
    }
}

impl EpubMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            coverage: kwargs
                .get(ruby.to_symbol("coverage"))
                .and_then(|v| String::try_convert(v).ok()),
            dc_format: kwargs
                .get(ruby.to_symbol("dc_format"))
                .and_then(|v| String::try_convert(v).ok()),
            relation: kwargs
                .get(ruby.to_symbol("relation"))
                .and_then(|v| String::try_convert(v).ok()),
            source: kwargs
                .get(ruby.to_symbol("source"))
                .and_then(|v| String::try_convert(v).ok()),
            dc_type: kwargs
                .get(ruby.to_symbol("dc_type"))
                .and_then(|v| String::try_convert(v).ok()),
            cover_image: kwargs
                .get(ruby.to_symbol("cover_image"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn coverage(&self) -> Option<String> {
        self.coverage.clone()
    }

    fn dc_format(&self) -> Option<String> {
        self.dc_format.clone()
    }

    fn relation(&self) -> Option<String> {
        self.relation.clone()
    }

    fn source(&self) -> Option<String> {
        self.source.clone()
    }

    fn dc_type(&self) -> Option<String> {
        self.dc_type.clone()
    }

    fn cover_image(&self) -> Option<String> {
        self.cover_image.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PstMetadata")]
pub struct PstMetadata {
    message_count: usize,
}

unsafe impl IntoValueFromNative for PstMetadata {}

impl magnus::TryConvert for PstMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PstMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PstMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<PstMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PstMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PstMetadata {}

impl Default for PstMetadata {
    fn default() -> Self {
        kreuzberg::PstMetadata::default().into()
    }
}

impl PstMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            message_count: kwargs
                .get(ruby.to_symbol("message_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn message_count(&self) -> usize {
        self.message_count
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::OcrConfidence")]
pub struct OcrConfidence {
    detection: Option<f64>,
    recognition: f64,
}

unsafe impl IntoValueFromNative for OcrConfidence {}

impl magnus::TryConvert for OcrConfidence {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrConfidence as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrConfidence: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrConfidence>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrConfidence: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrConfidence {}

impl Default for OcrConfidence {
    fn default() -> Self {
        kreuzberg::OcrConfidence::default().into()
    }
}

impl OcrConfidence {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            detection: kwargs
                .get(ruby.to_symbol("detection"))
                .and_then(|v| f64::try_convert(v).ok()),
            recognition: kwargs
                .get(ruby.to_symbol("recognition"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn detection(&self) -> Option<f64> {
        self.detection
    }

    fn recognition(&self) -> f64 {
        self.recognition
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OcrRotation")]
pub struct OcrRotation {
    angle_degrees: f64,
    confidence: Option<f64>,
}

unsafe impl IntoValueFromNative for OcrRotation {}

impl magnus::TryConvert for OcrRotation {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrRotation as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrRotation: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrRotation>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrRotation: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrRotation {}

impl OcrRotation {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            angle_degrees: kwargs
                .get(ruby.to_symbol("angle_degrees"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            confidence: kwargs
                .get(ruby.to_symbol("confidence"))
                .and_then(|v| f64::try_convert(v).ok()),
        })
    }

    fn angle_degrees(&self) -> f64 {
        self.angle_degrees
    }

    fn confidence(&self) -> Option<f64> {
        self.confidence
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::OcrElement")]
pub struct OcrElement {
    text: String,
    geometry: OcrBoundingGeometry,
    confidence: OcrConfidence,
    level: OcrElementLevel,
    rotation: Option<OcrRotation>,
    page_number: u32,
    parent_id: Option<String>,
    backend_metadata: HashMap<String, String>,
}

unsafe impl IntoValueFromNative for OcrElement {}

impl magnus::TryConvert for OcrElement {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrElement as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrElement: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrElement>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrElement: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrElement {}

impl Default for OcrElement {
    fn default() -> Self {
        Self {
            text: String::new(),
            geometry: Default::default(),
            confidence: Default::default(),
            level: Default::default(),
            rotation: None,
            page_number: 0,
            parent_id: None,
            backend_metadata: Default::default(),
        }
    }
}

impl OcrElement {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            geometry: kwargs
                .get(ruby.to_symbol("geometry"))
                .and_then(|v| OcrBoundingGeometry::try_convert(v).ok())
                .unwrap_or_default(),
            confidence: kwargs
                .get(ruby.to_symbol("confidence"))
                .and_then(|v| OcrConfidence::try_convert(v).ok())
                .unwrap_or_default(),
            level: kwargs
                .get(ruby.to_symbol("level"))
                .and_then(|v| OcrElementLevel::try_convert(v).ok())
                .unwrap_or_default(),
            rotation: kwargs
                .get(ruby.to_symbol("rotation"))
                .and_then(|v| OcrRotation::try_convert(v).ok()),
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            parent_id: kwargs
                .get(ruby.to_symbol("parent_id"))
                .and_then(|v| String::try_convert(v).ok()),
            backend_metadata: kwargs
                .get(ruby.to_symbol("backend_metadata"))
                .and_then(|v| <HashMap<String, String>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn geometry(&self) -> OcrBoundingGeometry {
        self.geometry.clone()
    }

    fn confidence(&self) -> OcrConfidence {
        self.confidence.clone()
    }

    fn level(&self) -> OcrElementLevel {
        self.level.clone()
    }

    fn rotation(&self) -> Option<OcrRotation> {
        self.rotation.clone()
    }

    fn page_number(&self) -> u32 {
        self.page_number
    }

    fn parent_id(&self) -> Option<String> {
        self.parent_id.clone()
    }

    fn backend_metadata(&self) -> HashMap<String, String> {
        self.backend_metadata.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::OcrElementConfig")]
pub struct OcrElementConfig {
    include_elements: bool,
    min_level: OcrElementLevel,
    min_confidence: f64,
    build_hierarchy: bool,
}

unsafe impl IntoValueFromNative for OcrElementConfig {}

impl magnus::TryConvert for OcrElementConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OcrElementConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrElementConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<OcrElementConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OcrElementConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OcrElementConfig {}

impl Default for OcrElementConfig {
    fn default() -> Self {
        Self {
            include_elements: false,
            min_level: Default::default(),
            min_confidence: 0.0,
            build_hierarchy: false,
        }
    }
}

impl OcrElementConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            include_elements: kwargs
                .get(ruby.to_symbol("include_elements"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            min_level: kwargs
                .get(ruby.to_symbol("min_level"))
                .and_then(|v| OcrElementLevel::try_convert(v).ok())
                .unwrap_or_default(),
            min_confidence: kwargs
                .get(ruby.to_symbol("min_confidence"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            build_hierarchy: kwargs
                .get(ruby.to_symbol("build_hierarchy"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn include_elements(&self) -> bool {
        self.include_elements
    }

    fn min_level(&self) -> OcrElementLevel {
        self.min_level.clone()
    }

    fn min_confidence(&self) -> f64 {
        self.min_confidence
    }

    fn build_hierarchy(&self) -> bool {
        self.build_hierarchy
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PageStructure")]
pub struct PageStructure {
    total_count: u32,
    unit_type: PageUnitType,
    boundaries: Option<Vec<PageBoundary>>,
    pages: Option<Vec<PageInfo>>,
}

unsafe impl IntoValueFromNative for PageStructure {}

impl magnus::TryConvert for PageStructure {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PageStructure as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PageStructure: {}", e),
                )
            })?
        };
        serde_json::from_str::<PageStructure>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PageStructure: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PageStructure {}

impl PageStructure {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            total_count: kwargs
                .get(ruby.to_symbol("total_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            unit_type: kwargs
                .get(ruby.to_symbol("unit_type"))
                .and_then(|v| PageUnitType::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: unit_type",
                    )
                })?,
            boundaries: kwargs
                .get(ruby.to_symbol("boundaries"))
                .and_then(|v| <Vec<PageBoundary>>::try_convert(v).ok()),
            pages: kwargs
                .get(ruby.to_symbol("pages"))
                .and_then(|v| <Vec<PageInfo>>::try_convert(v).ok()),
        })
    }

    fn total_count(&self) -> u32 {
        self.total_count
    }

    fn unit_type(&self) -> PageUnitType {
        self.unit_type.clone()
    }

    fn boundaries(&self) -> Option<Vec<PageBoundary>> {
        self.boundaries.clone()
    }

    fn pages(&self) -> Option<Vec<PageInfo>> {
        self.pages.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PageBoundary")]
pub struct PageBoundary {
    byte_start: usize,
    byte_end: usize,
    page_number: u32,
}

unsafe impl IntoValueFromNative for PageBoundary {}

impl magnus::TryConvert for PageBoundary {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PageBoundary as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PageBoundary: {}", e),
                )
            })?
        };
        serde_json::from_str::<PageBoundary>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PageBoundary: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PageBoundary {}

impl PageBoundary {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            byte_start: kwargs
                .get(ruby.to_symbol("byte_start"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            byte_end: kwargs
                .get(ruby.to_symbol("byte_end"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn byte_start(&self) -> usize {
        self.byte_start
    }

    fn byte_end(&self) -> usize {
        self.byte_end
    }

    fn page_number(&self) -> u32 {
        self.page_number
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PageInfo")]
pub struct PageInfo {
    number: u32,
    title: Option<String>,
    dimensions: Option<Vec<f64>>,
    image_count: Option<u32>,
    table_count: Option<u32>,
    hidden: Option<bool>,
    is_blank: Option<bool>,
    has_vector_graphics: bool,
}

unsafe impl IntoValueFromNative for PageInfo {}

impl magnus::TryConvert for PageInfo {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PageInfo as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PageInfo: {}", e),
                )
            })?
        };
        serde_json::from_str::<PageInfo>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PageInfo: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PageInfo {}

impl Default for PageInfo {
    fn default() -> Self {
        Self {
            number: 0,
            title: None,
            dimensions: None,
            image_count: None,
            table_count: None,
            hidden: None,
            is_blank: None,
            has_vector_graphics: false,
        }
    }
}

impl PageInfo {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            number: kwargs
                .get(ruby.to_symbol("number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            title: kwargs
                .get(ruby.to_symbol("title"))
                .and_then(|v| String::try_convert(v).ok()),
            dimensions: kwargs
                .get(ruby.to_symbol("dimensions"))
                .and_then(|v| <Vec<f64>>::try_convert(v).ok()),
            image_count: kwargs
                .get(ruby.to_symbol("image_count"))
                .and_then(|v| u32::try_convert(v).ok()),
            table_count: kwargs
                .get(ruby.to_symbol("table_count"))
                .and_then(|v| u32::try_convert(v).ok()),
            hidden: kwargs
                .get(ruby.to_symbol("hidden"))
                .and_then(|v| bool::try_convert(v).ok()),
            is_blank: kwargs
                .get(ruby.to_symbol("is_blank"))
                .and_then(|v| bool::try_convert(v).ok()),
            has_vector_graphics: kwargs
                .get(ruby.to_symbol("has_vector_graphics"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(false),
        })
    }

    fn number(&self) -> u32 {
        self.number
    }

    fn title(&self) -> Option<String> {
        self.title.clone()
    }

    fn dimensions(&self) -> Option<Vec<f64>> {
        self.dimensions.clone()
    }

    fn image_count(&self) -> Option<u32> {
        self.image_count
    }

    fn table_count(&self) -> Option<u32> {
        self.table_count
    }

    fn hidden(&self) -> Option<bool> {
        self.hidden
    }

    fn is_blank(&self) -> Option<bool> {
        self.is_blank
    }

    fn has_vector_graphics(&self) -> bool {
        self.has_vector_graphics
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PageContent")]
pub struct PageContent {
    page_number: u32,
    content: String,
    tables: Vec<Table>,
    image_indices: Vec<u32>,
    hierarchy: Option<PageHierarchy>,
    is_blank: Option<bool>,
    layout_regions: Option<Vec<LayoutRegion>>,
    speaker_notes: Option<String>,
    section_name: Option<String>,
    sheet_name: Option<String>,
}

unsafe impl IntoValueFromNative for PageContent {}

impl magnus::TryConvert for PageContent {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PageContent as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PageContent: {}", e),
                )
            })?
        };
        serde_json::from_str::<PageContent>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PageContent: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PageContent {}

impl Default for PageContent {
    fn default() -> Self {
        Self {
            page_number: 0,
            content: String::new(),
            tables: vec![],
            image_indices: vec![],
            hierarchy: None,
            is_blank: None,
            layout_regions: None,
            speaker_notes: None,
            section_name: None,
            sheet_name: None,
        }
    }
}

impl PageContent {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            tables: kwargs
                .get(ruby.to_symbol("tables"))
                .and_then(|v| <Vec<Table>>::try_convert(v).ok())
                .unwrap_or(vec![]),
            image_indices: kwargs
                .get(ruby.to_symbol("image_indices"))
                .and_then(|v| <Vec<u32>>::try_convert(v).ok())
                .unwrap_or(vec![]),
            hierarchy: kwargs
                .get(ruby.to_symbol("hierarchy"))
                .and_then(|v| PageHierarchy::try_convert(v).ok()),
            is_blank: kwargs
                .get(ruby.to_symbol("is_blank"))
                .and_then(|v| bool::try_convert(v).ok()),
            layout_regions: kwargs
                .get(ruby.to_symbol("layout_regions"))
                .and_then(|v| <Vec<LayoutRegion>>::try_convert(v).ok()),
            speaker_notes: kwargs
                .get(ruby.to_symbol("speaker_notes"))
                .and_then(|v| String::try_convert(v).ok()),
            section_name: kwargs
                .get(ruby.to_symbol("section_name"))
                .and_then(|v| String::try_convert(v).ok()),
            sheet_name: kwargs
                .get(ruby.to_symbol("sheet_name"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

    fn page_number(&self) -> u32 {
        self.page_number
    }

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

    fn tables(&self) -> Vec<Table> {
        self.tables.clone()
    }

    fn image_indices(&self) -> Vec<u32> {
        self.image_indices.clone()
    }

    fn hierarchy(&self) -> Option<PageHierarchy> {
        self.hierarchy.clone()
    }

    fn is_blank(&self) -> Option<bool> {
        self.is_blank
    }

    fn layout_regions(&self) -> Option<Vec<LayoutRegion>> {
        self.layout_regions.clone()
    }

    fn speaker_notes(&self) -> Option<String> {
        self.speaker_notes.clone()
    }

    fn section_name(&self) -> Option<String> {
        self.section_name.clone()
    }

    fn sheet_name(&self) -> Option<String> {
        self.sheet_name.clone()
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::LayoutRegion")]
pub struct LayoutRegion {
    class_name: String,
    confidence: f64,
    bounding_box: BoundingBox,
    area_fraction: f64,
}

unsafe impl IntoValueFromNative for LayoutRegion {}

impl magnus::TryConvert for LayoutRegion {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LayoutRegion as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LayoutRegion: {}", e),
                )
            })?
        };
        serde_json::from_str::<LayoutRegion>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LayoutRegion: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LayoutRegion {}

impl Default for LayoutRegion {
    fn default() -> Self {
        kreuzberg::LayoutRegion::default().into()
    }
}

impl LayoutRegion {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            class_name: kwargs
                .get(ruby.to_symbol("class_name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            confidence: kwargs
                .get(ruby.to_symbol("confidence"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
            bounding_box: kwargs
                .get(ruby.to_symbol("bounding_box"))
                .and_then(|v| BoundingBox::try_convert(v).ok())
                .unwrap_or_default(),
            area_fraction: kwargs
                .get(ruby.to_symbol("area_fraction"))
                .and_then(|v| f64::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn confidence(&self) -> f64 {
        self.confidence
    }

    fn bounding_box(&self) -> BoundingBox {
        self.bounding_box.clone()
    }

    fn area_fraction(&self) -> f64 {
        self.area_fraction
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::PageHierarchy")]
pub struct PageHierarchy {
    block_count: u32,
    blocks: Vec<HierarchicalBlock>,
}

unsafe impl IntoValueFromNative for PageHierarchy {}

impl magnus::TryConvert for PageHierarchy {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PageHierarchy as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PageHierarchy: {}", e),
                )
            })?
        };
        serde_json::from_str::<PageHierarchy>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PageHierarchy: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PageHierarchy {}

impl Default for PageHierarchy {
    fn default() -> Self {
        Self {
            block_count: 0,
            blocks: vec![],
        }
    }
}

impl PageHierarchy {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            block_count: kwargs
                .get(ruby.to_symbol("block_count"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            blocks: kwargs
                .get(ruby.to_symbol("blocks"))
                .and_then(|v| <Vec<HierarchicalBlock>>::try_convert(v).ok())
                .unwrap_or(vec![]),
        })
    }

    fn block_count(&self) -> u32 {
        self.block_count
    }

    fn blocks(&self) -> Vec<HierarchicalBlock> {
        self.blocks.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::HierarchicalBlock")]
pub struct HierarchicalBlock {
    text: String,
    font_size: f32,
    level: String,
    bbox: Option<Vec<f32>>,
}

unsafe impl IntoValueFromNative for HierarchicalBlock {}

impl magnus::TryConvert for HierarchicalBlock {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&HierarchicalBlock as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into HierarchicalBlock: {}", e),
                )
            })?
        };
        serde_json::from_str::<HierarchicalBlock>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize HierarchicalBlock: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for HierarchicalBlock {}

impl HierarchicalBlock {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            font_size: kwargs
                .get(ruby.to_symbol("font_size"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            level: kwargs
                .get(ruby.to_symbol("level"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            bbox: kwargs
                .get(ruby.to_symbol("bbox"))
                .and_then(|v| <Vec<f32>>::try_convert(v).ok()),
        })
    }

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

    fn font_size(&self) -> f32 {
        self.font_size
    }

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

    fn bbox(&self) -> Option<Vec<f32>> {
        self.bbox.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::CellChange")]
pub struct CellChange {
    row: usize,
    col: usize,
    from: String,
    to: String,
}

unsafe impl IntoValueFromNative for CellChange {}

impl magnus::TryConvert for CellChange {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&CellChange as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into CellChange: {}", e),
                )
            })?
        };
        serde_json::from_str::<CellChange>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize CellChange: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for CellChange {}

impl CellChange {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            row: kwargs
                .get(ruby.to_symbol("row"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            col: kwargs
                .get(ruby.to_symbol("col"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            from: kwargs
                .get(ruby.to_symbol("from"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            to: kwargs
                .get(ruby.to_symbol("to"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn row(&self) -> usize {
        self.row
    }

    fn col(&self) -> usize {
        self.col
    }

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

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DocumentRevision")]
pub struct DocumentRevision {
    revision_id: String,
    author: Option<String>,
    timestamp: Option<String>,
    kind: RevisionKind,
    anchor: Option<RevisionAnchor>,
    delta: RevisionDelta,
}

unsafe impl IntoValueFromNative for DocumentRevision {}

impl magnus::TryConvert for DocumentRevision {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DocumentRevision as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DocumentRevision: {}", e),
                )
            })?
        };
        serde_json::from_str::<DocumentRevision>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DocumentRevision: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DocumentRevision {}

impl DocumentRevision {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            revision_id: kwargs
                .get(ruby.to_symbol("revision_id"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            author: kwargs
                .get(ruby.to_symbol("author"))
                .and_then(|v| String::try_convert(v).ok()),
            timestamp: kwargs
                .get(ruby.to_symbol("timestamp"))
                .and_then(|v| String::try_convert(v).ok()),
            kind: kwargs
                .get(ruby.to_symbol("kind"))
                .and_then(|v| RevisionKind::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: kind",
                    )
                })?,
            anchor: kwargs
                .get(ruby.to_symbol("anchor"))
                .and_then(|v| RevisionAnchor::try_convert(v).ok()),
            delta: kwargs
                .get(ruby.to_symbol("delta"))
                .and_then(|v| RevisionDelta::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: delta",
                    )
                })?,
        })
    }

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

    fn author(&self) -> Option<String> {
        self.author.clone()
    }

    fn timestamp(&self) -> Option<String> {
        self.timestamp.clone()
    }

    fn kind(&self) -> RevisionKind {
        self.kind.clone()
    }

    fn anchor(&self) -> Option<RevisionAnchor> {
        self.anchor.clone()
    }

    fn delta(&self) -> RevisionDelta {
        self.delta.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::RevisionDelta")]
pub struct RevisionDelta {
    content: Vec<DiffLine>,
    table_changes: Vec<CellChange>,
}

unsafe impl IntoValueFromNative for RevisionDelta {}

impl magnus::TryConvert for RevisionDelta {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&RevisionDelta as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into RevisionDelta: {}", e),
                )
            })?
        };
        serde_json::from_str::<RevisionDelta>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize RevisionDelta: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for RevisionDelta {}

impl Default for RevisionDelta {
    fn default() -> Self {
        Self {
            content: vec![],
            table_changes: vec![],
        }
    }
}

impl RevisionDelta {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| <Vec<DiffLine>>::try_convert(v).ok())
                .unwrap_or_default(),
            table_changes: kwargs
                .get(ruby.to_symbol("table_changes"))
                .and_then(|v| <Vec<CellChange>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn content(&self) -> Vec<DiffLine> {
        self.content.clone()
    }

    fn table_changes(&self) -> Vec<CellChange> {
        self.table_changes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::Table")]
pub struct Table {
    cells: Vec<Vec<String>>,
    markdown: String,
    page_number: u32,
    bounding_box: Option<BoundingBox>,
}

unsafe impl IntoValueFromNative for Table {}

impl magnus::TryConvert for Table {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&Table as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into Table: {}", e),
                )
            })?
        };
        serde_json::from_str::<Table>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize Table: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for Table {}

impl Default for Table {
    fn default() -> Self {
        kreuzberg::Table::default().into()
    }
}

impl Table {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            cells: kwargs
                .get(ruby.to_symbol("cells"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok())
                .unwrap_or_default(),
            markdown: kwargs
                .get(ruby.to_symbol("markdown"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            page_number: kwargs
                .get(ruby.to_symbol("page_number"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            bounding_box: kwargs
                .get(ruby.to_symbol("bounding_box"))
                .and_then(|v| BoundingBox::try_convert(v).ok()),
        })
    }

    fn cells(&self) -> Vec<Vec<String>> {
        self.cells.clone()
    }

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

    fn page_number(&self) -> u32 {
        self.page_number
    }

    fn bounding_box(&self) -> Option<BoundingBox> {
        self.bounding_box.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::TableCell")]
pub struct TableCell {
    content: String,
    row_span: u32,
    col_span: u32,
    is_header: bool,
}

unsafe impl IntoValueFromNative for TableCell {}

impl magnus::TryConvert for TableCell {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TableCell as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TableCell: {}", e),
                )
            })?
        };
        serde_json::from_str::<TableCell>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TableCell: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TableCell {}

impl Default for TableCell {
    fn default() -> Self {
        kreuzberg::TableCell::default().into()
    }
}

impl TableCell {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content: kwargs
                .get(ruby.to_symbol("content"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            row_span: kwargs
                .get(ruby.to_symbol("row_span"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            col_span: kwargs
                .get(ruby.to_symbol("col_span"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            is_header: kwargs
                .get(ruby.to_symbol("is_header"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn row_span(&self) -> u32 {
        self.row_span
    }

    fn col_span(&self) -> u32 {
        self.col_span
    }

    fn is_header(&self) -> bool {
        self.is_header
    }

    #[allow(clippy::should_implement_trait)]
    fn to_s(&self) -> String {
        self.content.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ExtractedUri")]
pub struct ExtractedUri {
    url: String,
    label: Option<String>,
    page: Option<u32>,
    kind: UriKind,
}

unsafe impl IntoValueFromNative for ExtractedUri {}

impl magnus::TryConvert for ExtractedUri {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExtractedUri as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExtractedUri: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExtractedUri>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExtractedUri: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExtractedUri {}

impl ExtractedUri {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            url: kwargs
                .get(ruby.to_symbol("url"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            label: kwargs
                .get(ruby.to_symbol("label"))
                .and_then(|v| String::try_convert(v).ok()),
            page: kwargs
                .get(ruby.to_symbol("page"))
                .and_then(|v| u32::try_convert(v).ok()),
            kind: kwargs
                .get(ruby.to_symbol("kind"))
                .and_then(|v| UriKind::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: kind",
                    )
                })?,
        })
    }

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

    fn label(&self) -> Option<String> {
        self.label.clone()
    }

    fn page(&self) -> Option<u32> {
        self.page
    }

    fn kind(&self) -> UriKind {
        self.kind.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DetectResponse")]
pub struct DetectResponse {
    mime_type: String,
    filename: Option<String>,
}

unsafe impl IntoValueFromNative for DetectResponse {}

impl magnus::TryConvert for DetectResponse {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DetectResponse as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DetectResponse: {}", e),
                )
            })?
        };
        serde_json::from_str::<DetectResponse>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DetectResponse: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DetectResponse {}

impl DetectResponse {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            filename: kwargs
                .get(ruby.to_symbol("filename"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

    fn filename(&self) -> Option<String> {
        self.filename.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::DiffOptions")]
pub struct DiffOptions {
    include_metadata: bool,
    include_embedded: bool,
    max_content_chars: Option<usize>,
}

unsafe impl IntoValueFromNative for DiffOptions {}

impl magnus::TryConvert for DiffOptions {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DiffOptions as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DiffOptions: {}", e),
                )
            })?
        };
        serde_json::from_str::<DiffOptions>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DiffOptions: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DiffOptions {}

impl Default for DiffOptions {
    fn default() -> Self {
        kreuzberg::DiffOptions::default().into()
    }
}

impl DiffOptions {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            include_metadata: kwargs
                .get(ruby.to_symbol("include_metadata"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            include_embedded: kwargs
                .get(ruby.to_symbol("include_embedded"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or(true),
            max_content_chars: kwargs
                .get(ruby.to_symbol("max_content_chars"))
                .and_then(|v| usize::try_convert(v).ok()),
        })
    }

    fn include_metadata(&self) -> bool {
        self.include_metadata
    }

    fn include_embedded(&self) -> bool {
        self.include_embedded
    }

    fn max_content_chars(&self) -> Option<usize> {
        self.max_content_chars
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ExtractionDiff")]
pub struct ExtractionDiff {
    content_diff: Vec<DiffHunk>,
    tables_added: Vec<Table>,
    tables_removed: Vec<Table>,
    tables_changed: Vec<TableDiff>,
    metadata_changed: String,
    embedded_changes: EmbeddedChanges,
}

unsafe impl IntoValueFromNative for ExtractionDiff {}

impl magnus::TryConvert for ExtractionDiff {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ExtractionDiff as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ExtractionDiff: {}", e),
                )
            })?
        };
        serde_json::from_str::<ExtractionDiff>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ExtractionDiff: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ExtractionDiff {}

impl ExtractionDiff {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            content_diff: kwargs
                .get(ruby.to_symbol("content_diff"))
                .and_then(|v| <Vec<DiffHunk>>::try_convert(v).ok())
                .unwrap_or_default(),
            tables_added: kwargs
                .get(ruby.to_symbol("tables_added"))
                .and_then(|v| <Vec<Table>>::try_convert(v).ok())
                .unwrap_or_default(),
            tables_removed: kwargs
                .get(ruby.to_symbol("tables_removed"))
                .and_then(|v| <Vec<Table>>::try_convert(v).ok())
                .unwrap_or_default(),
            tables_changed: kwargs
                .get(ruby.to_symbol("tables_changed"))
                .and_then(|v| <Vec<TableDiff>>::try_convert(v).ok())
                .unwrap_or_default(),
            metadata_changed: kwargs
                .get(ruby.to_symbol("metadata_changed"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            embedded_changes: kwargs
                .get(ruby.to_symbol("embedded_changes"))
                .and_then(|v| EmbeddedChanges::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: embedded_changes",
                    )
                })?,
        })
    }

    fn content_diff(&self) -> Vec<DiffHunk> {
        self.content_diff.clone()
    }

    fn tables_added(&self) -> Vec<Table> {
        self.tables_added.clone()
    }

    fn tables_removed(&self) -> Vec<Table> {
        self.tables_removed.clone()
    }

    fn tables_changed(&self) -> Vec<TableDiff> {
        self.tables_changed.clone()
    }

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

    fn embedded_changes(&self) -> EmbeddedChanges {
        self.embedded_changes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DiffHunk")]
pub struct DiffHunk {
    from_line: usize,
    from_count: usize,
    to_line: usize,
    to_count: usize,
    lines: Vec<DiffLine>,
}

unsafe impl IntoValueFromNative for DiffHunk {}

impl magnus::TryConvert for DiffHunk {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DiffHunk as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DiffHunk: {}", e),
                )
            })?
        };
        serde_json::from_str::<DiffHunk>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DiffHunk: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DiffHunk {}

impl DiffHunk {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            from_line: kwargs
                .get(ruby.to_symbol("from_line"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            from_count: kwargs
                .get(ruby.to_symbol("from_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            to_line: kwargs
                .get(ruby.to_symbol("to_line"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            to_count: kwargs
                .get(ruby.to_symbol("to_count"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            lines: kwargs
                .get(ruby.to_symbol("lines"))
                .and_then(|v| <Vec<DiffLine>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn from_line(&self) -> usize {
        self.from_line
    }

    fn from_count(&self) -> usize {
        self.from_count
    }

    fn to_line(&self) -> usize {
        self.to_line
    }

    fn to_count(&self) -> usize {
        self.to_count
    }

    fn lines(&self) -> Vec<DiffLine> {
        self.lines.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::TableDiff")]
pub struct TableDiff {
    from_index: usize,
    to_index: usize,
    cell_changes: Vec<CellChange>,
}

unsafe impl IntoValueFromNative for TableDiff {}

impl magnus::TryConvert for TableDiff {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&TableDiff as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into TableDiff: {}", e),
                )
            })?
        };
        serde_json::from_str::<TableDiff>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize TableDiff: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for TableDiff {}

impl TableDiff {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            from_index: kwargs
                .get(ruby.to_symbol("from_index"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            to_index: kwargs
                .get(ruby.to_symbol("to_index"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            cell_changes: kwargs
                .get(ruby.to_symbol("cell_changes"))
                .and_then(|v| <Vec<CellChange>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn from_index(&self) -> usize {
        self.from_index
    }

    fn to_index(&self) -> usize {
        self.to_index
    }

    fn cell_changes(&self) -> Vec<CellChange> {
        self.cell_changes.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::EmbeddedChanges")]
pub struct EmbeddedChanges {
    added: Vec<ArchiveEntry>,
    removed: Vec<ArchiveEntry>,
    changed: Vec<EmbeddedDiff>,
}

unsafe impl IntoValueFromNative for EmbeddedChanges {}

impl magnus::TryConvert for EmbeddedChanges {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmbeddedChanges as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmbeddedChanges: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmbeddedChanges>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmbeddedChanges: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmbeddedChanges {}

impl EmbeddedChanges {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            added: kwargs
                .get(ruby.to_symbol("added"))
                .and_then(|v| <Vec<ArchiveEntry>>::try_convert(v).ok())
                .unwrap_or_default(),
            removed: kwargs
                .get(ruby.to_symbol("removed"))
                .and_then(|v| <Vec<ArchiveEntry>>::try_convert(v).ok())
                .unwrap_or_default(),
            changed: kwargs
                .get(ruby.to_symbol("changed"))
                .and_then(|v| <Vec<EmbeddedDiff>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn added(&self) -> Vec<ArchiveEntry> {
        self.added.clone()
    }

    fn removed(&self) -> Vec<ArchiveEntry> {
        self.removed.clone()
    }

    fn changed(&self) -> Vec<EmbeddedDiff> {
        self.changed.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::EmbeddedDiff")]
pub struct EmbeddedDiff {
    path: String,
    diff: ExtractionDiff,
}

unsafe impl IntoValueFromNative for EmbeddedDiff {}

impl magnus::TryConvert for EmbeddedDiff {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmbeddedDiff as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmbeddedDiff: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmbeddedDiff>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmbeddedDiff: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmbeddedDiff {}

impl EmbeddedDiff {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            path: kwargs
                .get(ruby.to_symbol("path"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            diff: kwargs
                .get(ruby.to_symbol("diff"))
                .and_then(|v| ExtractionDiff::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: diff",
                    )
                })?,
        })
    }

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

    fn diff(&self) -> ExtractionDiff {
        self.diff.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::EmbeddingPreset")]
pub struct EmbeddingPreset {
    name: String,
    chunk_size: usize,
    overlap: usize,
    model_repo: String,
    pooling: String,
    model_file: String,
    dimensions: usize,
    description: String,
}

unsafe impl IntoValueFromNative for EmbeddingPreset {}

impl magnus::TryConvert for EmbeddingPreset {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmbeddingPreset as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmbeddingPreset: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmbeddingPreset>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmbeddingPreset: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmbeddingPreset {}

impl EmbeddingPreset {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            name: kwargs
                .get(ruby.to_symbol("name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            chunk_size: kwargs
                .get(ruby.to_symbol("chunk_size"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            overlap: kwargs
                .get(ruby.to_symbol("overlap"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            model_repo: kwargs
                .get(ruby.to_symbol("model_repo"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            pooling: kwargs
                .get(ruby.to_symbol("pooling"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            model_file: kwargs
                .get(ruby.to_symbol("model_file"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            dimensions: kwargs
                .get(ruby.to_symbol("dimensions"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            description: kwargs
                .get(ruby.to_symbol("description"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn chunk_size(&self) -> usize {
        self.chunk_size
    }

    fn overlap(&self) -> usize {
        self.overlap
    }

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

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

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

    fn dimensions(&self) -> usize {
        self.dimensions
    }

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::YakeParams")]
pub struct YakeParams {
    window_size: usize,
}

unsafe impl IntoValueFromNative for YakeParams {}

impl magnus::TryConvert for YakeParams {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&YakeParams as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into YakeParams: {}", e),
                )
            })?
        };
        serde_json::from_str::<YakeParams>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize YakeParams: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for YakeParams {}

impl Default for YakeParams {
    fn default() -> Self {
        kreuzberg::YakeParams::default().into()
    }
}

impl YakeParams {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            window_size: kwargs
                .get(ruby.to_symbol("window_size"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(2),
        })
    }

    fn window_size(&self) -> usize {
        self.window_size
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::RakeParams")]
pub struct RakeParams {
    min_word_length: usize,
    max_words_per_phrase: usize,
}

unsafe impl IntoValueFromNative for RakeParams {}

impl magnus::TryConvert for RakeParams {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&RakeParams as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into RakeParams: {}", e),
                )
            })?
        };
        serde_json::from_str::<RakeParams>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize RakeParams: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for RakeParams {}

impl Default for RakeParams {
    fn default() -> Self {
        kreuzberg::RakeParams::default().into()
    }
}

impl RakeParams {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            min_word_length: kwargs
                .get(ruby.to_symbol("min_word_length"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(1),
            max_words_per_phrase: kwargs
                .get(ruby.to_symbol("max_words_per_phrase"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(3),
        })
    }

    fn min_word_length(&self) -> usize {
        self.min_word_length
    }

    fn max_words_per_phrase(&self) -> usize {
        self.max_words_per_phrase
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::KeywordConfig")]
pub struct KeywordConfig {
    algorithm: KeywordAlgorithm,
    max_keywords: usize,
    min_score: f32,
    ngram_range: Vec<usize>,
    language: Option<String>,
    yake_params: Option<YakeParams>,
    rake_params: Option<RakeParams>,
}

unsafe impl IntoValueFromNative for KeywordConfig {}

impl magnus::TryConvert for KeywordConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&KeywordConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into KeywordConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<KeywordConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize KeywordConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for KeywordConfig {}

impl Default for KeywordConfig {
    fn default() -> Self {
        Self {
            algorithm: Default::default(),
            max_keywords: 10,
            min_score: 0.0,
            ngram_range: vec![],
            language: None,
            yake_params: None,
            rake_params: None,
        }
    }
}

impl KeywordConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            algorithm: kwargs
                .get(ruby.to_symbol("algorithm"))
                .and_then(|v| KeywordAlgorithm::try_convert(v).ok())
                .unwrap_or_default(),
            max_keywords: kwargs
                .get(ruby.to_symbol("max_keywords"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or(10),
            min_score: kwargs
                .get(ruby.to_symbol("min_score"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or(0.0),
            ngram_range: kwargs
                .get(ruby.to_symbol("ngram_range"))
                .and_then(|v| <Vec<usize>>::try_convert(v).ok())
                .unwrap_or_default(),
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok()),
            yake_params: kwargs
                .get(ruby.to_symbol("yake_params"))
                .and_then(|v| YakeParams::try_convert(v).ok()),
            rake_params: kwargs
                .get(ruby.to_symbol("rake_params"))
                .and_then(|v| RakeParams::try_convert(v).ok()),
        })
    }

    fn algorithm(&self) -> KeywordAlgorithm {
        self.algorithm.clone()
    }

    fn max_keywords(&self) -> usize {
        self.max_keywords
    }

    fn min_score(&self) -> f32 {
        self.min_score
    }

    fn ngram_range(&self) -> Vec<usize> {
        self.ngram_range.clone()
    }

    fn language(&self) -> Option<String> {
        self.language.clone()
    }

    fn yake_params(&self) -> Option<YakeParams> {
        self.yake_params.clone()
    }

    fn rake_params(&self) -> Option<RakeParams> {
        self.rake_params.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::Keyword")]
pub struct Keyword {
    text: String,
    score: f32,
    algorithm: KeywordAlgorithm,
    positions: Option<Vec<usize>>,
}

unsafe impl IntoValueFromNative for Keyword {}

impl magnus::TryConvert for Keyword {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&Keyword as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into Keyword: {}", e),
                )
            })?
        };
        serde_json::from_str::<Keyword>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize Keyword: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for Keyword {}

impl Keyword {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            text: kwargs
                .get(ruby.to_symbol("text"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            score: kwargs
                .get(ruby.to_symbol("score"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            algorithm: kwargs
                .get(ruby.to_symbol("algorithm"))
                .and_then(|v| KeywordAlgorithm::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: algorithm",
                    )
                })?,
            positions: kwargs
                .get(ruby.to_symbol("positions"))
                .and_then(|v| <Vec<usize>>::try_convert(v).ok()),
        })
    }

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

    fn score(&self) -> f32 {
        self.score
    }

    fn algorithm(&self) -> KeywordAlgorithm {
        self.algorithm.clone()
    }

    fn positions(&self) -> Option<Vec<usize>> {
        self.positions.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PaddleOcrConfig")]
pub struct PaddleOcrConfig {
    language: String,
    cache_dir: Option<String>,
    use_angle_cls: bool,
    enable_table_detection: bool,
    det_db_thresh: f32,
    det_db_box_thresh: f32,
    det_db_unclip_ratio: f32,
    det_limit_side_len: u32,
    rec_batch_num: u32,
    padding: u32,
    drop_score: f32,
    model_tier: String,
}

unsafe impl IntoValueFromNative for PaddleOcrConfig {}

impl magnus::TryConvert for PaddleOcrConfig {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PaddleOcrConfig as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PaddleOcrConfig: {}", e),
                )
            })?
        };
        serde_json::from_str::<PaddleOcrConfig>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PaddleOcrConfig: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PaddleOcrConfig {}

impl Default for PaddleOcrConfig {
    fn default() -> Self {
        kreuzberg::PaddleOcrConfig::default().into()
    }
}

impl PaddleOcrConfig {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            language: kwargs
                .get(ruby.to_symbol("language"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            cache_dir: kwargs
                .get(ruby.to_symbol("cache_dir"))
                .and_then(|v| String::try_convert(v).ok()),
            use_angle_cls: kwargs
                .get(ruby.to_symbol("use_angle_cls"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            enable_table_detection: kwargs
                .get(ruby.to_symbol("enable_table_detection"))
                .and_then(|v| bool::try_convert(v).ok())
                .unwrap_or_default(),
            det_db_thresh: kwargs
                .get(ruby.to_symbol("det_db_thresh"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            det_db_box_thresh: kwargs
                .get(ruby.to_symbol("det_db_box_thresh"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            det_db_unclip_ratio: kwargs
                .get(ruby.to_symbol("det_db_unclip_ratio"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            det_limit_side_len: kwargs
                .get(ruby.to_symbol("det_limit_side_len"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            rec_batch_num: kwargs
                .get(ruby.to_symbol("rec_batch_num"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            padding: kwargs
                .get(ruby.to_symbol("padding"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            drop_score: kwargs
                .get(ruby.to_symbol("drop_score"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            model_tier: kwargs
                .get(ruby.to_symbol("model_tier"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

    fn cache_dir(&self) -> Option<String> {
        self.cache_dir.clone()
    }

    fn use_angle_cls(&self) -> bool {
        self.use_angle_cls
    }

    fn enable_table_detection(&self) -> bool {
        self.enable_table_detection
    }

    fn det_db_thresh(&self) -> f32 {
        self.det_db_thresh
    }

    fn det_db_box_thresh(&self) -> f32 {
        self.det_db_box_thresh
    }

    fn det_db_unclip_ratio(&self) -> f32 {
        self.det_db_unclip_ratio
    }

    fn det_limit_side_len(&self) -> u32 {
        self.det_limit_side_len
    }

    fn rec_batch_num(&self) -> u32 {
        self.rec_batch_num
    }

    fn padding(&self) -> u32 {
        self.padding
    }

    fn drop_score(&self) -> f32 {
        self.drop_score
    }

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

    fn with_cache_dir(&self, path: String) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_cache_dir(std::path::PathBuf::from(path)).into()
    }

    fn with_table_detection(&self, enable: bool) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_table_detection(enable).into()
    }

    fn with_angle_cls(&self, enable: bool) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_angle_cls(enable).into()
    }

    fn with_det_db_thresh(&self, threshold: f32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_det_db_thresh(threshold).into()
    }

    fn with_det_db_box_thresh(&self, threshold: f32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_det_db_box_thresh(threshold).into()
    }

    fn with_det_db_unclip_ratio(&self, ratio: f32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_det_db_unclip_ratio(ratio).into()
    }

    fn with_det_limit_side_len(&self, length: u32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_det_limit_side_len(length).into()
    }

    fn with_rec_batch_num(&self, batch_size: u32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_rec_batch_num(batch_size).into()
    }

    fn with_drop_score(&self, score: f32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_drop_score(score).into()
    }

    fn with_padding(&self, padding: u32) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_padding(padding).into()
    }

    fn with_model_tier(&self, tier: String) -> PaddleOcrConfig {
        let core_self = kreuzberg::PaddleOcrConfig {
            language: self.language.clone(),

            cache_dir: self.cache_dir.clone().map(Into::into),

            use_angle_cls: self.use_angle_cls,

            enable_table_detection: self.enable_table_detection,

            det_db_thresh: self.det_db_thresh,

            det_db_box_thresh: self.det_db_box_thresh,

            det_db_unclip_ratio: self.det_db_unclip_ratio,

            det_limit_side_len: self.det_limit_side_len,

            rec_batch_num: self.rec_batch_num,

            padding: self.padding,

            drop_score: self.drop_score,

            model_tier: self.model_tier.clone(),
        };
        core_self.with_model_tier(tier).into()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::ModelPaths")]
pub struct ModelPaths {
    det_model: String,
    cls_model: String,
    rec_model: String,
    dict_file: String,
}

unsafe impl IntoValueFromNative for ModelPaths {}

impl magnus::TryConvert for ModelPaths {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&ModelPaths as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ModelPaths: {}", e),
                )
            })?
        };
        serde_json::from_str::<ModelPaths>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize ModelPaths: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for ModelPaths {}

impl ModelPaths {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            det_model: kwargs
                .get(ruby.to_symbol("det_model"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            cls_model: kwargs
                .get(ruby.to_symbol("cls_model"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            rec_model: kwargs
                .get(ruby.to_symbol("rec_model"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            dict_file: kwargs
                .get(ruby.to_symbol("dict_file"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

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

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

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

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::OrientationResult")]
pub struct OrientationResult {
    degrees: u32,
    confidence: f32,
}

unsafe impl IntoValueFromNative for OrientationResult {}

impl magnus::TryConvert for OrientationResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&OrientationResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OrientationResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<OrientationResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize OrientationResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for OrientationResult {}

impl OrientationResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            degrees: kwargs
                .get(ruby.to_symbol("degrees"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            confidence: kwargs
                .get(ruby.to_symbol("confidence"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn degrees(&self) -> u32 {
        self.degrees
    }

    fn confidence(&self) -> f32 {
        self.confidence
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::BBox")]
pub struct BBox {
    x1: f32,
    y1: f32,
    x2: f32,
    y2: f32,
}

unsafe impl IntoValueFromNative for BBox {}

impl magnus::TryConvert for BBox {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&BBox as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into BBox: {}", e),
                )
            })?
        };
        serde_json::from_str::<BBox>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize BBox: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for BBox {}

impl BBox {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            x1: kwargs
                .get(ruby.to_symbol("x1"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            y1: kwargs
                .get(ruby.to_symbol("y1"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            x2: kwargs
                .get(ruby.to_symbol("x2"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            y2: kwargs
                .get(ruby.to_symbol("y2"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn x1(&self) -> f32 {
        self.x1
    }

    fn y1(&self) -> f32 {
        self.y1
    }

    fn x2(&self) -> f32 {
        self.x2
    }

    fn y2(&self) -> f32 {
        self.y2
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::LayoutDetection")]
pub struct LayoutDetection {
    class_name: LayoutClass,
    confidence: f32,
    bbox: BBox,
}

unsafe impl IntoValueFromNative for LayoutDetection {}

impl magnus::TryConvert for LayoutDetection {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&LayoutDetection as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into LayoutDetection: {}", e),
                )
            })?
        };
        serde_json::from_str::<LayoutDetection>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize LayoutDetection: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for LayoutDetection {}

impl LayoutDetection {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            class_name: kwargs
                .get(ruby.to_symbol("class_name"))
                .and_then(|v| LayoutClass::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: class_name",
                    )
                })?,
            confidence: kwargs
                .get(ruby.to_symbol("confidence"))
                .and_then(|v| f32::try_convert(v).ok())
                .unwrap_or_default(),
            bbox: kwargs
                .get(ruby.to_symbol("bbox"))
                .and_then(|v| BBox::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: bbox",
                    )
                })?,
        })
    }

    fn class_name(&self) -> LayoutClass {
        self.class_name.clone()
    }

    fn confidence(&self) -> f32 {
        self.confidence
    }

    fn bbox(&self) -> BBox {
        self.bbox.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::RecognizedTable")]
pub struct RecognizedTable {
    detection_bbox: BBox,
    cells: Vec<Vec<String>>,
    markdown: String,
}

unsafe impl IntoValueFromNative for RecognizedTable {}

impl magnus::TryConvert for RecognizedTable {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&RecognizedTable as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into RecognizedTable: {}", e),
                )
            })?
        };
        serde_json::from_str::<RecognizedTable>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize RecognizedTable: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for RecognizedTable {}

impl RecognizedTable {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            detection_bbox: kwargs
                .get(ruby.to_symbol("detection_bbox"))
                .and_then(|v| BBox::try_convert(v).ok())
                .ok_or_else(|| {
                    magnus::Error::new(
                        unsafe { magnus::Ruby::get_unchecked() }.exception_arg_error(),
                        "missing required field: detection_bbox",
                    )
                })?,
            cells: kwargs
                .get(ruby.to_symbol("cells"))
                .and_then(|v| <Vec<Vec<String>>>::try_convert(v).ok())
                .unwrap_or_default(),
            markdown: kwargs
                .get(ruby.to_symbol("markdown"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn detection_bbox(&self) -> BBox {
        self.detection_bbox.clone()
    }

    fn cells(&self) -> Vec<Vec<String>> {
        self.cells.clone()
    }

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

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::DetectionResult")]
pub struct DetectionResult {
    page_width: u32,
    page_height: u32,
    detections: Vec<LayoutDetection>,
}

unsafe impl IntoValueFromNative for DetectionResult {}

impl magnus::TryConvert for DetectionResult {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&DetectionResult as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DetectionResult: {}", e),
                )
            })?
        };
        serde_json::from_str::<DetectionResult>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize DetectionResult: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for DetectionResult {}

impl DetectionResult {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            page_width: kwargs
                .get(ruby.to_symbol("page_width"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            page_height: kwargs
                .get(ruby.to_symbol("page_height"))
                .and_then(|v| u32::try_convert(v).ok())
                .unwrap_or_default(),
            detections: kwargs
                .get(ruby.to_symbol("detections"))
                .and_then(|v| <Vec<LayoutDetection>>::try_convert(v).ok())
                .unwrap_or_default(),
        })
    }

    fn page_width(&self) -> u32 {
        self.page_width
    }

    fn page_height(&self) -> u32 {
        self.page_height
    }

    fn detections(&self) -> Vec<LayoutDetection> {
        self.detections.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[magnus::wrap(class = "Kreuzberg::EmbeddedFile")]
pub struct EmbeddedFile {
    name: String,
    data: Vec<u8>,
    compressed_size: usize,
    mime_type: Option<String>,
}

unsafe impl IntoValueFromNative for EmbeddedFile {}

impl magnus::TryConvert for EmbeddedFile {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&EmbeddedFile as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmbeddedFile: {}", e),
                )
            })?
        };
        serde_json::from_str::<EmbeddedFile>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize EmbeddedFile: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for EmbeddedFile {}

impl EmbeddedFile {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            name: kwargs
                .get(ruby.to_symbol("name"))
                .and_then(|v| String::try_convert(v).ok())
                .unwrap_or_default(),
            data: kwargs
                .get(ruby.to_symbol("data"))
                .and_then(|v| <Vec<u8>>::try_convert(v).ok())
                .unwrap_or_default(),
            compressed_size: kwargs
                .get(ruby.to_symbol("compressed_size"))
                .and_then(|v| usize::try_convert(v).ok())
                .unwrap_or_default(),
            mime_type: kwargs
                .get(ruby.to_symbol("mime_type"))
                .and_then(|v| String::try_convert(v).ok()),
        })
    }

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

    fn data(&self) -> Vec<u8> {
        self.data.clone()
    }

    fn compressed_size(&self) -> usize {
        self.compressed_size
    }

    fn mime_type(&self) -> Option<String> {
        self.mime_type.clone()
    }
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[magnus::wrap(class = "Kreuzberg::PdfMetadata")]
pub struct PdfMetadata {
    pdf_version: Option<String>,
    producer: Option<String>,
    is_encrypted: Option<bool>,
    width: Option<i64>,
    height: Option<i64>,
    page_count: Option<u32>,
}

unsafe impl IntoValueFromNative for PdfMetadata {}

impl magnus::TryConvert for PdfMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        if let Ok(r) = <&PdfMetadata as magnus::TryConvert>::try_convert(val) {
            return Ok(r.clone());
        }
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into PdfMetadata: {}", e),
                )
            })?
        };
        serde_json::from_str::<PdfMetadata>(&json_str).map_err(|e| {
            magnus::Error::new(
                unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
                format!("failed to deserialize PdfMetadata: {}", e),
            )
        })
    }
}

unsafe impl TryConvertOwned for PdfMetadata {}

impl Default for PdfMetadata {
    fn default() -> Self {
        kreuzberg::pdf::metadata::PdfMetadata::default().into()
    }
}

impl PdfMetadata {
    fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
        let (kwargs_opt,) = args.optional;
        let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
        Ok(Self {
            pdf_version: kwargs
                .get(ruby.to_symbol("pdf_version"))
                .and_then(|v| String::try_convert(v).ok()),
            producer: kwargs
                .get(ruby.to_symbol("producer"))
                .and_then(|v| String::try_convert(v).ok()),
            is_encrypted: kwargs
                .get(ruby.to_symbol("is_encrypted"))
                .and_then(|v| bool::try_convert(v).ok()),
            width: kwargs
                .get(ruby.to_symbol("width"))
                .and_then(|v| i64::try_convert(v).ok()),
            height: kwargs
                .get(ruby.to_symbol("height"))
                .and_then(|v| i64::try_convert(v).ok()),
            page_count: kwargs
                .get(ruby.to_symbol("page_count"))
                .and_then(|v| u32::try_convert(v).ok()),
        })
    }

    fn pdf_version(&self) -> Option<String> {
        self.pdf_version.clone()
    }

    fn producer(&self) -> Option<String> {
        self.producer.clone()
    }

    fn is_encrypted(&self) -> Option<bool> {
        self.is_encrypted
    }

    fn width(&self) -> Option<i64> {
        self.width
    }

    fn height(&self) -> Option<i64> {
        self.height
    }

    fn page_count(&self) -> Option<u32> {
        self.page_count
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ExecutionProviderType {
    Auto,
    Cpu,
    CoreMl,
    Cuda,
    TensorRt,
}

impl Default for ExecutionProviderType {
    fn default() -> Self {
        Self::Auto
    }
}

impl magnus::IntoValue for ExecutionProviderType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ExecutionProviderType::Auto => "auto",
            ExecutionProviderType::Cpu => "cpu",
            ExecutionProviderType::CoreMl => "core_ml",
            ExecutionProviderType::Cuda => "cuda",
            ExecutionProviderType::TensorRt => "tensor_rt",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ExecutionProviderType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "auto" | "Auto" => Ok(ExecutionProviderType::Auto),
            "cpu" | "Cpu" => Ok(ExecutionProviderType::Cpu),
            "core_ml" | "CoreMl" => Ok(ExecutionProviderType::CoreMl),
            "cuda" | "Cuda" => Ok(ExecutionProviderType::Cuda),
            "tensor_rt" | "TensorRt" => Ok(ExecutionProviderType::TensorRt),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ExecutionProviderType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ExecutionProviderType {}
unsafe impl TryConvertOwned for ExecutionProviderType {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OutputFormat {
    Plain,
    Markdown,
    Djot,
    Html,
    Json,
    Structured,
    Custom { _0: String },
}

impl Default for OutputFormat {
    fn default() -> Self {
        Self::Plain
    }
}

impl magnus::IntoValue for OutputFormat {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for OutputFormat {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OutputFormat: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for OutputFormat {}
unsafe impl TryConvertOwned for OutputFormat {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum HtmlTheme {
    Default,
    GitHub,
    Dark,
    Light,
    Unstyled,
}

impl Default for HtmlTheme {
    fn default() -> Self {
        Self::Unstyled
    }
}

impl magnus::IntoValue for HtmlTheme {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            HtmlTheme::Default => "default",
            HtmlTheme::GitHub => "git_hub",
            HtmlTheme::Dark => "dark",
            HtmlTheme::Light => "light",
            HtmlTheme::Unstyled => "unstyled",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for HtmlTheme {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "default" | "Default" => Ok(HtmlTheme::Default),
            "git_hub" | "GitHub" => Ok(HtmlTheme::GitHub),
            "dark" | "Dark" => Ok(HtmlTheme::Dark),
            "light" | "Light" => Ok(HtmlTheme::Light),
            "unstyled" | "Unstyled" => Ok(HtmlTheme::Unstyled),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid HtmlTheme value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for HtmlTheme {}
unsafe impl TryConvertOwned for HtmlTheme {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TableModel {
    Tatr,
    SlanetWired,
    SlanetWireless,
    SlanetPlus,
    SlanetAuto,
    Disabled,
}

impl Default for TableModel {
    fn default() -> Self {
        Self::Tatr
    }
}

impl magnus::IntoValue for TableModel {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            TableModel::Tatr => "tatr",
            TableModel::SlanetWired => "slanet_wired",
            TableModel::SlanetWireless => "slanet_wireless",
            TableModel::SlanetPlus => "slanet_plus",
            TableModel::SlanetAuto => "slanet_auto",
            TableModel::Disabled => "disabled",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for TableModel {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "tatr" | "Tatr" => Ok(TableModel::Tatr),
            "slanet_wired" | "SlanetWired" => Ok(TableModel::SlanetWired),
            "slanet_wireless" | "SlanetWireless" => Ok(TableModel::SlanetWireless),
            "slanet_plus" | "SlanetPlus" => Ok(TableModel::SlanetPlus),
            "slanet_auto" | "SlanetAuto" => Ok(TableModel::SlanetAuto),
            "disabled" | "Disabled" => Ok(TableModel::Disabled),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid TableModel value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for TableModel {}
unsafe impl TryConvertOwned for TableModel {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChunkerType {
    Text,
    Markdown,
    Yaml,
    Semantic,
}

impl Default for ChunkerType {
    fn default() -> Self {
        Self::Text
    }
}

impl magnus::IntoValue for ChunkerType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ChunkerType::Text => "text",
            ChunkerType::Markdown => "markdown",
            ChunkerType::Yaml => "yaml",
            ChunkerType::Semantic => "semantic",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ChunkerType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "text" | "Text" => Ok(ChunkerType::Text),
            "markdown" | "Markdown" => Ok(ChunkerType::Markdown),
            "yaml" | "Yaml" => Ok(ChunkerType::Yaml),
            "semantic" | "Semantic" => Ok(ChunkerType::Semantic),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ChunkerType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ChunkerType {}
unsafe impl TryConvertOwned for ChunkerType {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ChunkSizing {
    Characters,
    Tokenizer { model: String, cache_dir: Option<String> },
}

impl Default for ChunkSizing {
    fn default() -> Self {
        Self::Characters
    }
}

impl magnus::IntoValue for ChunkSizing {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for ChunkSizing {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into ChunkSizing: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for ChunkSizing {}
unsafe impl TryConvertOwned for ChunkSizing {}
impl ChunkSizing {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum EmbeddingModelType {
    Preset { name: String },
    Custom { model_id: String, dimensions: usize },
    Llm { llm: LlmConfig },
    Plugin { name: String },
}

impl Default for EmbeddingModelType {
    fn default() -> Self {
        Self::Preset {
            name: Default::default(),
        }
    }
}

impl magnus::IntoValue for EmbeddingModelType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for EmbeddingModelType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into EmbeddingModelType: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for EmbeddingModelType {}
unsafe impl TryConvertOwned for EmbeddingModelType {}
impl EmbeddingModelType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CodeContentMode {
    Chunks,
    Raw,
    Structure,
}

impl Default for CodeContentMode {
    fn default() -> Self {
        Self::Chunks
    }
}

impl magnus::IntoValue for CodeContentMode {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            CodeContentMode::Chunks => "chunks",
            CodeContentMode::Raw => "raw",
            CodeContentMode::Structure => "structure",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for CodeContentMode {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "chunks" | "Chunks" => Ok(CodeContentMode::Chunks),
            "raw" | "Raw" => Ok(CodeContentMode::Raw),
            "structure" | "Structure" => Ok(CodeContentMode::Structure),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid CodeContentMode value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for CodeContentMode {}
unsafe impl TryConvertOwned for CodeContentMode {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum ListType {
    Bullet,
    Numbered,
    Lettered,
    Indented,
}

impl Default for ListType {
    fn default() -> Self {
        Self::Bullet
    }
}

impl magnus::IntoValue for ListType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ListType::Bullet => "bullet",
            ListType::Numbered => "numbered",
            ListType::Lettered => "lettered",
            ListType::Indented => "indented",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ListType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "bullet" | "Bullet" => Ok(ListType::Bullet),
            "numbered" | "Numbered" => Ok(ListType::Numbered),
            "lettered" | "Lettered" => Ok(ListType::Lettered),
            "indented" | "Indented" => Ok(ListType::Indented),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ListType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ListType {}
unsafe impl TryConvertOwned for ListType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum OcrBackendType {
    Tesseract,
    EasyOCR,
    PaddleOCR,
    Custom,
}

impl Default for OcrBackendType {
    fn default() -> Self {
        Self::Tesseract
    }
}

impl magnus::IntoValue for OcrBackendType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            OcrBackendType::Tesseract => "tesseract",
            OcrBackendType::EasyOCR => "easy_o_c_r",
            OcrBackendType::PaddleOCR => "paddle_o_c_r",
            OcrBackendType::Custom => "custom",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for OcrBackendType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "tesseract" | "Tesseract" => Ok(OcrBackendType::Tesseract),
            "easy_o_c_r" | "EasyOCR" => Ok(OcrBackendType::EasyOCR),
            "paddle_o_c_r" | "PaddleOCR" => Ok(OcrBackendType::PaddleOCR),
            "custom" | "Custom" => Ok(OcrBackendType::Custom),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid OcrBackendType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for OcrBackendType {}
unsafe impl TryConvertOwned for OcrBackendType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum ProcessingStage {
    Early,
    Middle,
    Late,
}

impl Default for ProcessingStage {
    fn default() -> Self {
        Self::Early
    }
}

impl magnus::IntoValue for ProcessingStage {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ProcessingStage::Early => "early",
            ProcessingStage::Middle => "middle",
            ProcessingStage::Late => "late",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ProcessingStage {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "early" | "Early" => Ok(ProcessingStage::Early),
            "middle" | "Middle" => Ok(ProcessingStage::Middle),
            "late" | "Late" => Ok(ProcessingStage::Late),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ProcessingStage value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ProcessingStage {}
unsafe impl TryConvertOwned for ProcessingStage {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum ReductionLevel {
    Off,
    Light,
    Moderate,
    Aggressive,
    Maximum,
}

impl Default for ReductionLevel {
    fn default() -> Self {
        Self::Off
    }
}

impl magnus::IntoValue for ReductionLevel {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ReductionLevel::Off => "off",
            ReductionLevel::Light => "light",
            ReductionLevel::Moderate => "moderate",
            ReductionLevel::Aggressive => "aggressive",
            ReductionLevel::Maximum => "maximum",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ReductionLevel {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "off" | "Off" => Ok(ReductionLevel::Off),
            "light" | "Light" => Ok(ReductionLevel::Light),
            "moderate" | "Moderate" => Ok(ReductionLevel::Moderate),
            "aggressive" | "Aggressive" => Ok(ReductionLevel::Aggressive),
            "maximum" | "Maximum" => Ok(ReductionLevel::Maximum),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ReductionLevel value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ReductionLevel {}
unsafe impl TryConvertOwned for ReductionLevel {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PdfAnnotationType {
    Text,
    Highlight,
    Link,
    Stamp,
    Underline,
    StrikeOut,
    Other,
}

impl Default for PdfAnnotationType {
    fn default() -> Self {
        Self::Text
    }
}

impl magnus::IntoValue for PdfAnnotationType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            PdfAnnotationType::Text => "text",
            PdfAnnotationType::Highlight => "highlight",
            PdfAnnotationType::Link => "link",
            PdfAnnotationType::Stamp => "stamp",
            PdfAnnotationType::Underline => "underline",
            PdfAnnotationType::StrikeOut => "strike_out",
            PdfAnnotationType::Other => "other",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for PdfAnnotationType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "text" | "Text" => Ok(PdfAnnotationType::Text),
            "highlight" | "Highlight" => Ok(PdfAnnotationType::Highlight),
            "link" | "Link" => Ok(PdfAnnotationType::Link),
            "stamp" | "Stamp" => Ok(PdfAnnotationType::Stamp),
            "underline" | "Underline" => Ok(PdfAnnotationType::Underline),
            "strike_out" | "StrikeOut" => Ok(PdfAnnotationType::StrikeOut),
            "other" | "Other" => Ok(PdfAnnotationType::Other),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid PdfAnnotationType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for PdfAnnotationType {}
unsafe impl TryConvertOwned for PdfAnnotationType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BlockType {
    Paragraph,
    Heading,
    Blockquote,
    CodeBlock,
    ListItem,
    OrderedList,
    BulletList,
    TaskList,
    DefinitionList,
    DefinitionTerm,
    DefinitionDescription,
    Div,
    Section,
    ThematicBreak,
    RawBlock,
    MathDisplay,
}

impl Default for BlockType {
    fn default() -> Self {
        Self::Paragraph
    }
}

impl magnus::IntoValue for BlockType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            BlockType::Paragraph => "paragraph",
            BlockType::Heading => "heading",
            BlockType::Blockquote => "blockquote",
            BlockType::CodeBlock => "code_block",
            BlockType::ListItem => "list_item",
            BlockType::OrderedList => "ordered_list",
            BlockType::BulletList => "bullet_list",
            BlockType::TaskList => "task_list",
            BlockType::DefinitionList => "definition_list",
            BlockType::DefinitionTerm => "definition_term",
            BlockType::DefinitionDescription => "definition_description",
            BlockType::Div => "div",
            BlockType::Section => "section",
            BlockType::ThematicBreak => "thematic_break",
            BlockType::RawBlock => "raw_block",
            BlockType::MathDisplay => "math_display",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for BlockType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "paragraph" | "Paragraph" => Ok(BlockType::Paragraph),
            "heading" | "Heading" => Ok(BlockType::Heading),
            "blockquote" | "Blockquote" => Ok(BlockType::Blockquote),
            "code_block" | "CodeBlock" => Ok(BlockType::CodeBlock),
            "list_item" | "ListItem" => Ok(BlockType::ListItem),
            "ordered_list" | "OrderedList" => Ok(BlockType::OrderedList),
            "bullet_list" | "BulletList" => Ok(BlockType::BulletList),
            "task_list" | "TaskList" => Ok(BlockType::TaskList),
            "definition_list" | "DefinitionList" => Ok(BlockType::DefinitionList),
            "definition_term" | "DefinitionTerm" => Ok(BlockType::DefinitionTerm),
            "definition_description" | "DefinitionDescription" => Ok(BlockType::DefinitionDescription),
            "div" | "Div" => Ok(BlockType::Div),
            "section" | "Section" => Ok(BlockType::Section),
            "thematic_break" | "ThematicBreak" => Ok(BlockType::ThematicBreak),
            "raw_block" | "RawBlock" => Ok(BlockType::RawBlock),
            "math_display" | "MathDisplay" => Ok(BlockType::MathDisplay),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid BlockType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for BlockType {}
unsafe impl TryConvertOwned for BlockType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InlineType {
    Text,
    Strong,
    Emphasis,
    Highlight,
    Subscript,
    Superscript,
    Insert,
    Delete,
    Code,
    Link,
    Image,
    Span,
    Math,
    RawInline,
    FootnoteRef,
    Symbol,
}

impl Default for InlineType {
    fn default() -> Self {
        Self::Text
    }
}

impl magnus::IntoValue for InlineType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            InlineType::Text => "text",
            InlineType::Strong => "strong",
            InlineType::Emphasis => "emphasis",
            InlineType::Highlight => "highlight",
            InlineType::Subscript => "subscript",
            InlineType::Superscript => "superscript",
            InlineType::Insert => "insert",
            InlineType::Delete => "delete",
            InlineType::Code => "code",
            InlineType::Link => "link",
            InlineType::Image => "image",
            InlineType::Span => "span",
            InlineType::Math => "math",
            InlineType::RawInline => "raw_inline",
            InlineType::FootnoteRef => "footnote_ref",
            InlineType::Symbol => "symbol",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for InlineType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "text" | "Text" => Ok(InlineType::Text),
            "strong" | "Strong" => Ok(InlineType::Strong),
            "emphasis" | "Emphasis" => Ok(InlineType::Emphasis),
            "highlight" | "Highlight" => Ok(InlineType::Highlight),
            "subscript" | "Subscript" => Ok(InlineType::Subscript),
            "superscript" | "Superscript" => Ok(InlineType::Superscript),
            "insert" | "Insert" => Ok(InlineType::Insert),
            "delete" | "Delete" => Ok(InlineType::Delete),
            "code" | "Code" => Ok(InlineType::Code),
            "link" | "Link" => Ok(InlineType::Link),
            "image" | "Image" => Ok(InlineType::Image),
            "span" | "Span" => Ok(InlineType::Span),
            "math" | "Math" => Ok(InlineType::Math),
            "raw_inline" | "RawInline" => Ok(InlineType::RawInline),
            "footnote_ref" | "FootnoteRef" => Ok(InlineType::FootnoteRef),
            "symbol" | "Symbol" => Ok(InlineType::Symbol),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid InlineType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for InlineType {}
unsafe impl TryConvertOwned for InlineType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RelationshipKind {
    FootnoteReference,
    CitationReference,
    InternalLink,
    Caption,
    Label,
    TocEntry,
    CrossReference,
}

impl Default for RelationshipKind {
    fn default() -> Self {
        Self::FootnoteReference
    }
}

impl magnus::IntoValue for RelationshipKind {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            RelationshipKind::FootnoteReference => "footnote_reference",
            RelationshipKind::CitationReference => "citation_reference",
            RelationshipKind::InternalLink => "internal_link",
            RelationshipKind::Caption => "caption",
            RelationshipKind::Label => "label",
            RelationshipKind::TocEntry => "toc_entry",
            RelationshipKind::CrossReference => "cross_reference",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for RelationshipKind {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "footnote_reference" | "FootnoteReference" => Ok(RelationshipKind::FootnoteReference),
            "citation_reference" | "CitationReference" => Ok(RelationshipKind::CitationReference),
            "internal_link" | "InternalLink" => Ok(RelationshipKind::InternalLink),
            "caption" | "Caption" => Ok(RelationshipKind::Caption),
            "label" | "Label" => Ok(RelationshipKind::Label),
            "toc_entry" | "TocEntry" => Ok(RelationshipKind::TocEntry),
            "cross_reference" | "CrossReference" => Ok(RelationshipKind::CrossReference),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid RelationshipKind value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for RelationshipKind {}
unsafe impl TryConvertOwned for RelationshipKind {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContentLayer {
    Body,
    Header,
    Footer,
    Footnote,
}

impl Default for ContentLayer {
    fn default() -> Self {
        Self::Body
    }
}

impl magnus::IntoValue for ContentLayer {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ContentLayer::Body => "body",
            ContentLayer::Header => "header",
            ContentLayer::Footer => "footer",
            ContentLayer::Footnote => "footnote",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ContentLayer {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "body" | "Body" => Ok(ContentLayer::Body),
            "header" | "Header" => Ok(ContentLayer::Header),
            "footer" | "Footer" => Ok(ContentLayer::Footer),
            "footnote" | "Footnote" => Ok(ContentLayer::Footnote),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ContentLayer value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ContentLayer {}
unsafe impl TryConvertOwned for ContentLayer {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "node_type")]
#[serde(rename_all = "snake_case")]
pub enum NodeContent {
    Title {
        text: String,
    },
    Heading {
        level: u8,
        text: String,
    },
    Paragraph {
        text: String,
    },
    List {
        ordered: bool,
    },
    ListItem {
        text: String,
    },
    Table {
        grid: TableGrid,
    },
    Image {
        description: Option<String>,
        image_index: Option<u32>,
        src: Option<String>,
    },
    Code {
        text: String,
        language: Option<String>,
    },
    Quote,
    Formula {
        text: String,
    },
    Footnote {
        text: String,
    },
    Group {
        label: Option<String>,
        heading_level: Option<u8>,
        heading_text: Option<String>,
    },
    PageBreak,
    Slide {
        number: u32,
        title: Option<String>,
    },
    DefinitionList,
    DefinitionItem {
        term: String,
        definition: String,
    },
    Citation {
        key: String,
        text: String,
    },
    Admonition {
        kind: String,
        title: Option<String>,
    },
    RawBlock {
        format: String,
        content: String,
    },
    MetadataBlock {
        entries: Vec<Vec<String>>,
    },
}

impl Default for NodeContent {
    fn default() -> Self {
        Self::Title {
            text: Default::default(),
        }
    }
}

impl magnus::IntoValue for NodeContent {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for NodeContent {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into NodeContent: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for NodeContent {}
unsafe impl TryConvertOwned for NodeContent {}
impl NodeContent {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "annotation_type")]
#[serde(rename_all = "snake_case")]
pub enum AnnotationKind {
    Bold,
    Italic,
    Underline,
    Strikethrough,
    Code,
    Subscript,
    Superscript,
    Link { url: String, title: Option<String> },
    Highlight,
    Color { value: String },
    FontSize { value: String },
    Custom { name: String, value: Option<String> },
}

impl Default for AnnotationKind {
    fn default() -> Self {
        Self::Bold
    }
}

impl magnus::IntoValue for AnnotationKind {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for AnnotationKind {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into AnnotationKind: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for AnnotationKind {}
unsafe impl TryConvertOwned for AnnotationKind {}
impl AnnotationKind {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExtractionMethod {
    Native,
    Ocr,
    Mixed,
}

impl Default for ExtractionMethod {
    fn default() -> Self {
        Self::Native
    }
}

impl magnus::IntoValue for ExtractionMethod {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ExtractionMethod::Native => "native",
            ExtractionMethod::Ocr => "ocr",
            ExtractionMethod::Mixed => "mixed",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ExtractionMethod {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "native" | "Native" => Ok(ExtractionMethod::Native),
            "ocr" | "Ocr" => Ok(ExtractionMethod::Ocr),
            "mixed" | "Mixed" => Ok(ExtractionMethod::Mixed),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ExtractionMethod value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ExtractionMethod {}
unsafe impl TryConvertOwned for ExtractionMethod {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChunkType {
    Heading,
    PartyList,
    Definitions,
    OperativeClause,
    SignatureBlock,
    Schedule,
    TableLike,
    Formula,
    CodeBlock,
    Image,
    OrgChart,
    Diagram,
    Unknown,
}

impl Default for ChunkType {
    fn default() -> Self {
        Self::Unknown
    }
}

impl magnus::IntoValue for ChunkType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ChunkType::Heading => "heading",
            ChunkType::PartyList => "party_list",
            ChunkType::Definitions => "definitions",
            ChunkType::OperativeClause => "operative_clause",
            ChunkType::SignatureBlock => "signature_block",
            ChunkType::Schedule => "schedule",
            ChunkType::TableLike => "table_like",
            ChunkType::Formula => "formula",
            ChunkType::CodeBlock => "code_block",
            ChunkType::Image => "image",
            ChunkType::OrgChart => "org_chart",
            ChunkType::Diagram => "diagram",
            ChunkType::Unknown => "unknown",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ChunkType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "heading" | "Heading" => Ok(ChunkType::Heading),
            "party_list" | "PartyList" => Ok(ChunkType::PartyList),
            "definitions" | "Definitions" => Ok(ChunkType::Definitions),
            "operative_clause" | "OperativeClause" => Ok(ChunkType::OperativeClause),
            "signature_block" | "SignatureBlock" => Ok(ChunkType::SignatureBlock),
            "schedule" | "Schedule" => Ok(ChunkType::Schedule),
            "table_like" | "TableLike" => Ok(ChunkType::TableLike),
            "formula" | "Formula" => Ok(ChunkType::Formula),
            "code_block" | "CodeBlock" => Ok(ChunkType::CodeBlock),
            "image" | "Image" => Ok(ChunkType::Image),
            "org_chart" | "OrgChart" => Ok(ChunkType::OrgChart),
            "diagram" | "Diagram" => Ok(ChunkType::Diagram),
            "unknown" | "Unknown" => Ok(ChunkType::Unknown),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ChunkType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ChunkType {}
unsafe impl TryConvertOwned for ChunkType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ImageKind {
    Photograph,
    Diagram,
    Chart,
    Drawing,
    TextBlock,
    Decoration,
    Logo,
    Icon,
    TileFragment,
    Mask,
    PageRaster,
    Unknown,
}

impl Default for ImageKind {
    fn default() -> Self {
        Self::Photograph
    }
}

impl magnus::IntoValue for ImageKind {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ImageKind::Photograph => "photograph",
            ImageKind::Diagram => "diagram",
            ImageKind::Chart => "chart",
            ImageKind::Drawing => "drawing",
            ImageKind::TextBlock => "text_block",
            ImageKind::Decoration => "decoration",
            ImageKind::Logo => "logo",
            ImageKind::Icon => "icon",
            ImageKind::TileFragment => "tile_fragment",
            ImageKind::Mask => "mask",
            ImageKind::PageRaster => "page_raster",
            ImageKind::Unknown => "unknown",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ImageKind {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "photograph" | "Photograph" => Ok(ImageKind::Photograph),
            "diagram" | "Diagram" => Ok(ImageKind::Diagram),
            "chart" | "Chart" => Ok(ImageKind::Chart),
            "drawing" | "Drawing" => Ok(ImageKind::Drawing),
            "text_block" | "TextBlock" => Ok(ImageKind::TextBlock),
            "decoration" | "Decoration" => Ok(ImageKind::Decoration),
            "logo" | "Logo" => Ok(ImageKind::Logo),
            "icon" | "Icon" => Ok(ImageKind::Icon),
            "tile_fragment" | "TileFragment" => Ok(ImageKind::TileFragment),
            "mask" | "Mask" => Ok(ImageKind::Mask),
            "page_raster" | "PageRaster" => Ok(ImageKind::PageRaster),
            "unknown" | "Unknown" => Ok(ImageKind::Unknown),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ImageKind value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ImageKind {}
unsafe impl TryConvertOwned for ImageKind {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResultFormat {
    Unified,
    ElementBased,
}

impl Default for ResultFormat {
    fn default() -> Self {
        Self::Unified
    }
}

impl magnus::IntoValue for ResultFormat {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ResultFormat::Unified => "unified",
            ResultFormat::ElementBased => "element_based",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ResultFormat {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "unified" | "Unified" => Ok(ResultFormat::Unified),
            "element_based" | "ElementBased" => Ok(ResultFormat::ElementBased),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ResultFormat value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ResultFormat {}
unsafe impl TryConvertOwned for ResultFormat {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ElementType {
    Title,
    NarrativeText,
    Heading,
    ListItem,
    Table,
    Image,
    PageBreak,
    CodeBlock,
    BlockQuote,
    Footer,
    Header,
}

impl Default for ElementType {
    fn default() -> Self {
        Self::Title
    }
}

impl magnus::IntoValue for ElementType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ElementType::Title => "title",
            ElementType::NarrativeText => "narrative_text",
            ElementType::Heading => "heading",
            ElementType::ListItem => "list_item",
            ElementType::Table => "table",
            ElementType::Image => "image",
            ElementType::PageBreak => "page_break",
            ElementType::CodeBlock => "code_block",
            ElementType::BlockQuote => "block_quote",
            ElementType::Footer => "footer",
            ElementType::Header => "header",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ElementType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "title" | "Title" => Ok(ElementType::Title),
            "narrative_text" | "NarrativeText" => Ok(ElementType::NarrativeText),
            "heading" | "Heading" => Ok(ElementType::Heading),
            "list_item" | "ListItem" => Ok(ElementType::ListItem),
            "table" | "Table" => Ok(ElementType::Table),
            "image" | "Image" => Ok(ElementType::Image),
            "page_break" | "PageBreak" => Ok(ElementType::PageBreak),
            "code_block" | "CodeBlock" => Ok(ElementType::CodeBlock),
            "block_quote" | "BlockQuote" => Ok(ElementType::BlockQuote),
            "footer" | "Footer" => Ok(ElementType::Footer),
            "header" | "Header" => Ok(ElementType::Header),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ElementType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ElementType {}
unsafe impl TryConvertOwned for ElementType {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "format_type")]
#[serde(rename_all = "snake_case")]
pub enum FormatMetadata {
    Pdf { _0: PdfMetadata },
    Docx { _0: DocxMetadata },
    Excel { _0: ExcelMetadata },
    Email { _0: EmailMetadata },
    Pptx { _0: PptxMetadata },
    Archive { _0: ArchiveMetadata },
    Image { _0: ImageMetadata },
    Xml { _0: XmlMetadata },
    Text { _0: TextMetadata },
    Html { _0: HtmlMetadata },
    Ocr { _0: OcrMetadata },
    Csv { _0: CsvMetadata },
    Bibtex { _0: BibtexMetadata },
    Citation { _0: CitationMetadata },
    FictionBook { _0: FictionBookMetadata },
    Dbf { _0: DbfMetadata },
    Jats { _0: JatsMetadata },
    Epub { _0: EpubMetadata },
    Pst { _0: PstMetadata },
    Code { _0: String },
}

impl Default for FormatMetadata {
    fn default() -> Self {
        Self::Pdf { _0: Default::default() }
    }
}

impl magnus::IntoValue for FormatMetadata {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for FormatMetadata {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into FormatMetadata: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for FormatMetadata {}
unsafe impl TryConvertOwned for FormatMetadata {}
impl FormatMetadata {
    pub fn pdf(&self) -> Option<&PdfMetadata> {
        match self {
            Self::Pdf { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn docx(&self) -> Option<&DocxMetadata> {
        match self {
            Self::Docx { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn excel(&self) -> Option<&ExcelMetadata> {
        match self {
            Self::Excel { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn email(&self) -> Option<&EmailMetadata> {
        match self {
            Self::Email { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn pptx(&self) -> Option<&PptxMetadata> {
        match self {
            Self::Pptx { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn archive(&self) -> Option<&ArchiveMetadata> {
        match self {
            Self::Archive { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn image(&self) -> Option<&ImageMetadata> {
        match self {
            Self::Image { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn xml(&self) -> Option<&XmlMetadata> {
        match self {
            Self::Xml { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn text(&self) -> Option<&TextMetadata> {
        match self {
            Self::Text { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn html(&self) -> Option<&HtmlMetadata> {
        match self {
            Self::Html { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn ocr(&self) -> Option<&OcrMetadata> {
        match self {
            Self::Ocr { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn csv(&self) -> Option<&CsvMetadata> {
        match self {
            Self::Csv { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn bibtex(&self) -> Option<&BibtexMetadata> {
        match self {
            Self::Bibtex { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn citation(&self) -> Option<&CitationMetadata> {
        match self {
            Self::Citation { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn fiction_book(&self) -> Option<&FictionBookMetadata> {
        match self {
            Self::FictionBook { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn dbf(&self) -> Option<&DbfMetadata> {
        match self {
            Self::Dbf { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn jats(&self) -> Option<&JatsMetadata> {
        match self {
            Self::Jats { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn epub(&self) -> Option<&EpubMetadata> {
        match self {
            Self::Epub { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn pst(&self) -> Option<&PstMetadata> {
        match self {
            Self::Pst { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn code(&self) -> Option<&String> {
        match self {
            Self::Code { _0 } => Some(_0),
            _ => None,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TextDirection {
    #[serde(rename = "ltr")]
    LeftToRight,
    #[serde(rename = "rtl")]
    RightToLeft,
    #[serde(rename = "auto")]
    Auto,
}

impl Default for TextDirection {
    fn default() -> Self {
        Self::LeftToRight
    }
}

impl magnus::IntoValue for TextDirection {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            TextDirection::LeftToRight => "left_to_right",
            TextDirection::RightToLeft => "right_to_left",
            TextDirection::Auto => "auto",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for TextDirection {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "left_to_right" | "LeftToRight" => Ok(TextDirection::LeftToRight),
            "right_to_left" | "RightToLeft" => Ok(TextDirection::RightToLeft),
            "auto" | "Auto" => Ok(TextDirection::Auto),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid TextDirection value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for TextDirection {}
unsafe impl TryConvertOwned for TextDirection {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LinkType {
    Anchor,
    Internal,
    External,
    Email,
    Phone,
    Other,
}

impl Default for LinkType {
    fn default() -> Self {
        Self::Anchor
    }
}

impl magnus::IntoValue for LinkType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            LinkType::Anchor => "anchor",
            LinkType::Internal => "internal",
            LinkType::External => "external",
            LinkType::Email => "email",
            LinkType::Phone => "phone",
            LinkType::Other => "other",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for LinkType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "anchor" | "Anchor" => Ok(LinkType::Anchor),
            "internal" | "Internal" => Ok(LinkType::Internal),
            "external" | "External" => Ok(LinkType::External),
            "email" | "Email" => Ok(LinkType::Email),
            "phone" | "Phone" => Ok(LinkType::Phone),
            "other" | "Other" => Ok(LinkType::Other),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid LinkType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for LinkType {}
unsafe impl TryConvertOwned for LinkType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ImageType {
    #[serde(rename = "data-uri")]
    DataUri,
    #[serde(rename = "inline-svg")]
    InlineSvg,
    External,
    Relative,
}

impl Default for ImageType {
    fn default() -> Self {
        Self::DataUri
    }
}

impl magnus::IntoValue for ImageType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            ImageType::DataUri => "data_uri",
            ImageType::InlineSvg => "inline_svg",
            ImageType::External => "external",
            ImageType::Relative => "relative",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for ImageType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "data_uri" | "DataUri" => Ok(ImageType::DataUri),
            "inline_svg" | "InlineSvg" => Ok(ImageType::InlineSvg),
            "external" | "External" => Ok(ImageType::External),
            "relative" | "Relative" => Ok(ImageType::Relative),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid ImageType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for ImageType {}
unsafe impl TryConvertOwned for ImageType {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StructuredDataType {
    #[serde(rename = "json-ld")]
    JsonLd,
    Microdata,
    #[serde(rename = "rdfa")]
    RDFa,
}

impl Default for StructuredDataType {
    fn default() -> Self {
        Self::JsonLd
    }
}

impl magnus::IntoValue for StructuredDataType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            StructuredDataType::JsonLd => "json_ld",
            StructuredDataType::Microdata => "microdata",
            StructuredDataType::RDFa => "r_d_fa",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for StructuredDataType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "json_ld" | "JsonLd" => Ok(StructuredDataType::JsonLd),
            "microdata" | "Microdata" => Ok(StructuredDataType::Microdata),
            "r_d_fa" | "RDFa" => Ok(StructuredDataType::RDFa),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid StructuredDataType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for StructuredDataType {}
unsafe impl TryConvertOwned for StructuredDataType {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum OcrBoundingGeometry {
    Rectangle {
        left: u32,
        top: u32,
        width: u32,
        height: u32,
    },
    Quadrilateral {
        points: String,
    },
}

impl Default for OcrBoundingGeometry {
    fn default() -> Self {
        Self::Rectangle {
            left: Default::default(),
            top: Default::default(),
            width: Default::default(),
            height: Default::default(),
        }
    }
}

impl magnus::IntoValue for OcrBoundingGeometry {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for OcrBoundingGeometry {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into OcrBoundingGeometry: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for OcrBoundingGeometry {}
unsafe impl TryConvertOwned for OcrBoundingGeometry {}
impl OcrBoundingGeometry {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OcrElementLevel {
    Word,
    Line,
    Block,
    Page,
}

impl Default for OcrElementLevel {
    fn default() -> Self {
        Self::Line
    }
}

impl magnus::IntoValue for OcrElementLevel {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            OcrElementLevel::Word => "word",
            OcrElementLevel::Line => "line",
            OcrElementLevel::Block => "block",
            OcrElementLevel::Page => "page",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for OcrElementLevel {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "word" | "Word" => Ok(OcrElementLevel::Word),
            "line" | "Line" => Ok(OcrElementLevel::Line),
            "block" | "Block" => Ok(OcrElementLevel::Block),
            "page" | "Page" => Ok(OcrElementLevel::Page),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid OcrElementLevel value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for OcrElementLevel {}
unsafe impl TryConvertOwned for OcrElementLevel {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PageUnitType {
    Page,
    Slide,
    Sheet,
}

impl Default for PageUnitType {
    fn default() -> Self {
        Self::Page
    }
}

impl magnus::IntoValue for PageUnitType {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            PageUnitType::Page => "page",
            PageUnitType::Slide => "slide",
            PageUnitType::Sheet => "sheet",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for PageUnitType {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "page" | "Page" => Ok(PageUnitType::Page),
            "slide" | "Slide" => Ok(PageUnitType::Slide),
            "sheet" | "Sheet" => Ok(PageUnitType::Sheet),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid PageUnitType value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for PageUnitType {}
unsafe impl TryConvertOwned for PageUnitType {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind")]
#[serde(rename_all = "snake_case")]
pub enum DiffLine {
    Context { _0: String },
    Added { _0: String },
    Removed { _0: String },
}

impl Default for DiffLine {
    fn default() -> Self {
        Self::Context { _0: Default::default() }
    }
}

impl magnus::IntoValue for DiffLine {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for DiffLine {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into DiffLine: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for DiffLine {}
unsafe impl TryConvertOwned for DiffLine {}
impl DiffLine {
    pub fn context(&self) -> Option<&String> {
        match self {
            Self::Context { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn added(&self) -> Option<&String> {
        match self {
            Self::Added { _0 } => Some(_0),
            _ => None,
        }
    }
    pub fn removed(&self) -> Option<&String> {
        match self {
            Self::Removed { _0 } => Some(_0),
            _ => None,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RevisionKind {
    Insertion,
    Deletion,
    FormatChange,
    Comment,
}

impl Default for RevisionKind {
    fn default() -> Self {
        Self::Insertion
    }
}

impl magnus::IntoValue for RevisionKind {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            RevisionKind::Insertion => "insertion",
            RevisionKind::Deletion => "deletion",
            RevisionKind::FormatChange => "format_change",
            RevisionKind::Comment => "comment",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for RevisionKind {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "insertion" | "Insertion" => Ok(RevisionKind::Insertion),
            "deletion" | "Deletion" => Ok(RevisionKind::Deletion),
            "format_change" | "FormatChange" => Ok(RevisionKind::FormatChange),
            "comment" | "Comment" => Ok(RevisionKind::Comment),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid RevisionKind value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for RevisionKind {}
unsafe impl TryConvertOwned for RevisionKind {}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum RevisionAnchor {
    Paragraph { index: usize },
    TableCell { row: usize, col: usize, table_index: usize },
    Page { index: usize },
    Slide { index: usize },
    Sheet { index: usize, name: Option<String> },
}

impl Default for RevisionAnchor {
    fn default() -> Self {
        Self::Paragraph {
            index: Default::default(),
        }
    }
}

impl magnus::IntoValue for RevisionAnchor {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        match serde_json::to_value(&self) {
            Ok(v) => json_to_ruby(handle, v),
            Err(_) => handle.qnil().into_value_with(handle),
        }
    }
}

impl magnus::TryConvert for RevisionAnchor {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        // For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
        // For unit enums or when passed as a string, fall back to string-based conversion.
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_type_error(),
                    format!("no implicit conversion into RevisionAnchor: {}", e),
                )
            })?
        };
        // Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
        // If that fails, try treating it as a plain string value and wrap in quotes
        // If both fail, try as Custom variant (for untagged enum support)
        serde_json::from_str(&json_str)
            .or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
            .or_else(|_| {
                // Try as a JSON string for Custom variant (untagged enums accept any remaining value)
                match serde_json::to_value(&json_str) {
                    Ok(val) => serde_json::from_value(val),
                    Err(e) => Err(e),
                }
            })
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
    }
}

unsafe impl IntoValueFromNative for RevisionAnchor {}
unsafe impl TryConvertOwned for RevisionAnchor {}
impl RevisionAnchor {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UriKind {
    Hyperlink,
    Image,
    Anchor,
    Citation,
    Reference,
    Email,
}

impl Default for UriKind {
    fn default() -> Self {
        Self::Hyperlink
    }
}

impl magnus::IntoValue for UriKind {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            UriKind::Hyperlink => "hyperlink",
            UriKind::Image => "image",
            UriKind::Anchor => "anchor",
            UriKind::Citation => "citation",
            UriKind::Reference => "reference",
            UriKind::Email => "email",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for UriKind {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "hyperlink" | "Hyperlink" => Ok(UriKind::Hyperlink),
            "image" | "Image" => Ok(UriKind::Image),
            "anchor" | "Anchor" => Ok(UriKind::Anchor),
            "citation" | "Citation" => Ok(UriKind::Citation),
            "reference" | "Reference" => Ok(UriKind::Reference),
            "email" | "Email" => Ok(UriKind::Email),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid UriKind value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for UriKind {}
unsafe impl TryConvertOwned for UriKind {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum KeywordAlgorithm {
    Yake,
    Rake,
}

impl Default for KeywordAlgorithm {
    fn default() -> Self {
        Self::Yake
    }
}

impl magnus::IntoValue for KeywordAlgorithm {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            KeywordAlgorithm::Yake => "yake",
            KeywordAlgorithm::Rake => "rake",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for KeywordAlgorithm {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "yake" | "Yake" => Ok(KeywordAlgorithm::Yake),
            "rake" | "Rake" => Ok(KeywordAlgorithm::Rake),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid KeywordAlgorithm value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for KeywordAlgorithm {}
unsafe impl TryConvertOwned for KeywordAlgorithm {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum PSMMode {
    OsdOnly,
    AutoOsd,
    AutoOnly,
    Auto,
    SingleColumn,
    SingleBlockVertical,
    SingleBlock,
    SingleLine,
    SingleWord,
    CircleWord,
    SingleChar,
}

impl Default for PSMMode {
    fn default() -> Self {
        Self::OsdOnly
    }
}

impl magnus::IntoValue for PSMMode {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            PSMMode::OsdOnly => "osd_only",
            PSMMode::AutoOsd => "auto_osd",
            PSMMode::AutoOnly => "auto_only",
            PSMMode::Auto => "auto",
            PSMMode::SingleColumn => "single_column",
            PSMMode::SingleBlockVertical => "single_block_vertical",
            PSMMode::SingleBlock => "single_block",
            PSMMode::SingleLine => "single_line",
            PSMMode::SingleWord => "single_word",
            PSMMode::CircleWord => "circle_word",
            PSMMode::SingleChar => "single_char",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for PSMMode {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "osd_only" | "OsdOnly" => Ok(PSMMode::OsdOnly),
            "auto_osd" | "AutoOsd" => Ok(PSMMode::AutoOsd),
            "auto_only" | "AutoOnly" => Ok(PSMMode::AutoOnly),
            "auto" | "Auto" => Ok(PSMMode::Auto),
            "single_column" | "SingleColumn" => Ok(PSMMode::SingleColumn),
            "single_block_vertical" | "SingleBlockVertical" => Ok(PSMMode::SingleBlockVertical),
            "single_block" | "SingleBlock" => Ok(PSMMode::SingleBlock),
            "single_line" | "SingleLine" => Ok(PSMMode::SingleLine),
            "single_word" | "SingleWord" => Ok(PSMMode::SingleWord),
            "circle_word" | "CircleWord" => Ok(PSMMode::CircleWord),
            "single_char" | "SingleChar" => Ok(PSMMode::SingleChar),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid PSMMode value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for PSMMode {}
unsafe impl TryConvertOwned for PSMMode {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum PaddleLanguage {
    English,
    Chinese,
    Japanese,
    Korean,
    German,
    French,
    Latin,
    Cyrillic,
    TraditionalChinese,
    Thai,
    Greek,
    EastSlavic,
    Arabic,
    Devanagari,
    Tamil,
    Telugu,
}

impl Default for PaddleLanguage {
    fn default() -> Self {
        Self::English
    }
}

impl magnus::IntoValue for PaddleLanguage {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            PaddleLanguage::English => "english",
            PaddleLanguage::Chinese => "chinese",
            PaddleLanguage::Japanese => "japanese",
            PaddleLanguage::Korean => "korean",
            PaddleLanguage::German => "german",
            PaddleLanguage::French => "french",
            PaddleLanguage::Latin => "latin",
            PaddleLanguage::Cyrillic => "cyrillic",
            PaddleLanguage::TraditionalChinese => "traditional_chinese",
            PaddleLanguage::Thai => "thai",
            PaddleLanguage::Greek => "greek",
            PaddleLanguage::EastSlavic => "east_slavic",
            PaddleLanguage::Arabic => "arabic",
            PaddleLanguage::Devanagari => "devanagari",
            PaddleLanguage::Tamil => "tamil",
            PaddleLanguage::Telugu => "telugu",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for PaddleLanguage {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "english" | "English" => Ok(PaddleLanguage::English),
            "chinese" | "Chinese" => Ok(PaddleLanguage::Chinese),
            "japanese" | "Japanese" => Ok(PaddleLanguage::Japanese),
            "korean" | "Korean" => Ok(PaddleLanguage::Korean),
            "german" | "German" => Ok(PaddleLanguage::German),
            "french" | "French" => Ok(PaddleLanguage::French),
            "latin" | "Latin" => Ok(PaddleLanguage::Latin),
            "cyrillic" | "Cyrillic" => Ok(PaddleLanguage::Cyrillic),
            "traditional_chinese" | "TraditionalChinese" => Ok(PaddleLanguage::TraditionalChinese),
            "thai" | "Thai" => Ok(PaddleLanguage::Thai),
            "greek" | "Greek" => Ok(PaddleLanguage::Greek),
            "east_slavic" | "EastSlavic" => Ok(PaddleLanguage::EastSlavic),
            "arabic" | "Arabic" => Ok(PaddleLanguage::Arabic),
            "devanagari" | "Devanagari" => Ok(PaddleLanguage::Devanagari),
            "tamil" | "Tamil" => Ok(PaddleLanguage::Tamil),
            "telugu" | "Telugu" => Ok(PaddleLanguage::Telugu),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid PaddleLanguage value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for PaddleLanguage {}
unsafe impl TryConvertOwned for PaddleLanguage {}

#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LayoutClass {
    Caption,
    Footnote,
    Formula,
    ListItem,
    PageFooter,
    PageHeader,
    Picture,
    SectionHeader,
    Table,
    Text,
    Title,
    DocumentIndex,
    Code,
    CheckboxSelected,
    CheckboxUnselected,
    Form,
    KeyValueRegion,
}

impl Default for LayoutClass {
    fn default() -> Self {
        Self::Caption
    }
}

impl magnus::IntoValue for LayoutClass {
    fn into_value_with(self, handle: &Ruby) -> magnus::Value {
        let sym = match self {
            LayoutClass::Caption => "caption",
            LayoutClass::Footnote => "footnote",
            LayoutClass::Formula => "formula",
            LayoutClass::ListItem => "list_item",
            LayoutClass::PageFooter => "page_footer",
            LayoutClass::PageHeader => "page_header",
            LayoutClass::Picture => "picture",
            LayoutClass::SectionHeader => "section_header",
            LayoutClass::Table => "table",
            LayoutClass::Text => "text",
            LayoutClass::Title => "title",
            LayoutClass::DocumentIndex => "document_index",
            LayoutClass::Code => "code",
            LayoutClass::CheckboxSelected => "checkbox_selected",
            LayoutClass::CheckboxUnselected => "checkbox_unselected",
            LayoutClass::Form => "form",
            LayoutClass::KeyValueRegion => "key_value_region",
        };
        handle.to_symbol(sym).into_value_with(handle)
    }
}

impl magnus::TryConvert for LayoutClass {
    fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
        let s: String = magnus::TryConvert::try_convert(val)?;
        // Accept the serde wire name (snake_case), the PascalCase Rust variant name,
        // and a lowercase fallback so fixtures written in any of those styles work.
        match s.as_str() {
            "caption" | "Caption" => Ok(LayoutClass::Caption),
            "footnote" | "Footnote" => Ok(LayoutClass::Footnote),
            "formula" | "Formula" => Ok(LayoutClass::Formula),
            "list_item" | "ListItem" => Ok(LayoutClass::ListItem),
            "page_footer" | "PageFooter" => Ok(LayoutClass::PageFooter),
            "page_header" | "PageHeader" => Ok(LayoutClass::PageHeader),
            "picture" | "Picture" => Ok(LayoutClass::Picture),
            "section_header" | "SectionHeader" => Ok(LayoutClass::SectionHeader),
            "table" | "Table" => Ok(LayoutClass::Table),
            "text" | "Text" => Ok(LayoutClass::Text),
            "title" | "Title" => Ok(LayoutClass::Title),
            "document_index" | "DocumentIndex" => Ok(LayoutClass::DocumentIndex),
            "code" | "Code" => Ok(LayoutClass::Code),
            "checkbox_selected" | "CheckboxSelected" => Ok(LayoutClass::CheckboxSelected),
            "checkbox_unselected" | "CheckboxUnselected" => Ok(LayoutClass::CheckboxUnselected),
            "form" | "Form" => Ok(LayoutClass::Form),
            "key_value_region" | "KeyValueRegion" => Ok(LayoutClass::KeyValueRegion),
            other => Err(magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_arg_error(),
                format!("invalid LayoutClass value: {other}"),
            )),
        }
    }
}

unsafe impl IntoValueFromNative for LayoutClass {}
unsafe impl TryConvertOwned for LayoutClass {}

fn extract_bytes(args: &[magnus::Value]) -> Result<ExtractionResult, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<u8>, String), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (content, mime_type) = args.required;

    let (config,) = args.optional;

    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::extract_bytes(&content, &mime_type, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into())
}

fn extract_bytes_async(args: &[magnus::Value]) -> Result<ExtractionResult, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<u8>, String), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (content, mime_type) = args.required;

    let (config,) = args.optional;

    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::extract_bytes(&content, &mime_type, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into())
}

fn extract_file(args: &[magnus::Value]) -> Result<ExtractionResult, Error> {
    let args = magnus::scan_args::scan_args::<(String,), (Option<magnus::Value>, Option<magnus::Value>), (), (), (), ()>(
        args,
    )?;
    let (path,) = args.required;

    let (mime_type, config) = args.optional;

    let mime_type: Option<String> = mime_type.and_then(|v| {
        if v.is_nil() {
            None
        } else {
            Some(String::try_convert(v).unwrap_or_default())
        }
    });

    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async {
            kreuzberg::extract_file(std::path::PathBuf::from(path), mime_type.as_deref(), &config_core).await
        })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into())
}

fn extract_file_async(args: &[magnus::Value]) -> Result<ExtractionResult, Error> {
    let args = magnus::scan_args::scan_args::<(String,), (Option<magnus::Value>, Option<magnus::Value>), (), (), (), ()>(
        args,
    )?;
    let (path,) = args.required;

    let (mime_type, config) = args.optional;

    let mime_type: Option<String> = mime_type.and_then(|v| {
        if v.is_nil() {
            None
        } else {
            Some(String::try_convert(v).unwrap_or_default())
        }
    });

    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async {
            kreuzberg::extract_file(std::path::PathBuf::from(path), mime_type.as_deref(), &config_core).await
        })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into())
}

fn extract_file_sync(args: &[magnus::Value]) -> Result<ExtractionResult, Error> {
    let args = magnus::scan_args::scan_args::<(String,), (Option<magnus::Value>, Option<magnus::Value>), (), (), (), ()>(
        args,
    )?;
    let (path,) = args.required;

    let (mime_type, config) = args.optional;

    let mime_type: Option<String> = mime_type.and_then(|v| {
        if v.is_nil() {
            None
        } else {
            Some(String::try_convert(v).unwrap_or_default())
        }
    });

    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let result = kreuzberg::extract_file_sync(std::path::PathBuf::from(path), mime_type.as_deref(), &config_core)
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into())
}

fn extract_bytes_sync(args: &[magnus::Value]) -> Result<ExtractionResult, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<u8>, String), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (content, mime_type) = args.required;

    let (config,) = args.optional;

    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let result = kreuzberg::extract_bytes_sync(&content, &mime_type, &config_core).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result.into())
}

fn batch_extract_files_sync(args: &[magnus::Value]) -> Result<Vec<ExtractionResult>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<BatchFileItem>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (items,) = args.required;

    let (config,) = args.optional;

    let items_core: Vec<kreuzberg::BatchFileItem> = items.into_iter().map(Into::into).collect();
    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let result = kreuzberg::batch_extract_files_sync(items_core, &config_core).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result.into_iter().map(Into::into).collect())
}

fn batch_extract_bytes_sync(args: &[magnus::Value]) -> Result<Vec<ExtractionResult>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<BatchBytesItem>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (items,) = args.required;

    let (config,) = args.optional;

    let items_core: Vec<kreuzberg::BatchBytesItem> = items.into_iter().map(Into::into).collect();
    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let result = kreuzberg::batch_extract_bytes_sync(items_core, &config_core).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result.into_iter().map(Into::into).collect())
}

fn batch_extract_files(args: &[magnus::Value]) -> Result<Vec<ExtractionResult>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<BatchFileItem>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (items,) = args.required;

    let (config,) = args.optional;

    let items_core: Vec<kreuzberg::BatchFileItem> = items.into_iter().map(Into::into).collect();
    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::batch_extract_files(items_core, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into_iter().map(Into::into).collect())
}

fn batch_extract_files_async(args: &[magnus::Value]) -> Result<Vec<ExtractionResult>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<BatchFileItem>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (items,) = args.required;

    let (config,) = args.optional;

    let items_core: Vec<kreuzberg::BatchFileItem> = items.into_iter().map(Into::into).collect();
    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::batch_extract_files(items_core, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into_iter().map(Into::into).collect())
}

fn batch_extract_bytes(args: &[magnus::Value]) -> Result<Vec<ExtractionResult>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<BatchBytesItem>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (items,) = args.required;

    let (config,) = args.optional;

    let items_core: Vec<kreuzberg::BatchBytesItem> = items.into_iter().map(Into::into).collect();
    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::batch_extract_bytes(items_core, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into_iter().map(Into::into).collect())
}

fn batch_extract_bytes_async(args: &[magnus::Value]) -> Result<Vec<ExtractionResult>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<BatchBytesItem>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (items,) = args.required;

    let (config,) = args.optional;

    let items_core: Vec<kreuzberg::BatchBytesItem> = items.into_iter().map(Into::into).collect();
    let config_core: kreuzberg::ExtractionConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: ExtractionConfig = ExtractionConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::batch_extract_bytes(items_core, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result.into_iter().map(Into::into).collect())
}

fn detect_mime_type_from_bytes(content: Vec<u8>) -> Result<String, Error> {
    let result = kreuzberg::detect_mime_type_from_bytes(&content).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn get_extensions_for_mime(mime_type: String) -> Result<Vec<String>, Error> {
    let result = kreuzberg::get_extensions_for_mime(&mime_type).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn list_embedding_backends() -> Result<Vec<String>, Error> {
    let result = kreuzberg::list_embedding_backends().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn list_document_extractors() -> Result<Vec<String>, Error> {
    let result = kreuzberg::list_document_extractors().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn list_ocr_backends() -> Result<Vec<String>, Error> {
    let result = kreuzberg::list_ocr_backends().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn list_post_processors() -> Result<Vec<String>, Error> {
    let result = kreuzberg::list_post_processors().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn list_renderers() -> Result<Vec<String>, Error> {
    let result = kreuzberg::list_renderers().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn list_validators() -> Result<Vec<String>, Error> {
    let result = kreuzberg::list_validators().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

#[allow(unused_variables)]
fn compare(args: &[magnus::Value]) -> Result<ExtractionDiff, Error> {
    let args =
        magnus::scan_args::scan_args::<(magnus::Value, magnus::Value), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (a, b) = args.required;

    let (opts,) = args.optional;

    let a: kreuzberg::ExtractionResult = {
        let binding_val: ExtractionResult = ExtractionResult::try_convert(a)
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))?;
        binding_val.into()
    };
    let b: kreuzberg::ExtractionResult = {
        let binding_val: ExtractionResult = ExtractionResult::try_convert(b)
            .map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))?;
        binding_val.into()
    };
    let opts: kreuzberg::DiffOptions = match opts {
        Some(_v) if !_v.is_nil() => {
            let binding_val: DiffOptions = DiffOptions::try_convert(_v).map_err(|e| {
                magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string())
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    Err(magnus::Error::new(
        unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
        "Not implemented: compare",
    ))
}

fn embed_texts_async(args: &[magnus::Value]) -> Result<Vec<Vec<f32>>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<String>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (texts,) = args.required;

    let (config,) = args.optional;

    let config_core: kreuzberg::EmbeddingConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: EmbeddingConfig = EmbeddingConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::embed_texts_async(texts, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result)
}

fn embed_texts_async_async(args: &[magnus::Value]) -> Result<Vec<Vec<f32>>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<String>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (texts,) = args.required;

    let (config,) = args.optional;

    let config_core: kreuzberg::EmbeddingConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: EmbeddingConfig = EmbeddingConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let rt = tokio::runtime::Runtime::new().map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    let result = rt
        .block_on(async { kreuzberg::embed_texts_async(texts, &config_core).await })
        .map_err(|e| {
            magnus::Error::new(
                unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                e.to_string(),
            )
        })?;
    Ok(result)
}

fn render_pdf_page_to_png(args: &[magnus::Value]) -> Result<Vec<u8>, Error> {
    let args =
        magnus::scan_args::scan_args::<(Vec<u8>, usize), (Option<i32>, Option<magnus::Value>), (), (), (), ()>(args)?;
    let (pdf_bytes, page_index) = args.required;

    let (dpi, password) = args.optional;

    let password: Option<String> = password.and_then(|v| {
        if v.is_nil() {
            None
        } else {
            Some(String::try_convert(v).unwrap_or_default())
        }
    });

    let result = kreuzberg::render_pdf_page_to_png(&pdf_bytes, page_index, dpi, password.as_deref()).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result.to_vec())
}

fn detect_mime_type(path: String, check_exists: bool) -> Result<String, Error> {
    let result = kreuzberg::detect_mime_type(path, check_exists).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn embed_texts(args: &[magnus::Value]) -> Result<Vec<Vec<f32>>, Error> {
    let args = magnus::scan_args::scan_args::<(Vec<String>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
    let (texts,) = args.required;

    let (config,) = args.optional;

    let config_core: kreuzberg::EmbeddingConfig = match config {
        Some(_v) if !_v.is_nil() => {
            let binding_val: EmbeddingConfig = EmbeddingConfig::try_convert(_v).map_err(|e| {
                magnus::Error::new(
                    unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
                    e.to_string(),
                )
            })?;
            binding_val.into()
        }
        _ => Default::default(),
    };
    let result = kreuzberg::embed_texts(texts, &config_core).map_err(|e| {
        magnus::Error::new(
            unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
            e.to_string(),
        )
    })?;
    Ok(result)
}

fn get_embedding_preset(name: String) -> Option<EmbeddingPreset> {
    kreuzberg::get_embedding_preset(&name).map(Into::into)
}

fn list_embedding_presets() -> Vec<String> {
    kreuzberg::list_embedding_presets()
}

#[allow(unused_imports)]
use magnus::TryConvert as _;
#[allow(unused_imports)]
use magnus::value::InnerValue as _;
/// Wrapper that bridges a foreign Rb object to the `OcrBackend` trait.
pub struct RbOcrBackendBridge {
    inner: magnus::value::Opaque<magnus::Value>,
    cached_name: String,
}

impl std::fmt::Debug for RbOcrBackendBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RbOcrBackendBridge")
    }
}

impl RbOcrBackendBridge {
    /// Create a new bridge wrapping a Ruby object.
    /// Validates that the Ruby object responds to all required methods.
    pub fn new(rb_obj: magnus::Value, name: String) -> Result<Self, magnus::Error> {
        if !rb_obj.respond_to("process_image", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "process_image"),
            ));
        }
        if !rb_obj.respond_to("supports_language", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "supports_language"),
            ));
        }
        if !rb_obj.respond_to("backend_type", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "backend_type"),
            ));
        }
        Ok(Self {
            inner: magnus::value::Opaque::from(rb_obj),
            cached_name: name,
        })
    }
}

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

    fn version(&self) -> String {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("version", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <String as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("initialize", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'initialize' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }

    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("shutdown", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'shutdown' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::OcrBackend for RbOcrBackendBridge {
    async fn process_image(
        &self,
        image_bytes: &[u8],
        config: &kreuzberg::OcrConfig,
    ) -> std::result::Result<kreuzberg::ExtractionResult, kreuzberg::KreuzbergError> {
        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        let cached_name_for_blocking = cached_name.clone();

        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        // cached_name is referenced both inside the spawn_blocking closure and after
        // the await for the JoinError fallback, so clone once for each consumer.
        let cached_name_for_blocking = cached_name.clone();
        let image_bytes_owned = image_bytes.to_vec();
        let config_owned = config.clone();

        let join: std::result::Result<
            std::result::Result<kreuzberg::ExtractionResult, kreuzberg::KreuzbergError>,
            tokio::task::JoinError,
        > = tokio::task::spawn_blocking(
            move || -> std::result::Result<kreuzberg::ExtractionResult, kreuzberg::KreuzbergError> {
                // SAFETY: spawn_blocking thread acquires GVL via Ruby::get_unchecked
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                let value = inner.get_inner_with(&ruby);
                let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
                    "process_image",
                    (
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            ruby.str_new(String::from_utf8_lossy(AsRef::<[u8]>::as_ref(&image_bytes_owned)).as_ref())
                                .as_value()
                        },
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            serde_json::to_string(&config_owned)
                                .ok()
                                .map(|s| ruby.str_new(s.as_str()).as_value())
                                .unwrap_or_else(|| ruby.qnil().as_value())
                        },
                    ),
                ) {
                    Ok(v) => v,
                    Err(e) => {
                        return Err(kreuzberg::KreuzbergError::Other(format!(
                            "Plugin '{}' method 'process_image' failed: {}",
                            cached_name_for_blocking, e
                        )));
                    }
                };
                let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
                    s
                } else {
                    match val.funcall::<_, _, String>("to_json", ()) {
                        Ok(s) => s,
                        Err(e) => {
                            return Err(kreuzberg::KreuzbergError::Other(format!(
                                "Ruby method 'process_image' returned non-JSON value: {}",
                                e
                            )));
                        }
                    }
                };
                serde_json::from_str::<kreuzberg::ExtractionResult>(&json_str).map_err(|e| {
                    kreuzberg::KreuzbergError::Other(format!(
                        "Failed to deserialize Ruby 'process_image' return value: {}",
                        e
                    ))
                })
            },
        )
        .await;

        match join {
            Ok(v) => v,
            Err(e) => Err(kreuzberg::KreuzbergError::Other(format!(
                "spawn_blocking failed for '{}': {}",
                cached_name, e
            ))),
        }
    }

    fn supports_language(&self, lang: &str) -> bool {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
            "supports_language",
            ({
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                ruby.str_new(AsRef::<str>::as_ref(&lang)).as_value()
            },),
        ) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <bool as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn backend_type(&self) -> kreuzberg::OcrBackendType {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("backend_type", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            match val.funcall::<_, _, String>("to_json", ()) {
                Ok(s) => s,
                Err(e) => {
                    let _ = e;
                    return Default::default();
                }
            }
        };
        serde_json::from_str::<kreuzberg::OcrBackendType>(&json_str).unwrap_or_default()
    }
}

pub fn register_ocr_backend(rb_obj: magnus::Value, name: String) -> Result<(), magnus::Error> {
    let required_methods = ["process_image", "supports_language", "backend_type"];
    for method in &required_methods {
        if !rb_obj.respond_to(*method, false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Backend missing required method: {}", method),
            ));
        }
    }
    let wrapper = RbOcrBackendBridge::new(rb_obj, name)?;
    let arc: Arc<dyn kreuzberg::OcrBackend> = Arc::new(wrapper);

    kreuzberg::plugins::registry::get_ocr_backend_registry()
        .write()
        .register(arc)
        .map_err(|e| {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            magnus::Error::new(ruby.exception_runtime_error(), format!("register failed: {}", e))
        })?;
    Ok(())
}

pub fn unregister_ocr_backend(name: String) -> Result<(), magnus::Error> {
    kreuzberg::plugins::ocr_backend::unregister_ocr_backend(&name).map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

pub fn clear_ocr_backends() -> Result<(), magnus::Error> {
    kreuzberg::plugins::ocr_backend::clear_ocr_backends().map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

#[allow(unused_imports)]
use magnus::TryConvert as _;
#[allow(unused_imports)]
use magnus::value::InnerValue as _;
/// Wrapper that bridges a foreign Rb object to the `PostProcessor` trait.
pub struct RbPostProcessorBridge {
    inner: magnus::value::Opaque<magnus::Value>,
    cached_name: String,
}

impl std::fmt::Debug for RbPostProcessorBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RbPostProcessorBridge")
    }
}

impl RbPostProcessorBridge {
    /// Create a new bridge wrapping a Ruby object.
    /// Validates that the Ruby object responds to all required methods.
    pub fn new(rb_obj: magnus::Value, name: String) -> Result<Self, magnus::Error> {
        if !rb_obj.respond_to("process", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "process"),
            ));
        }
        if !rb_obj.respond_to("processing_stage", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "processing_stage"),
            ));
        }
        Ok(Self {
            inner: magnus::value::Opaque::from(rb_obj),
            cached_name: name,
        })
    }
}

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

    fn version(&self) -> String {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("version", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <String as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("initialize", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'initialize' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }

    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("shutdown", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'shutdown' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::PostProcessor for RbPostProcessorBridge {
    async fn process(
        &self,
        result: &mut kreuzberg::ExtractionResult,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        let cached_name_for_blocking = cached_name.clone();

        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        // cached_name is referenced both inside the spawn_blocking closure and after
        // the await for the JoinError fallback, so clone once for each consumer.
        let cached_name_for_blocking = cached_name.clone();
        let result_owned = result.clone();
        let config_owned = config.clone();

        let join: std::result::Result<std::result::Result<(), kreuzberg::KreuzbergError>, tokio::task::JoinError> =
            tokio::task::spawn_blocking(move || -> std::result::Result<(), kreuzberg::KreuzbergError> {
                // SAFETY: spawn_blocking thread acquires GVL via Ruby::get_unchecked
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                let value = inner.get_inner_with(&ruby);
                let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
                    "process",
                    (
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            serde_json::to_string(&result_owned)
                                .ok()
                                .map(|s| ruby.str_new(s.as_str()).as_value())
                                .unwrap_or_else(|| ruby.qnil().as_value())
                        },
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            serde_json::to_string(&config_owned)
                                .ok()
                                .map(|s| ruby.str_new(s.as_str()).as_value())
                                .unwrap_or_else(|| ruby.qnil().as_value())
                        },
                    ),
                ) {
                    Ok(v) => v,
                    Err(e) => {
                        return Err(kreuzberg::KreuzbergError::Other(format!(
                            "Plugin '{}' method 'process' failed: {}",
                            cached_name_for_blocking, e
                        )));
                    }
                };
                let _ = val;
                Ok(())
            })
            .await;

        match join {
            Ok(v) => v,
            Err(e) => Err(kreuzberg::KreuzbergError::Other(format!(
                "spawn_blocking failed for '{}': {}",
                cached_name, e
            ))),
        }
    }

    fn processing_stage(&self) -> kreuzberg::ProcessingStage {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("processing_stage", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
            s
        } else {
            match val.funcall::<_, _, String>("to_json", ()) {
                Ok(s) => s,
                Err(e) => {
                    let _ = e;
                    return Default::default();
                }
            }
        };
        serde_json::from_str::<kreuzberg::ProcessingStage>(&json_str).unwrap_or_default()
    }
}

pub fn register_post_processor(rb_obj: magnus::Value, name: String) -> Result<(), magnus::Error> {
    let required_methods = ["process", "processing_stage"];
    for method in &required_methods {
        if !rb_obj.respond_to(*method, false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Backend missing required method: {}", method),
            ));
        }
    }
    let wrapper = RbPostProcessorBridge::new(rb_obj, name)?;
    let arc: Arc<dyn kreuzberg::PostProcessor> = Arc::new(wrapper);

    kreuzberg::plugins::registry::get_post_processor_registry()
        .write()
        .register(arc)
        .map_err(|e| {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            magnus::Error::new(ruby.exception_runtime_error(), format!("register failed: {}", e))
        })?;
    Ok(())
}

pub fn unregister_post_processor(name: String) -> Result<(), magnus::Error> {
    kreuzberg::plugins::post_processor::unregister_post_processor(&name).map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

pub fn clear_post_processors() -> Result<(), magnus::Error> {
    kreuzberg::plugins::post_processor::clear_post_processors().map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

#[allow(unused_imports)]
use magnus::TryConvert as _;
#[allow(unused_imports)]
use magnus::value::InnerValue as _;
/// Wrapper that bridges a foreign Rb object to the `Validator` trait.
pub struct RbValidatorBridge {
    inner: magnus::value::Opaque<magnus::Value>,
    cached_name: String,
}

impl std::fmt::Debug for RbValidatorBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RbValidatorBridge")
    }
}

impl RbValidatorBridge {
    /// Create a new bridge wrapping a Ruby object.
    /// Validates that the Ruby object responds to all required methods.
    pub fn new(rb_obj: magnus::Value, name: String) -> Result<Self, magnus::Error> {
        if !rb_obj.respond_to("validate", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "validate"),
            ));
        }
        Ok(Self {
            inner: magnus::value::Opaque::from(rb_obj),
            cached_name: name,
        })
    }
}

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

    fn version(&self) -> String {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("version", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <String as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("initialize", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'initialize' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }

    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("shutdown", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'shutdown' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::Validator for RbValidatorBridge {
    async fn validate(
        &self,
        result: &kreuzberg::ExtractionResult,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        let cached_name_for_blocking = cached_name.clone();

        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        // cached_name is referenced both inside the spawn_blocking closure and after
        // the await for the JoinError fallback, so clone once for each consumer.
        let cached_name_for_blocking = cached_name.clone();
        let result_owned = result.clone();
        let config_owned = config.clone();

        let join: std::result::Result<std::result::Result<(), kreuzberg::KreuzbergError>, tokio::task::JoinError> =
            tokio::task::spawn_blocking(move || -> std::result::Result<(), kreuzberg::KreuzbergError> {
                // SAFETY: spawn_blocking thread acquires GVL via Ruby::get_unchecked
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                let value = inner.get_inner_with(&ruby);
                let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
                    "validate",
                    (
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            serde_json::to_string(&result_owned)
                                .ok()
                                .map(|s| ruby.str_new(s.as_str()).as_value())
                                .unwrap_or_else(|| ruby.qnil().as_value())
                        },
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            serde_json::to_string(&config_owned)
                                .ok()
                                .map(|s| ruby.str_new(s.as_str()).as_value())
                                .unwrap_or_else(|| ruby.qnil().as_value())
                        },
                    ),
                ) {
                    Ok(v) => v,
                    Err(e) => {
                        return Err(kreuzberg::KreuzbergError::Other(format!(
                            "Plugin '{}' method 'validate' failed: {}",
                            cached_name_for_blocking, e
                        )));
                    }
                };
                let _ = val;
                Ok(())
            })
            .await;

        match join {
            Ok(v) => v,
            Err(e) => Err(kreuzberg::KreuzbergError::Other(format!(
                "spawn_blocking failed for '{}': {}",
                cached_name, e
            ))),
        }
    }
}

pub fn register_validator(rb_obj: magnus::Value, name: String) -> Result<(), magnus::Error> {
    let required_methods = ["validate"];
    for method in &required_methods {
        if !rb_obj.respond_to(*method, false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Backend missing required method: {}", method),
            ));
        }
    }
    let wrapper = RbValidatorBridge::new(rb_obj, name)?;
    let arc: Arc<dyn kreuzberg::Validator> = Arc::new(wrapper);

    kreuzberg::plugins::registry::get_validator_registry()
        .write()
        .register(arc)
        .map_err(|e| {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            magnus::Error::new(ruby.exception_runtime_error(), format!("register failed: {}", e))
        })?;
    Ok(())
}

pub fn unregister_validator(name: String) -> Result<(), magnus::Error> {
    kreuzberg::plugins::validator::unregister_validator(&name).map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

pub fn clear_validators() -> Result<(), magnus::Error> {
    kreuzberg::plugins::validator::clear_validators().map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

#[allow(unused_imports)]
use magnus::TryConvert as _;
#[allow(unused_imports)]
use magnus::value::InnerValue as _;
/// Wrapper that bridges a foreign Rb object to the `EmbeddingBackend` trait.
pub struct RbEmbeddingBackendBridge {
    inner: magnus::value::Opaque<magnus::Value>,
    cached_name: String,
}

impl std::fmt::Debug for RbEmbeddingBackendBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RbEmbeddingBackendBridge")
    }
}

impl RbEmbeddingBackendBridge {
    /// Create a new bridge wrapping a Ruby object.
    /// Validates that the Ruby object responds to all required methods.
    pub fn new(rb_obj: magnus::Value, name: String) -> Result<Self, magnus::Error> {
        if !rb_obj.respond_to("dimensions", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "dimensions"),
            ));
        }
        if !rb_obj.respond_to("embed", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "embed"),
            ));
        }
        Ok(Self {
            inner: magnus::value::Opaque::from(rb_obj),
            cached_name: name,
        })
    }
}

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

    fn version(&self) -> String {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("version", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <String as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("initialize", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'initialize' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }

    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("shutdown", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'shutdown' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::EmbeddingBackend for RbEmbeddingBackendBridge {
    fn dimensions(&self) -> usize {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("dimensions", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <usize as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    async fn embed(&self, texts: Vec<String>) -> std::result::Result<Vec<Vec<f32>>, kreuzberg::KreuzbergError> {
        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        let cached_name_for_blocking = cached_name.clone();

        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        // cached_name is referenced both inside the spawn_blocking closure and after
        // the await for the JoinError fallback, so clone once for each consumer.
        let cached_name_for_blocking = cached_name.clone();
        let texts_owned = texts.clone();

        let join: std::result::Result<
            std::result::Result<Vec<Vec<f32>>, kreuzberg::KreuzbergError>,
            tokio::task::JoinError,
        > = tokio::task::spawn_blocking(
            move || -> std::result::Result<Vec<Vec<f32>>, kreuzberg::KreuzbergError> {
                // SAFETY: spawn_blocking thread acquires GVL via Ruby::get_unchecked
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                let value = inner.get_inner_with(&ruby);
                let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
                    "embed",
                    ({
                        let ruby = unsafe { magnus::Ruby::get_unchecked() };
                        serde_json::to_string(&texts_owned)
                            .ok()
                            .map(|s| ruby.str_new(s.as_str()).as_value())
                            .unwrap_or_else(|| ruby.qnil().as_value())
                    },),
                ) {
                    Ok(v) => v,
                    Err(e) => {
                        return Err(kreuzberg::KreuzbergError::Other(format!(
                            "Plugin '{}' method 'embed' failed: {}",
                            cached_name_for_blocking, e
                        )));
                    }
                };
                <Vec<Vec<f32>> as magnus::TryConvert>::try_convert(val).map_err(|e| {
                    kreuzberg::KreuzbergError::Other(format!("Failed to convert Ruby 'embed' return value: {}", e))
                })
            },
        )
        .await;

        match join {
            Ok(v) => v,
            Err(e) => Err(kreuzberg::KreuzbergError::Other(format!(
                "spawn_blocking failed for '{}': {}",
                cached_name, e
            ))),
        }
    }
}

pub fn register_embedding_backend(rb_obj: magnus::Value, name: String) -> Result<(), magnus::Error> {
    let required_methods = ["dimensions", "embed"];
    for method in &required_methods {
        if !rb_obj.respond_to(*method, false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Backend missing required method: {}", method),
            ));
        }
    }
    let wrapper = RbEmbeddingBackendBridge::new(rb_obj, name)?;
    let arc: Arc<dyn kreuzberg::EmbeddingBackend> = Arc::new(wrapper);

    kreuzberg::plugins::registry::get_embedding_backend_registry()
        .write()
        .register(arc)
        .map_err(|e| {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            magnus::Error::new(ruby.exception_runtime_error(), format!("register failed: {}", e))
        })?;
    Ok(())
}

pub fn unregister_embedding_backend(name: String) -> Result<(), magnus::Error> {
    kreuzberg::plugins::embedding_backend::unregister_embedding_backend(&name).map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

pub fn clear_embedding_backends() -> Result<(), magnus::Error> {
    kreuzberg::plugins::embedding_backend::clear_embedding_backends().map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

#[allow(unused_imports)]
use magnus::TryConvert as _;
#[allow(unused_imports)]
use magnus::value::InnerValue as _;
/// Wrapper that bridges a foreign Rb object to the `DocumentExtractor` trait.
pub struct RbDocumentExtractorBridge {
    inner: magnus::value::Opaque<magnus::Value>,
    cached_name: String,
}

impl std::fmt::Debug for RbDocumentExtractorBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RbDocumentExtractorBridge")
    }
}

impl RbDocumentExtractorBridge {
    /// Create a new bridge wrapping a Ruby object.
    /// Validates that the Ruby object responds to all required methods.
    pub fn new(rb_obj: magnus::Value, name: String) -> Result<Self, magnus::Error> {
        if !rb_obj.respond_to("extract_bytes", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "extract_bytes"),
            ));
        }
        if !rb_obj.respond_to("supported_mime_types", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "supported_mime_types"),
            ));
        }
        Ok(Self {
            inner: magnus::value::Opaque::from(rb_obj),
            cached_name: name,
        })
    }
}

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

    fn version(&self) -> String {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("version", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <String as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("initialize", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'initialize' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }

    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("shutdown", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'shutdown' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::DocumentExtractor for RbDocumentExtractorBridge {
    async fn extract_bytes(
        &self,
        content: &[u8],
        mime_type: &str,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<kreuzberg::InternalDocument, kreuzberg::KreuzbergError> {
        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        let cached_name_for_blocking = cached_name.clone();

        let inner = self.inner;
        let cached_name = self.cached_name.clone();
        // cached_name is referenced both inside the spawn_blocking closure and after
        // the await for the JoinError fallback, so clone once for each consumer.
        let cached_name_for_blocking = cached_name.clone();
        let content_owned = content.to_vec();
        let mime_type_owned = mime_type.to_string();
        let config_owned = config.clone();

        let join: std::result::Result<
            std::result::Result<kreuzberg::InternalDocument, kreuzberg::KreuzbergError>,
            tokio::task::JoinError,
        > = tokio::task::spawn_blocking(
            move || -> std::result::Result<kreuzberg::InternalDocument, kreuzberg::KreuzbergError> {
                // SAFETY: spawn_blocking thread acquires GVL via Ruby::get_unchecked
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                let value = inner.get_inner_with(&ruby);
                let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
                    "extract_bytes",
                    (
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            ruby.str_new(String::from_utf8_lossy(AsRef::<[u8]>::as_ref(&content_owned)).as_ref())
                                .as_value()
                        },
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            ruby.str_new(AsRef::<str>::as_ref(&mime_type_owned.as_str())).as_value()
                        },
                        {
                            let ruby = unsafe { magnus::Ruby::get_unchecked() };
                            serde_json::to_string(&config_owned)
                                .ok()
                                .map(|s| ruby.str_new(s.as_str()).as_value())
                                .unwrap_or_else(|| ruby.qnil().as_value())
                        },
                    ),
                ) {
                    Ok(v) => v,
                    Err(e) => {
                        return Err(kreuzberg::KreuzbergError::Other(format!(
                            "Plugin '{}' method 'extract_bytes' failed: {}",
                            cached_name_for_blocking, e
                        )));
                    }
                };
                let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
                    s
                } else {
                    match val.funcall::<_, _, String>("to_json", ()) {
                        Ok(s) => s,
                        Err(e) => {
                            return Err(kreuzberg::KreuzbergError::Other(format!(
                                "Ruby method 'extract_bytes' returned non-JSON value: {}",
                                e
                            )));
                        }
                    }
                };
                serde_json::from_str::<kreuzberg::InternalDocument>(&json_str).map_err(|e| {
                    kreuzberg::KreuzbergError::Other(format!(
                        "Failed to deserialize Ruby 'extract_bytes' return value: {}",
                        e
                    ))
                })
            },
        )
        .await;

        match join {
            Ok(v) => v,
            Err(e) => Err(kreuzberg::KreuzbergError::Other(format!(
                "spawn_blocking failed for '{}': {}",
                cached_name, e
            ))),
        }
    }

    fn supported_mime_types(&self) -> &[&str] {
        let __types: Vec<String> = {
            // SAFETY: bridge methods are only invoked from threads holding the GVL
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            let value = self.inner.get_inner_with(&ruby);
            let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("supported_mime_types", ()) {
                Ok(v) => v,
                Err(e) => {
                    let _ = e;
                    return Default::default();
                }
            };
            <Vec<String> as magnus::TryConvert>::try_convert(val).unwrap_or_default()
        };
        let __strs: Vec<&'static str> = __types
            .into_iter()
            .map(|s| -> &'static str { Box::leak(s.into_boxed_str()) })
            .collect();
        Box::leak(__strs.into_boxed_slice())
    }
}

pub fn register_document_extractor(rb_obj: magnus::Value, name: String) -> Result<(), magnus::Error> {
    let required_methods = ["extract_bytes", "supported_mime_types"];
    for method in &required_methods {
        if !rb_obj.respond_to(*method, false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Backend missing required method: {}", method),
            ));
        }
    }
    let wrapper = RbDocumentExtractorBridge::new(rb_obj, name)?;
    let arc: Arc<dyn kreuzberg::DocumentExtractor> = Arc::new(wrapper);

    kreuzberg::plugins::registry::get_document_extractor_registry()
        .write()
        .register(arc)
        .map_err(|e| {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            magnus::Error::new(ruby.exception_runtime_error(), format!("register failed: {}", e))
        })?;
    Ok(())
}

pub fn unregister_document_extractor(name: String) -> Result<(), magnus::Error> {
    kreuzberg::plugins::document_extractor::unregister_document_extractor(&name).map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

pub fn clear_document_extractors() -> Result<(), magnus::Error> {
    kreuzberg::plugins::document_extractor::clear_document_extractors().map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

#[allow(unused_imports)]
use magnus::TryConvert as _;
#[allow(unused_imports)]
use magnus::value::InnerValue as _;
/// Wrapper that bridges a foreign Rb object to the `Renderer` trait.
pub struct RbRendererBridge {
    inner: magnus::value::Opaque<magnus::Value>,
    cached_name: String,
}

impl std::fmt::Debug for RbRendererBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RbRendererBridge")
    }
}

impl RbRendererBridge {
    /// Create a new bridge wrapping a Ruby object.
    /// Validates that the Ruby object responds to all required methods.
    pub fn new(rb_obj: magnus::Value, name: String) -> Result<Self, magnus::Error> {
        if !rb_obj.respond_to("render", false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Ruby object missing required method: {}", "render"),
            ));
        }
        Ok(Self {
            inner: magnus::value::Opaque::from(rb_obj),
            cached_name: name,
        })
    }
}

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

    fn version(&self) -> String {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("version", ()) {
            Ok(v) => v,
            Err(e) => {
                let _ = e;
                return Default::default();
            }
        };
        <String as magnus::TryConvert>::try_convert(val).unwrap_or_default()
    }

    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("initialize", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'initialize' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }

    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>("shutdown", ()) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'shutdown' failed: {}",
                    e
                )));
            }
        };
        let _ = val;
        Ok(())
    }
}

impl kreuzberg::Renderer for RbRendererBridge {
    fn render(&self, doc: &kreuzberg::InternalDocument) -> std::result::Result<String, kreuzberg::KreuzbergError> {
        // SAFETY: bridge methods are only invoked from threads holding the GVL
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        let value = self.inner.get_inner_with(&ruby);
        let val: magnus::Value = match value.funcall::<_, _, magnus::Value>(
            "render",
            ({
                let ruby = unsafe { magnus::Ruby::get_unchecked() };
                serde_json::to_string(&doc)
                    .ok()
                    .map(|s| ruby.str_new(s.as_str()).as_value())
                    .unwrap_or_else(|| ruby.qnil().as_value())
            },),
        ) {
            Ok(v) => v,
            Err(e) => {
                return Err(kreuzberg::KreuzbergError::Other(format!(
                    "Ruby method 'render' failed: {}",
                    e
                )));
            }
        };
        <String as magnus::TryConvert>::try_convert(val).map_err(|e| {
            kreuzberg::KreuzbergError::Other(format!("Failed to convert Ruby 'render' return value: {}", e))
        })
    }
}

pub fn register_renderer(rb_obj: magnus::Value, name: String) -> Result<(), magnus::Error> {
    let required_methods = ["render"];
    for method in &required_methods {
        if !rb_obj.respond_to(*method, false).unwrap_or(false) {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            return Err(magnus::Error::new(
                ruby.exception_runtime_error(),
                format!("Backend missing required method: {}", method),
            ));
        }
    }
    let wrapper = RbRendererBridge::new(rb_obj, name)?;
    let arc: Arc<dyn kreuzberg::Renderer> = Arc::new(wrapper);

    kreuzberg::plugins::registry::get_renderer_registry()
        .write()
        .register(arc)
        .map_err(|e| {
            let ruby = unsafe { magnus::Ruby::get_unchecked() };
            magnus::Error::new(ruby.exception_runtime_error(), format!("register failed: {}", e))
        })?;
    Ok(())
}

pub fn unregister_renderer(name: String) -> Result<(), magnus::Error> {
    kreuzberg::plugins::renderer::unregister_renderer(&name).map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

pub fn clear_renderers() -> Result<(), magnus::Error> {
    kreuzberg::plugins::renderer::clear_renderers().map_err(|e| {
        let ruby = unsafe { magnus::Ruby::get_unchecked() };
        magnus::Error::new(ruby.exception_runtime_error(), format!("{}", e))
    })
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::CacheStats> for CacheStats {
    fn from(val: kreuzberg::CacheStats) -> Self {
        Self {
            total_files: val.total_files,
            total_size_mb: val.total_size_mb,
            available_space_mb: val.available_space_mb,
            oldest_file_age_days: val.oldest_file_age_days,
            newest_file_age_days: val.newest_file_age_days,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<AccelerationConfig> for kreuzberg::AccelerationConfig {
    fn from(val: AccelerationConfig) -> Self {
        Self {
            provider: val.provider.into(),
            device_id: val.device_id,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::AccelerationConfig> for AccelerationConfig {
    fn from(val: kreuzberg::AccelerationConfig) -> Self {
        Self {
            provider: val.provider.into(),
            device_id: val.device_id,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ContentFilterConfig> for kreuzberg::ContentFilterConfig {
    fn from(val: ContentFilterConfig) -> Self {
        Self {
            include_headers: val.include_headers,
            include_footers: val.include_footers,
            strip_repeating_text: val.strip_repeating_text,
            include_watermarks: val.include_watermarks,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ContentFilterConfig> for ContentFilterConfig {
    fn from(val: kreuzberg::ContentFilterConfig) -> Self {
        Self {
            include_headers: val.include_headers,
            include_footers: val.include_footers,
            strip_repeating_text: val.strip_repeating_text,
            include_watermarks: val.include_watermarks,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EmailConfig> for kreuzberg::EmailConfig {
    fn from(val: EmailConfig) -> Self {
        Self {
            msg_fallback_codepage: val.msg_fallback_codepage,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmailConfig> for EmailConfig {
    fn from(val: kreuzberg::EmailConfig) -> Self {
        Self {
            msg_fallback_codepage: val.msg_fallback_codepage,
        }
    }
}

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

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

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

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::FileExtractionConfig> for FileExtractionConfig {
    fn from(val: kreuzberg::FileExtractionConfig) -> Self {
        Self {
            enable_quality_processing: val.enable_quality_processing,
            ocr: val.ocr.map(Into::into),
            force_ocr: val.force_ocr,
            force_ocr_pages: val.force_ocr_pages.map(|v| v.into_iter().collect()),
            disable_ocr: val.disable_ocr,
            chunking: val.chunking.map(Into::into),
            content_filter: val.content_filter.map(Into::into),
            images: val.images.map(Into::into),
            pdf_options: val.pdf_options.map(Into::into),
            token_reduction: val.token_reduction.map(Into::into),
            language_detection: val.language_detection.map(Into::into),
            pages: val.pages.map(Into::into),
            keywords: val.keywords.map(Into::into),
            postprocessor: val.postprocessor.map(Into::into),
            html_options: val.html_options.as_ref().map(|v| format!("{v:?}")),
            result_format: val.result_format.map(Into::into),
            output_format: val.output_format.map(Into::into),
            include_document_structure: val.include_document_structure,
            layout: val.layout.map(Into::into),
            timeout_secs: val.timeout_secs,
            tree_sitter: val.tree_sitter.map(Into::into),
            structured_extraction: val.structured_extraction.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<BatchBytesItem> for kreuzberg::BatchBytesItem {
    fn from(val: BatchBytesItem) -> Self {
        Self {
            content: val.content.to_vec().into(),
            mime_type: val.mime_type,
            config: val.config.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::BatchBytesItem> for BatchBytesItem {
    fn from(val: kreuzberg::BatchBytesItem) -> Self {
        Self {
            content: val.content.to_vec().into(),
            mime_type: val.mime_type,
            config: val.config.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<BatchFileItem> for kreuzberg::BatchFileItem {
    fn from(val: BatchFileItem) -> Self {
        Self {
            path: val.path.into(),
            config: val.config.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::BatchFileItem> for BatchFileItem {
    fn from(val: kreuzberg::BatchFileItem) -> Self {
        Self {
            path: val.path.to_string_lossy().to_string(),
            config: val.config.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ImageExtractionConfig> for kreuzberg::ImageExtractionConfig {
    fn from(val: ImageExtractionConfig) -> Self {
        Self {
            extract_images: val.extract_images,
            target_dpi: val.target_dpi,
            max_image_dimension: val.max_image_dimension,
            inject_placeholders: val.inject_placeholders,
            auto_adjust_dpi: val.auto_adjust_dpi,
            min_dpi: val.min_dpi,
            max_dpi: val.max_dpi,
            max_images_per_page: val.max_images_per_page,
            classify: val.classify,
            include_page_rasters: val.include_page_rasters,
            run_ocr_on_images: val.run_ocr_on_images,
            ocr_text_only: val.ocr_text_only,
            append_ocr_text: val.append_ocr_text,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ImageExtractionConfig> for ImageExtractionConfig {
    fn from(val: kreuzberg::ImageExtractionConfig) -> Self {
        Self {
            extract_images: val.extract_images,
            target_dpi: val.target_dpi,
            max_image_dimension: val.max_image_dimension,
            inject_placeholders: val.inject_placeholders,
            auto_adjust_dpi: val.auto_adjust_dpi,
            min_dpi: val.min_dpi,
            max_dpi: val.max_dpi,
            max_images_per_page: val.max_images_per_page,
            classify: val.classify,
            include_page_rasters: val.include_page_rasters,
            run_ocr_on_images: val.run_ocr_on_images,
            ocr_text_only: val.ocr_text_only,
            append_ocr_text: val.append_ocr_text,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TokenReductionOptions> for kreuzberg::TokenReductionOptions {
    fn from(val: TokenReductionOptions) -> Self {
        Self {
            mode: val.mode,
            preserve_important_words: val.preserve_important_words,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TokenReductionOptions> for TokenReductionOptions {
    fn from(val: kreuzberg::TokenReductionOptions) -> Self {
        Self {
            mode: val.mode,
            preserve_important_words: val.preserve_important_words,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<LanguageDetectionConfig> for kreuzberg::LanguageDetectionConfig {
    fn from(val: LanguageDetectionConfig) -> Self {
        Self {
            enabled: val.enabled,
            min_confidence: val.min_confidence,
            detect_multiple: val.detect_multiple,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LanguageDetectionConfig> for LanguageDetectionConfig {
    fn from(val: kreuzberg::LanguageDetectionConfig) -> Self {
        Self {
            enabled: val.enabled,
            min_confidence: val.min_confidence,
            detect_multiple: val.detect_multiple,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<HtmlOutputConfig> for kreuzberg::HtmlOutputConfig {
    fn from(val: HtmlOutputConfig) -> Self {
        Self {
            css: val.css,
            css_file: val.css_file.map(Into::into),
            theme: val.theme.into(),
            class_prefix: val.class_prefix,
            embed_css: val.embed_css,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::HtmlOutputConfig> for HtmlOutputConfig {
    fn from(val: kreuzberg::HtmlOutputConfig) -> Self {
        Self {
            css: val.css,
            css_file: val.css_file.map(|p| p.to_string_lossy().to_string()),
            theme: val.theme.into(),
            class_prefix: val.class_prefix,
            embed_css: val.embed_css,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<LayoutDetectionConfig> for kreuzberg::LayoutDetectionConfig {
    fn from(val: LayoutDetectionConfig) -> Self {
        Self {
            confidence_threshold: val.confidence_threshold,
            apply_heuristics: val.apply_heuristics,
            table_model: val.table_model.into(),
            acceleration: val.acceleration.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LayoutDetectionConfig> for LayoutDetectionConfig {
    fn from(val: kreuzberg::LayoutDetectionConfig) -> Self {
        Self {
            confidence_threshold: val.confidence_threshold,
            apply_heuristics: val.apply_heuristics,
            table_model: val.table_model.into(),
            acceleration: val.acceleration.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<LlmConfig> for kreuzberg::LlmConfig {
    fn from(val: LlmConfig) -> Self {
        Self {
            model: val.model,
            api_key: val.api_key,
            base_url: val.base_url,
            timeout_secs: val.timeout_secs,
            max_retries: val.max_retries,
            temperature: val.temperature,
            max_tokens: val.max_tokens,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LlmConfig> for LlmConfig {
    fn from(val: kreuzberg::LlmConfig) -> Self {
        Self {
            model: val.model,
            api_key: val.api_key,
            base_url: val.base_url,
            timeout_secs: val.timeout_secs,
            max_retries: val.max_retries,
            temperature: val.temperature,
            max_tokens: val.max_tokens,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<StructuredExtractionConfig> for kreuzberg::StructuredExtractionConfig {
    fn from(val: StructuredExtractionConfig) -> Self {
        Self {
            schema: serde_json::from_str(&val.schema).unwrap_or_default(),
            schema_name: val.schema_name,
            schema_description: val.schema_description,
            strict: val.strict,
            prompt: val.prompt,
            llm: val.llm.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::StructuredExtractionConfig> for StructuredExtractionConfig {
    fn from(val: kreuzberg::StructuredExtractionConfig) -> Self {
        Self {
            schema: val.schema.to_string(),
            schema_name: val.schema_name,
            schema_description: val.schema_description,
            strict: val.strict,
            prompt: val.prompt,
            llm: val.llm.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrQualityThresholds> for kreuzberg::OcrQualityThresholds {
    fn from(val: OcrQualityThresholds) -> Self {
        Self {
            min_total_non_whitespace: val.min_total_non_whitespace,
            min_non_whitespace_per_page: val.min_non_whitespace_per_page,
            min_meaningful_word_len: val.min_meaningful_word_len,
            min_meaningful_words: val.min_meaningful_words,
            min_alnum_ratio: val.min_alnum_ratio,
            min_garbage_chars: val.min_garbage_chars,
            max_fragmented_word_ratio: val.max_fragmented_word_ratio,
            critical_fragmented_word_ratio: val.critical_fragmented_word_ratio,
            min_avg_word_length: val.min_avg_word_length,
            min_words_for_avg_length_check: val.min_words_for_avg_length_check,
            min_consecutive_repeat_ratio: val.min_consecutive_repeat_ratio,
            min_words_for_repeat_check: val.min_words_for_repeat_check,
            substantive_min_chars: val.substantive_min_chars,
            non_text_min_chars: val.non_text_min_chars,
            alnum_ws_ratio_threshold: val.alnum_ws_ratio_threshold,
            pipeline_min_quality: val.pipeline_min_quality,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrQualityThresholds> for OcrQualityThresholds {
    fn from(val: kreuzberg::OcrQualityThresholds) -> Self {
        Self {
            min_total_non_whitespace: val.min_total_non_whitespace,
            min_non_whitespace_per_page: val.min_non_whitespace_per_page,
            min_meaningful_word_len: val.min_meaningful_word_len,
            min_meaningful_words: val.min_meaningful_words,
            min_alnum_ratio: val.min_alnum_ratio,
            min_garbage_chars: val.min_garbage_chars,
            max_fragmented_word_ratio: val.max_fragmented_word_ratio,
            critical_fragmented_word_ratio: val.critical_fragmented_word_ratio,
            min_avg_word_length: val.min_avg_word_length,
            min_words_for_avg_length_check: val.min_words_for_avg_length_check,
            min_consecutive_repeat_ratio: val.min_consecutive_repeat_ratio,
            min_words_for_repeat_check: val.min_words_for_repeat_check,
            substantive_min_chars: val.substantive_min_chars,
            non_text_min_chars: val.non_text_min_chars,
            alnum_ws_ratio_threshold: val.alnum_ws_ratio_threshold,
            pipeline_min_quality: val.pipeline_min_quality,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrPipelineStage> for kreuzberg::OcrPipelineStage {
    fn from(val: OcrPipelineStage) -> Self {
        Self {
            backend: val.backend,
            priority: val.priority,
            language: val.language,
            tesseract_config: val.tesseract_config.map(Into::into),
            paddle_ocr_config: val
                .paddle_ocr_config
                .as_ref()
                .and_then(|s| serde_json::from_str(s).ok()),
            vlm_config: val.vlm_config.map(Into::into),
            backend_options: val.backend_options.as_ref().and_then(|s| serde_json::from_str(s).ok()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrPipelineStage> for OcrPipelineStage {
    fn from(val: kreuzberg::OcrPipelineStage) -> Self {
        Self {
            backend: val.backend,
            priority: val.priority,
            language: val.language,
            tesseract_config: val.tesseract_config.map(Into::into),
            paddle_ocr_config: val.paddle_ocr_config.as_ref().map(ToString::to_string),
            vlm_config: val.vlm_config.map(Into::into),
            backend_options: val.backend_options.as_ref().map(ToString::to_string),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrPipelineConfig> for kreuzberg::OcrPipelineConfig {
    fn from(val: OcrPipelineConfig) -> Self {
        Self {
            stages: val.stages.into_iter().map(Into::into).collect(),
            quality_thresholds: val.quality_thresholds.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrPipelineConfig> for OcrPipelineConfig {
    fn from(val: kreuzberg::OcrPipelineConfig) -> Self {
        Self {
            stages: val.stages.into_iter().map(Into::into).collect(),
            quality_thresholds: val.quality_thresholds.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrConfig> for kreuzberg::OcrConfig {
    fn from(val: OcrConfig) -> Self {
        Self {
            enabled: val.enabled,
            backend: val.backend,
            language: val.language,
            tesseract_config: val.tesseract_config.map(Into::into),
            output_format: val.output_format.map(Into::into),
            paddle_ocr_config: val
                .paddle_ocr_config
                .as_ref()
                .and_then(|s| serde_json::from_str(s).ok()),
            backend_options: val.backend_options.as_ref().and_then(|s| serde_json::from_str(s).ok()),
            element_config: val.element_config.map(Into::into),
            quality_thresholds: val.quality_thresholds.map(Into::into),
            pipeline: val.pipeline.map(Into::into),
            auto_rotate: val.auto_rotate,
            vlm_config: val.vlm_config.map(Into::into),
            vlm_prompt: val.vlm_prompt,
            acceleration: val.acceleration.map(Into::into),
            tessdata_bytes: val
                .tessdata_bytes
                .map(|m| m.into_iter().map(|(k, v)| (k, v.to_vec().into())).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrConfig> for OcrConfig {
    fn from(val: kreuzberg::OcrConfig) -> Self {
        Self {
            enabled: val.enabled,
            backend: val.backend,
            language: val.language,
            tesseract_config: val.tesseract_config.map(Into::into),
            output_format: val.output_format.map(Into::into),
            paddle_ocr_config: val.paddle_ocr_config.as_ref().map(ToString::to_string),
            backend_options: val.backend_options.as_ref().map(ToString::to_string),
            element_config: val.element_config.map(Into::into),
            quality_thresholds: val.quality_thresholds.map(Into::into),
            pipeline: val.pipeline.map(Into::into),
            auto_rotate: val.auto_rotate,
            vlm_config: val.vlm_config.map(Into::into),
            vlm_prompt: val.vlm_prompt,
            acceleration: val.acceleration.map(Into::into),
            tessdata_bytes: val
                .tessdata_bytes
                .map(|m| m.into_iter().map(|(k, v)| (k, v.to_vec().into())).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PageConfig> for kreuzberg::PageConfig {
    fn from(val: PageConfig) -> Self {
        Self {
            extract_pages: val.extract_pages,
            insert_page_markers: val.insert_page_markers,
            marker_format: val.marker_format,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PageConfig> for PageConfig {
    fn from(val: kreuzberg::PageConfig) -> Self {
        Self {
            extract_pages: val.extract_pages,
            insert_page_markers: val.insert_page_markers,
            marker_format: val.marker_format,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PdfConfig> for kreuzberg::PdfConfig {
    fn from(val: PdfConfig) -> Self {
        Self {
            extract_images: val.extract_images,
            extract_tables: val.extract_tables,
            passwords: val.passwords.map(|v| v.into_iter().collect()),
            extract_metadata: val.extract_metadata,
            hierarchy: val.hierarchy.map(Into::into),
            extract_annotations: val.extract_annotations,
            top_margin_fraction: val.top_margin_fraction,
            bottom_margin_fraction: val.bottom_margin_fraction,
            allow_single_column_tables: val.allow_single_column_tables,
            ocr_inline_images: val.ocr_inline_images,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PdfConfig> for PdfConfig {
    fn from(val: kreuzberg::PdfConfig) -> Self {
        Self {
            extract_images: val.extract_images,
            extract_tables: val.extract_tables,
            passwords: val.passwords.map(|v| v.into_iter().collect()),
            extract_metadata: val.extract_metadata,
            hierarchy: val.hierarchy.map(Into::into),
            extract_annotations: val.extract_annotations,
            top_margin_fraction: val.top_margin_fraction,
            bottom_margin_fraction: val.bottom_margin_fraction,
            allow_single_column_tables: val.allow_single_column_tables,
            ocr_inline_images: val.ocr_inline_images,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<HierarchyConfig> for kreuzberg::HierarchyConfig {
    fn from(val: HierarchyConfig) -> Self {
        Self {
            enabled: val.enabled,
            k_clusters: val.k_clusters,
            include_bbox: val.include_bbox,
            ocr_coverage_threshold: val.ocr_coverage_threshold,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::HierarchyConfig> for HierarchyConfig {
    fn from(val: kreuzberg::HierarchyConfig) -> Self {
        Self {
            enabled: val.enabled,
            k_clusters: val.k_clusters,
            include_bbox: val.include_bbox,
            ocr_coverage_threshold: val.ocr_coverage_threshold,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PostProcessorConfig> for kreuzberg::PostProcessorConfig {
    fn from(val: PostProcessorConfig) -> Self {
        Self {
            enabled: val.enabled,
            enabled_processors: val.enabled_processors.map(|v| v.into_iter().collect()),
            disabled_processors: val.disabled_processors.map(|v| v.into_iter().collect()),
            enabled_set: val.enabled_set.map(|v| v.into_iter().collect()),
            disabled_set: val.disabled_set.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PostProcessorConfig> for PostProcessorConfig {
    fn from(val: kreuzberg::PostProcessorConfig) -> Self {
        Self {
            enabled: val.enabled,
            enabled_processors: val.enabled_processors.map(|v| v.into_iter().collect()),
            disabled_processors: val.disabled_processors.map(|v| v.into_iter().collect()),
            enabled_set: val.enabled_set.map(|v| v.into_iter().collect()),
            disabled_set: val.disabled_set.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ChunkingConfig> for kreuzberg::ChunkingConfig {
    fn from(val: ChunkingConfig) -> Self {
        Self {
            max_characters: val.max_characters,
            overlap: val.overlap,
            trim: val.trim,
            chunker_type: val.chunker_type.into(),
            embedding: val.embedding.map(Into::into),
            preset: val.preset,
            sizing: val.sizing.into(),
            prepend_heading_context: val.prepend_heading_context,
            topic_threshold: val.topic_threshold,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ChunkingConfig> for ChunkingConfig {
    fn from(val: kreuzberg::ChunkingConfig) -> Self {
        Self {
            max_characters: val.max_characters,
            overlap: val.overlap,
            trim: val.trim,
            chunker_type: val.chunker_type.into(),
            embedding: val.embedding.map(Into::into),
            preset: val.preset,
            sizing: val.sizing.into(),
            prepend_heading_context: val.prepend_heading_context,
            topic_threshold: val.topic_threshold,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EmbeddingConfig> for kreuzberg::EmbeddingConfig {
    fn from(val: EmbeddingConfig) -> Self {
        Self {
            model: val.model.into(),
            normalize: val.normalize,
            batch_size: val.batch_size,
            show_download_progress: val.show_download_progress,
            cache_dir: val.cache_dir.map(Into::into),
            acceleration: val.acceleration.map(Into::into),
            max_embed_duration_secs: val.max_embed_duration_secs,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmbeddingConfig> for EmbeddingConfig {
    fn from(val: kreuzberg::EmbeddingConfig) -> Self {
        Self {
            model: val.model.into(),
            normalize: val.normalize,
            batch_size: val.batch_size,
            show_download_progress: val.show_download_progress,
            cache_dir: val.cache_dir.map(|p| p.to_string_lossy().to_string()),
            acceleration: val.acceleration.map(Into::into),
            max_embed_duration_secs: val.max_embed_duration_secs,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TreeSitterConfig> for kreuzberg::TreeSitterConfig {
    fn from(val: TreeSitterConfig) -> Self {
        Self {
            enabled: val.enabled,
            cache_dir: val.cache_dir.map(Into::into),
            languages: val.languages.map(|v| v.into_iter().collect()),
            groups: val.groups.map(|v| v.into_iter().collect()),
            process: val.process.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TreeSitterConfig> for TreeSitterConfig {
    fn from(val: kreuzberg::TreeSitterConfig) -> Self {
        Self {
            enabled: val.enabled,
            cache_dir: val.cache_dir.map(|p| p.to_string_lossy().to_string()),
            languages: val.languages.map(|v| v.into_iter().collect()),
            groups: val.groups.map(|v| v.into_iter().collect()),
            process: val.process.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TreeSitterProcessConfig> for kreuzberg::TreeSitterProcessConfig {
    fn from(val: TreeSitterProcessConfig) -> Self {
        Self {
            structure: val.structure,
            imports: val.imports,
            exports: val.exports,
            comments: val.comments,
            docstrings: val.docstrings,
            symbols: val.symbols,
            diagnostics: val.diagnostics,
            chunk_max_size: val.chunk_max_size,
            content_mode: val.content_mode.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TreeSitterProcessConfig> for TreeSitterProcessConfig {
    fn from(val: kreuzberg::TreeSitterProcessConfig) -> Self {
        Self {
            structure: val.structure,
            imports: val.imports,
            exports: val.exports,
            comments: val.comments,
            docstrings: val.docstrings,
            symbols: val.symbols,
            diagnostics: val.diagnostics,
            chunk_max_size: val.chunk_max_size,
            content_mode: val.content_mode.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::SupportedFormat> for SupportedFormat {
    fn from(val: kreuzberg::SupportedFormat) -> Self {
        Self {
            extension: val.extension,
            mime_type: val.mime_type,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ServerConfig> for kreuzberg::ServerConfig {
    fn from(val: ServerConfig) -> Self {
        Self {
            host: val.host,
            port: val.port,
            cors_origins: val.cors_origins.into_iter().collect(),
            max_request_body_bytes: val.max_request_body_bytes,
            max_multipart_field_bytes: val.max_multipart_field_bytes,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ServerConfig> for ServerConfig {
    fn from(val: kreuzberg::ServerConfig) -> Self {
        Self {
            host: val.host,
            port: val.port,
            cors_origins: val.cors_origins.into_iter().collect(),
            max_request_body_bytes: val.max_request_body_bytes,
            max_multipart_field_bytes: val.max_multipart_field_bytes,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::extraction::structured::StructuredDataResult> for StructuredDataResult {
    fn from(val: kreuzberg::extraction::structured::StructuredDataResult) -> Self {
        Self {
            content: val.content,
            format: val.format.to_string(),
            metadata: val.metadata.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            text_fields: val.text_fields.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DocxAppProperties> for kreuzberg::DocxAppProperties {
    fn from(val: DocxAppProperties) -> Self {
        Self {
            application: val.application,
            app_version: val.app_version,
            template: val.template,
            total_time: val.total_time,
            pages: val.pages,
            words: val.words,
            characters: val.characters,
            characters_with_spaces: val.characters_with_spaces,
            lines: val.lines,
            paragraphs: val.paragraphs,
            company: val.company,
            doc_security: val.doc_security,
            scale_crop: val.scale_crop,
            links_up_to_date: val.links_up_to_date,
            shared_doc: val.shared_doc,
            hyperlinks_changed: val.hyperlinks_changed,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DocxAppProperties> for DocxAppProperties {
    fn from(val: kreuzberg::DocxAppProperties) -> Self {
        Self {
            application: val.application,
            app_version: val.app_version,
            template: val.template,
            total_time: val.total_time,
            pages: val.pages,
            words: val.words,
            characters: val.characters,
            characters_with_spaces: val.characters_with_spaces,
            lines: val.lines,
            paragraphs: val.paragraphs,
            company: val.company,
            doc_security: val.doc_security,
            scale_crop: val.scale_crop,
            links_up_to_date: val.links_up_to_date,
            shared_doc: val.shared_doc,
            hyperlinks_changed: val.hyperlinks_changed,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties> for XlsxAppProperties {
    fn from(val: kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties) -> Self {
        Self {
            application: val.application,
            app_version: val.app_version,
            doc_security: val.doc_security,
            scale_crop: val.scale_crop,
            links_up_to_date: val.links_up_to_date,
            shared_doc: val.shared_doc,
            hyperlinks_changed: val.hyperlinks_changed,
            company: val.company,
            worksheet_names: val.worksheet_names.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties> for PptxAppProperties {
    fn from(val: kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties) -> Self {
        Self {
            application: val.application,
            app_version: val.app_version,
            total_time: val.total_time,
            company: val.company,
            doc_security: val.doc_security,
            scale_crop: val.scale_crop,
            links_up_to_date: val.links_up_to_date,
            shared_doc: val.shared_doc,
            hyperlinks_changed: val.hyperlinks_changed,
            slides: val.slides,
            notes: val.notes,
            hidden_slides: val.hidden_slides,
            multimedia_clips: val.multimedia_clips,
            presentation_format: val.presentation_format,
            slide_titles: val.slide_titles.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<CoreProperties> for kreuzberg::CoreProperties {
    fn from(val: CoreProperties) -> Self {
        Self {
            title: val.title,
            subject: val.subject,
            creator: val.creator,
            keywords: val.keywords,
            description: val.description,
            last_modified_by: val.last_modified_by,
            revision: val.revision,
            created: val.created,
            modified: val.modified,
            category: val.category,
            content_status: val.content_status,
            language: val.language,
            identifier: val.identifier,
            version: val.version,
            last_printed: val.last_printed,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::CoreProperties> for CoreProperties {
    fn from(val: kreuzberg::CoreProperties) -> Self {
        Self {
            title: val.title,
            subject: val.subject,
            creator: val.creator,
            keywords: val.keywords,
            description: val.description,
            last_modified_by: val.last_modified_by,
            revision: val.revision,
            created: val.created,
            modified: val.modified,
            category: val.category,
            content_status: val.content_status,
            language: val.language,
            identifier: val.identifier,
            version: val.version,
            last_printed: val.last_printed,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<SecurityLimits> for kreuzberg::SecurityLimits {
    fn from(val: SecurityLimits) -> Self {
        Self {
            max_archive_size: val.max_archive_size,
            max_compression_ratio: val.max_compression_ratio,
            max_files_in_archive: val.max_files_in_archive,
            max_nesting_depth: val.max_nesting_depth,
            max_entity_length: val.max_entity_length,
            max_content_size: val.max_content_size,
            max_iterations: val.max_iterations,
            max_xml_depth: val.max_xml_depth,
            max_table_cells: val.max_table_cells,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::SecurityLimits> for SecurityLimits {
    fn from(val: kreuzberg::SecurityLimits) -> Self {
        Self {
            max_archive_size: val.max_archive_size,
            max_compression_ratio: val.max_compression_ratio,
            max_files_in_archive: val.max_files_in_archive,
            max_nesting_depth: val.max_nesting_depth,
            max_entity_length: val.max_entity_length,
            max_content_size: val.max_content_size,
            max_iterations: val.max_iterations,
            max_xml_depth: val.max_xml_depth,
            max_table_cells: val.max_table_cells,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TokenReductionConfig> for kreuzberg::TokenReductionConfig {
    fn from(val: TokenReductionConfig) -> Self {
        Self {
            level: val.level.into(),
            language_hint: val.language_hint,
            preserve_markdown: val.preserve_markdown,
            preserve_code: val.preserve_code,
            semantic_threshold: val.semantic_threshold,
            enable_parallel: val.enable_parallel,
            use_simd: val.use_simd,
            custom_stopwords: val.custom_stopwords.map(|m| m.into_iter().collect()),
            preserve_patterns: val.preserve_patterns.into_iter().collect(),
            target_reduction: val.target_reduction,
            enable_semantic_clustering: val.enable_semantic_clustering,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TokenReductionConfig> for TokenReductionConfig {
    fn from(val: kreuzberg::TokenReductionConfig) -> Self {
        Self {
            level: val.level.into(),
            language_hint: val.language_hint,
            preserve_markdown: val.preserve_markdown,
            preserve_code: val.preserve_code,
            semantic_threshold: val.semantic_threshold,
            enable_parallel: val.enable_parallel,
            use_simd: val.use_simd,
            custom_stopwords: val.custom_stopwords.map(|m| m.into_iter().collect()),
            preserve_patterns: val.preserve_patterns.into_iter().collect(),
            target_reduction: val.target_reduction,
            enable_semantic_clustering: val.enable_semantic_clustering,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PdfAnnotation> for kreuzberg::PdfAnnotation {
    fn from(val: PdfAnnotation) -> Self {
        Self {
            annotation_type: val.annotation_type.into(),
            content: val.content,
            page_number: val.page_number,
            bounding_box: val.bounding_box.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PdfAnnotation> for PdfAnnotation {
    fn from(val: kreuzberg::PdfAnnotation) -> Self {
        Self {
            annotation_type: val.annotation_type.into(),
            content: val.content,
            page_number: val.page_number,
            bounding_box: val.bounding_box.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DjotContent> for kreuzberg::DjotContent {
    fn from(val: DjotContent) -> Self {
        Self {
            plain_text: val.plain_text,
            blocks: val.blocks.into_iter().map(Into::into).collect(),
            metadata: val.metadata.into(),
            tables: val.tables.into_iter().map(Into::into).collect(),
            images: val.images.into_iter().map(Into::into).collect(),
            links: val.links.into_iter().map(Into::into).collect(),
            footnotes: val.footnotes.into_iter().map(Into::into).collect(),
            attributes: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DjotContent> for DjotContent {
    fn from(val: kreuzberg::DjotContent) -> Self {
        Self {
            plain_text: val.plain_text,
            blocks: val.blocks.into_iter().map(Into::into).collect(),
            metadata: val.metadata.into(),
            tables: val.tables.into_iter().map(Into::into).collect(),
            images: val.images.into_iter().map(Into::into).collect(),
            links: val.links.into_iter().map(Into::into).collect(),
            footnotes: val.footnotes.into_iter().map(Into::into).collect(),
            attributes: val.attributes.iter().map(|i| format!("{:?}", i)).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<FormattedBlock> for kreuzberg::FormattedBlock {
    fn from(val: FormattedBlock) -> Self {
        Self {
            block_type: val.block_type.into(),
            level: val.level,
            inline_content: val.inline_content.into_iter().map(Into::into).collect(),
            attributes: Default::default(),
            language: val.language,
            code: val.code,
            children: val.children.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::FormattedBlock> for FormattedBlock {
    fn from(val: kreuzberg::FormattedBlock) -> Self {
        Self {
            block_type: val.block_type.into(),
            level: val.level,
            inline_content: val.inline_content.into_iter().map(Into::into).collect(),
            attributes: val.attributes.as_ref().map(|v| format!("{v:?}")),
            language: val.language,
            code: val.code,
            children: val.children.into_iter().map(Into::into).collect(),
        }
    }
}

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

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::InlineElement> for InlineElement {
    fn from(val: kreuzberg::InlineElement) -> Self {
        Self {
            element_type: val.element_type.into(),
            content: val.content,
            attributes: val.attributes.as_ref().map(|v| format!("{v:?}")),
            metadata: val
                .metadata
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DjotImage> for kreuzberg::DjotImage {
    fn from(val: DjotImage) -> Self {
        Self {
            src: val.src,
            alt: val.alt,
            title: val.title,
            attributes: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DjotImage> for DjotImage {
    fn from(val: kreuzberg::DjotImage) -> Self {
        Self {
            src: val.src,
            alt: val.alt,
            title: val.title,
            attributes: val.attributes.as_ref().map(|v| format!("{v:?}")),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DjotLink> for kreuzberg::DjotLink {
    fn from(val: DjotLink) -> Self {
        Self {
            url: val.url,
            text: val.text,
            title: val.title,
            attributes: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DjotLink> for DjotLink {
    fn from(val: kreuzberg::DjotLink) -> Self {
        Self {
            url: val.url,
            text: val.text,
            title: val.title,
            attributes: val.attributes.as_ref().map(|v| format!("{v:?}")),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<Footnote> for kreuzberg::Footnote {
    fn from(val: Footnote) -> Self {
        Self {
            label: val.label,
            content: val.content.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::Footnote> for Footnote {
    fn from(val: kreuzberg::Footnote) -> Self {
        Self {
            label: val.label,
            content: val.content.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DocumentStructure> for kreuzberg::DocumentStructure {
    fn from(val: DocumentStructure) -> Self {
        Self {
            nodes: val.nodes.into_iter().map(Into::into).collect(),
            source_format: val.source_format,
            relationships: val.relationships.into_iter().map(Into::into).collect(),
            node_types: val.node_types.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DocumentStructure> for DocumentStructure {
    fn from(val: kreuzberg::DocumentStructure) -> Self {
        Self {
            nodes: val.nodes.into_iter().map(Into::into).collect(),
            source_format: val.source_format,
            relationships: val.relationships.into_iter().map(Into::into).collect(),
            node_types: val.node_types.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DocumentRelationship> for kreuzberg::DocumentRelationship {
    fn from(val: DocumentRelationship) -> Self {
        Self {
            source: kreuzberg::NodeIndex(val.source),
            target: kreuzberg::NodeIndex(val.target),
            kind: val.kind.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DocumentRelationship> for DocumentRelationship {
    fn from(val: kreuzberg::DocumentRelationship) -> Self {
        Self {
            source: val.source.0,
            target: val.target.0,
            kind: val.kind.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DocumentNode> for kreuzberg::DocumentNode {
    fn from(val: DocumentNode) -> Self {
        Self {
            id: Default::default(),
            content: val.content.into(),
            parent: (val.parent).map(kreuzberg::NodeIndex),
            children: (val.children.into_iter().collect::<Vec<_>>())
                .into_iter()
                .map(kreuzberg::NodeIndex)
                .collect(),
            content_layer: val.content_layer.into(),
            page: val.page,
            page_end: val.page_end,
            bbox: val.bbox.map(Into::into),
            annotations: val.annotations.into_iter().map(Into::into).collect(),
            attributes: val
                .attributes
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DocumentNode> for DocumentNode {
    fn from(val: kreuzberg::DocumentNode) -> Self {
        Self {
            id: format!("{:?}", val.id),
            content: val.content.into(),
            parent: val.parent.map(|v| v.0),
            children: val
                .children
                .iter()
                .map(|v| v.0)
                .collect::<Vec<_>>()
                .into_iter()
                .collect(),
            content_layer: val.content_layer.into(),
            page: val.page,
            page_end: val.page_end,
            bbox: val.bbox.map(Into::into),
            annotations: val.annotations.into_iter().map(Into::into).collect(),
            attributes: val
                .attributes
                .map(|m| m.into_iter().map(|(k, v)| (k.into(), v.into())).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TableGrid> for kreuzberg::TableGrid {
    fn from(val: TableGrid) -> Self {
        Self {
            rows: val.rows,
            cols: val.cols,
            cells: val.cells.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TableGrid> for TableGrid {
    fn from(val: kreuzberg::TableGrid) -> Self {
        Self {
            rows: val.rows,
            cols: val.cols,
            cells: val.cells.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<GridCell> for kreuzberg::GridCell {
    fn from(val: GridCell) -> Self {
        Self {
            content: val.content,
            row: val.row,
            col: val.col,
            row_span: val.row_span,
            col_span: val.col_span,
            is_header: val.is_header,
            bbox: val.bbox.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::GridCell> for GridCell {
    fn from(val: kreuzberg::GridCell) -> Self {
        Self {
            content: val.content,
            row: val.row,
            col: val.col,
            row_span: val.row_span,
            col_span: val.col_span,
            is_header: val.is_header,
            bbox: val.bbox.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TextAnnotation> for kreuzberg::TextAnnotation {
    fn from(val: TextAnnotation) -> Self {
        Self {
            start: val.start,
            end: val.end,
            kind: val.kind.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TextAnnotation> for TextAnnotation {
    fn from(val: kreuzberg::TextAnnotation) -> Self {
        Self {
            start: val.start,
            end: val.end,
            kind: val.kind.into(),
        }
    }
}

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

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExtractionResult> for ExtractionResult {
    fn from(val: kreuzberg::ExtractionResult) -> Self {
        Self {
            content: val.content,
            mime_type: val.mime_type.to_string(),
            metadata: val.metadata.into(),
            extraction_method: val.extraction_method.map(Into::into),
            tables: val.tables.into_iter().map(Into::into).collect(),
            detected_languages: val.detected_languages.map(|v| v.into_iter().collect()),
            chunks: val.chunks.map(|v| v.into_iter().map(Into::into).collect()),
            images: val.images.map(|v| v.into_iter().map(Into::into).collect()),
            pages: val.pages.map(|v| v.into_iter().map(Into::into).collect()),
            elements: val.elements.map(|v| v.into_iter().map(Into::into).collect()),
            djot_content: val.djot_content.map(Into::into),
            ocr_elements: val.ocr_elements.map(|v| v.into_iter().map(Into::into).collect()),
            document: val.document.map(Into::into),
            extracted_keywords: val.extracted_keywords.map(|v| v.into_iter().map(Into::into).collect()),
            quality_score: val.quality_score,
            processing_warnings: val.processing_warnings.into_iter().map(Into::into).collect(),
            annotations: val.annotations.map(|v| v.into_iter().map(Into::into).collect()),
            children: val.children.map(|v| v.into_iter().map(Into::into).collect()),
            uris: val.uris.map(|v| v.into_iter().map(Into::into).collect()),
            revisions: val.revisions.map(|v| v.into_iter().map(Into::into).collect()),
            structured_output: val.structured_output.as_ref().map(ToString::to_string),
            code_intelligence: val.code_intelligence.as_ref().map(ToString::to_string),
            llm_usage: val.llm_usage.map(|v| v.into_iter().map(Into::into).collect()),
            formatted_content: val.formatted_content,
            ocr_internal_document: val.ocr_internal_document.as_ref().map(|v| format!("{v:?}")),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ArchiveEntry> for kreuzberg::ArchiveEntry {
    fn from(val: ArchiveEntry) -> Self {
        Self {
            path: val.path,
            mime_type: val.mime_type,
            result: Box::new(val.result.into()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ArchiveEntry> for ArchiveEntry {
    fn from(val: kreuzberg::ArchiveEntry) -> Self {
        Self {
            path: val.path,
            mime_type: val.mime_type,
            result: (*val.result).into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ProcessingWarning> for kreuzberg::ProcessingWarning {
    fn from(val: ProcessingWarning) -> Self {
        Self {
            source: val.source.into(),
            message: val.message.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ProcessingWarning> for ProcessingWarning {
    fn from(val: kreuzberg::ProcessingWarning) -> Self {
        Self {
            source: val.source.to_string(),
            message: val.message.to_string(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<LlmUsage> for kreuzberg::LlmUsage {
    fn from(val: LlmUsage) -> Self {
        Self {
            model: val.model,
            source: val.source,
            input_tokens: val.input_tokens,
            output_tokens: val.output_tokens,
            total_tokens: val.total_tokens,
            estimated_cost: val.estimated_cost,
            finish_reason: val.finish_reason,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LlmUsage> for LlmUsage {
    fn from(val: kreuzberg::LlmUsage) -> Self {
        Self {
            model: val.model,
            source: val.source,
            input_tokens: val.input_tokens,
            output_tokens: val.output_tokens,
            total_tokens: val.total_tokens,
            estimated_cost: val.estimated_cost,
            finish_reason: val.finish_reason,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<Chunk> for kreuzberg::Chunk {
    fn from(val: Chunk) -> Self {
        Self {
            content: val.content,
            chunk_type: val.chunk_type.into(),
            embedding: val.embedding.map(|v| v.into_iter().collect()),
            metadata: val.metadata.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::Chunk> for Chunk {
    fn from(val: kreuzberg::Chunk) -> Self {
        Self {
            content: val.content,
            chunk_type: val.chunk_type.into(),
            embedding: val.embedding.map(|v| v.into_iter().collect()),
            metadata: val.metadata.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<HeadingContext> for kreuzberg::HeadingContext {
    fn from(val: HeadingContext) -> Self {
        Self {
            headings: val.headings.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::HeadingContext> for HeadingContext {
    fn from(val: kreuzberg::HeadingContext) -> Self {
        Self {
            headings: val.headings.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<HeadingLevel> for kreuzberg::HeadingLevel {
    fn from(val: HeadingLevel) -> Self {
        Self {
            level: val.level,
            text: val.text,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::HeadingLevel> for HeadingLevel {
    fn from(val: kreuzberg::HeadingLevel) -> Self {
        Self {
            level: val.level,
            text: val.text,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ChunkMetadata> for kreuzberg::ChunkMetadata {
    fn from(val: ChunkMetadata) -> Self {
        Self {
            byte_start: val.byte_start,
            byte_end: val.byte_end,
            token_count: val.token_count,
            chunk_index: val.chunk_index,
            total_chunks: val.total_chunks,
            first_page: val.first_page,
            last_page: val.last_page,
            heading_context: val.heading_context.map(Into::into),
            image_indices: val.image_indices.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ChunkMetadata> for ChunkMetadata {
    fn from(val: kreuzberg::ChunkMetadata) -> Self {
        Self {
            byte_start: val.byte_start,
            byte_end: val.byte_end,
            token_count: val.token_count,
            chunk_index: val.chunk_index,
            total_chunks: val.total_chunks,
            first_page: val.first_page,
            last_page: val.last_page,
            heading_context: val.heading_context.map(Into::into),
            image_indices: val.image_indices.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ExtractedImage> for kreuzberg::ExtractedImage {
    fn from(val: ExtractedImage) -> Self {
        Self {
            data: val.data.to_vec().into(),
            format: val.format.into(),
            image_index: val.image_index,
            page_number: val.page_number,
            width: val.width,
            height: val.height,
            colorspace: val.colorspace,
            bits_per_component: val.bits_per_component,
            is_mask: val.is_mask,
            description: val.description,
            ocr_result: val.ocr_result.map(Into::into).map(Box::new),
            bounding_box: val.bounding_box.map(Into::into),
            source_path: val.source_path,
            image_kind: val.image_kind.map(Into::into),
            kind_confidence: val.kind_confidence,
            cluster_id: val.cluster_id,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExtractedImage> for ExtractedImage {
    fn from(val: kreuzberg::ExtractedImage) -> Self {
        Self {
            data: val.data.to_vec().into(),
            format: val.format.to_string(),
            image_index: val.image_index,
            page_number: val.page_number,
            width: val.width,
            height: val.height,
            colorspace: val.colorspace,
            bits_per_component: val.bits_per_component,
            is_mask: val.is_mask,
            description: val.description,
            ocr_result: val.ocr_result.map(|v| (*v).into()),
            bounding_box: val.bounding_box.map(Into::into),
            source_path: val.source_path,
            image_kind: val.image_kind.map(Into::into),
            kind_confidence: val.kind_confidence,
            cluster_id: val.cluster_id,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<BoundingBox> for kreuzberg::BoundingBox {
    fn from(val: BoundingBox) -> Self {
        Self {
            x0: val.x0,
            y0: val.y0,
            x1: val.x1,
            y1: val.y1,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::BoundingBox> for BoundingBox {
    fn from(val: kreuzberg::BoundingBox) -> Self {
        Self {
            x0: val.x0,
            y0: val.y0,
            x1: val.x1,
            y1: val.y1,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ElementMetadata> for kreuzberg::ElementMetadata {
    fn from(val: ElementMetadata) -> Self {
        Self {
            page_number: val.page_number,
            filename: val.filename,
            coordinates: val.coordinates.map(Into::into),
            element_index: val.element_index,
            additional: val.additional.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ElementMetadata> for ElementMetadata {
    fn from(val: kreuzberg::ElementMetadata) -> Self {
        Self {
            page_number: val.page_number,
            filename: val.filename,
            coordinates: val.coordinates.map(Into::into),
            element_index: val.element_index,
            additional: val.additional.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<Element> for kreuzberg::Element {
    fn from(val: Element) -> Self {
        Self {
            element_id: Default::default(),
            element_type: val.element_type.into(),
            text: val.text,
            metadata: val.metadata.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::Element> for Element {
    fn from(val: kreuzberg::Element) -> Self {
        Self {
            element_id: format!("{:?}", val.element_id),
            element_type: val.element_type.into(),
            text: val.text,
            metadata: val.metadata.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExcelWorkbook> for ExcelWorkbook {
    fn from(val: kreuzberg::ExcelWorkbook) -> Self {
        Self {
            sheets: val.sheets.into_iter().map(Into::into).collect(),
            metadata: val.metadata.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
            revisions: val.revisions.map(|v| v.into_iter().map(Into::into).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExcelSheet> for ExcelSheet {
    fn from(val: kreuzberg::ExcelSheet) -> Self {
        Self {
            name: val.name,
            markdown: val.markdown,
            row_count: val.row_count,
            col_count: val.col_count,
            cell_count: val.cell_count,
            table_cells: val.table_cells.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::XmlExtractionResult> for XmlExtractionResult {
    fn from(val: kreuzberg::XmlExtractionResult) -> Self {
        Self {
            content: val.content,
            element_count: val.element_count,
            unique_elements: val.unique_elements.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TextExtractionResult> for TextExtractionResult {
    fn from(val: kreuzberg::TextExtractionResult) -> Self {
        Self {
            content: val.content,
            line_count: val.line_count,
            word_count: val.word_count,
            character_count: val.character_count,
            headers: val.headers.map(|v| v.into_iter().collect()),
            links: val.links.as_ref().map(|v| {
                v.iter()
                    .map(|(a, b)| vec![a.to_string(), b.to_string()])
                    .collect::<Vec<Vec<String>>>()
            }),
            code_blocks: val.code_blocks.as_ref().map(|v| {
                v.iter()
                    .map(|(a, b)| vec![a.to_string(), b.to_string()])
                    .collect::<Vec<Vec<String>>>()
            }),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PptxExtractionResult> for PptxExtractionResult {
    fn from(val: kreuzberg::PptxExtractionResult) -> Self {
        Self {
            content: val.content,
            metadata: val.metadata.into(),
            slide_count: val.slide_count,
            image_count: val.image_count,
            table_count: val.table_count,
            images: val.images.into_iter().map(Into::into).collect(),
            page_structure: val.page_structure.map(Into::into),
            page_contents: val.page_contents.map(|v| v.into_iter().map(Into::into).collect()),
            document: val.document.map(Into::into),
            hyperlinks: val.hyperlinks.iter().map(|i| format!("{:?}", i)).collect(),
            office_metadata: val
                .office_metadata
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
            revisions: val.revisions.map(|v| v.into_iter().map(Into::into).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmailExtractionResult> for EmailExtractionResult {
    fn from(val: kreuzberg::EmailExtractionResult) -> Self {
        Self {
            subject: val.subject,
            from_email: val.from_email,
            to_emails: val.to_emails.into_iter().collect(),
            cc_emails: val.cc_emails.into_iter().collect(),
            bcc_emails: val.bcc_emails.into_iter().collect(),
            date: val.date,
            message_id: val.message_id,
            plain_text: val.plain_text,
            html_content: val.html_content,
            content: val.content,
            attachments: val.attachments.into_iter().map(Into::into).collect(),
            metadata: val.metadata.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmailAttachment> for EmailAttachment {
    fn from(val: kreuzberg::EmailAttachment) -> Self {
        Self {
            name: val.name,
            filename: val.filename,
            mime_type: val.mime_type,
            size: val.size,
            is_image: val.is_image,
            data: val.data.map(|v| v.to_vec().into()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrExtractionResult> for kreuzberg::OcrExtractionResult {
    fn from(val: OcrExtractionResult) -> Self {
        Self {
            content: val.content,
            mime_type: val.mime_type,
            metadata: val
                .metadata
                .into_iter()
                .map(|(k, v)| (k.into(), serde_json::from_str(&v).unwrap_or_default()))
                .collect(),
            tables: val.tables.into_iter().map(Into::into).collect(),
            ocr_elements: val.ocr_elements.map(|v| v.into_iter().map(Into::into).collect()),
            internal_document: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrExtractionResult> for OcrExtractionResult {
    fn from(val: kreuzberg::OcrExtractionResult) -> Self {
        Self {
            content: val.content,
            mime_type: val.mime_type,
            metadata: val
                .metadata
                .into_iter()
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
            tables: val.tables.into_iter().map(Into::into).collect(),
            ocr_elements: val.ocr_elements.map(|v| v.into_iter().map(Into::into).collect()),
            internal_document: val.internal_document.as_ref().map(|v| format!("{v:?}")),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrTable> for kreuzberg::OcrTable {
    fn from(val: OcrTable) -> Self {
        Self {
            cells: val.cells.into_iter().collect(),
            markdown: val.markdown,
            page_number: val.page_number,
            bounding_box: val.bounding_box.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrTable> for OcrTable {
    fn from(val: kreuzberg::OcrTable) -> Self {
        Self {
            cells: val.cells.into_iter().collect(),
            markdown: val.markdown,
            page_number: val.page_number,
            bounding_box: val.bounding_box.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrTableBoundingBox> for kreuzberg::OcrTableBoundingBox {
    fn from(val: OcrTableBoundingBox) -> Self {
        Self {
            left: val.left,
            top: val.top,
            right: val.right,
            bottom: val.bottom,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrTableBoundingBox> for OcrTableBoundingBox {
    fn from(val: kreuzberg::OcrTableBoundingBox) -> Self {
        Self {
            left: val.left,
            top: val.top,
            right: val.right,
            bottom: val.bottom,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ImagePreprocessingConfig> for kreuzberg::ImagePreprocessingConfig {
    fn from(val: ImagePreprocessingConfig) -> Self {
        Self {
            target_dpi: val.target_dpi,
            auto_rotate: val.auto_rotate,
            deskew: val.deskew,
            denoise: val.denoise,
            contrast_enhance: val.contrast_enhance,
            binarization_method: val.binarization_method,
            invert_colors: val.invert_colors,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ImagePreprocessingConfig> for ImagePreprocessingConfig {
    fn from(val: kreuzberg::ImagePreprocessingConfig) -> Self {
        Self {
            target_dpi: val.target_dpi,
            auto_rotate: val.auto_rotate,
            deskew: val.deskew,
            denoise: val.denoise,
            contrast_enhance: val.contrast_enhance,
            binarization_method: val.binarization_method,
            invert_colors: val.invert_colors,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TesseractConfig> for kreuzberg::TesseractConfig {
    fn from(val: TesseractConfig) -> Self {
        Self {
            language: val.language,
            psm: val.psm,
            output_format: val.output_format,
            oem: val.oem,
            min_confidence: val.min_confidence,
            preprocessing: val.preprocessing.map(Into::into),
            enable_table_detection: val.enable_table_detection,
            table_min_confidence: val.table_min_confidence,
            table_column_threshold: val.table_column_threshold,
            table_row_threshold_ratio: val.table_row_threshold_ratio,
            use_cache: val.use_cache,
            classify_use_pre_adapted_templates: val.classify_use_pre_adapted_templates,
            language_model_ngram_on: val.language_model_ngram_on,
            tessedit_dont_blkrej_good_wds: val.tessedit_dont_blkrej_good_wds,
            tessedit_dont_rowrej_good_wds: val.tessedit_dont_rowrej_good_wds,
            tessedit_enable_dict_correction: val.tessedit_enable_dict_correction,
            tessedit_char_whitelist: val.tessedit_char_whitelist,
            tessedit_char_blacklist: val.tessedit_char_blacklist,
            tessedit_use_primary_params_model: val.tessedit_use_primary_params_model,
            textord_space_size_is_variable: val.textord_space_size_is_variable,
            thresholding_method: val.thresholding_method,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TesseractConfig> for TesseractConfig {
    fn from(val: kreuzberg::TesseractConfig) -> Self {
        Self {
            language: val.language,
            psm: val.psm,
            output_format: val.output_format,
            oem: val.oem,
            min_confidence: val.min_confidence,
            preprocessing: val.preprocessing.map(Into::into),
            enable_table_detection: val.enable_table_detection,
            table_min_confidence: val.table_min_confidence,
            table_column_threshold: val.table_column_threshold,
            table_row_threshold_ratio: val.table_row_threshold_ratio,
            use_cache: val.use_cache,
            classify_use_pre_adapted_templates: val.classify_use_pre_adapted_templates,
            language_model_ngram_on: val.language_model_ngram_on,
            tessedit_dont_blkrej_good_wds: val.tessedit_dont_blkrej_good_wds,
            tessedit_dont_rowrej_good_wds: val.tessedit_dont_rowrej_good_wds,
            tessedit_enable_dict_correction: val.tessedit_enable_dict_correction,
            tessedit_char_whitelist: val.tessedit_char_whitelist,
            tessedit_char_blacklist: val.tessedit_char_blacklist,
            tessedit_use_primary_params_model: val.tessedit_use_primary_params_model,
            textord_space_size_is_variable: val.textord_space_size_is_variable,
            thresholding_method: val.thresholding_method,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ImagePreprocessingMetadata> for kreuzberg::ImagePreprocessingMetadata {
    fn from(val: ImagePreprocessingMetadata) -> Self {
        Self {
            original_dimensions: Default::default(),
            original_dpi: Default::default(),
            target_dpi: val.target_dpi,
            scale_factor: val.scale_factor,
            auto_adjusted: val.auto_adjusted,
            final_dpi: val.final_dpi,
            new_dimensions: Default::default(),
            resample_method: val.resample_method,
            dimension_clamped: val.dimension_clamped,
            calculated_dpi: val.calculated_dpi,
            skipped_resize: val.skipped_resize,
            resize_error: val.resize_error,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ImagePreprocessingMetadata> for ImagePreprocessingMetadata {
    fn from(val: kreuzberg::ImagePreprocessingMetadata) -> Self {
        Self {
            original_dimensions: vec![val.original_dimensions.0 as _, val.original_dimensions.1 as _],
            original_dpi: vec![val.original_dpi.0 as _, val.original_dpi.1 as _],
            target_dpi: val.target_dpi,
            scale_factor: val.scale_factor,
            auto_adjusted: val.auto_adjusted,
            final_dpi: val.final_dpi,
            new_dimensions: val.new_dimensions.map(|t| {
                let arr: Vec<_> = [t.0, t.1].into_iter().map(|v| v as _).collect();
                arr
            }),
            resample_method: val.resample_method,
            dimension_clamped: val.dimension_clamped,
            calculated_dpi: val.calculated_dpi,
            skipped_resize: val.skipped_resize,
            resize_error: val.resize_error,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<Metadata> for kreuzberg::Metadata {
    fn from(val: Metadata) -> Self {
        Self {
            title: val.title,
            subject: val.subject,
            authors: val.authors.map(|v| v.into_iter().collect()),
            keywords: val.keywords.map(|v| v.into_iter().collect()),
            language: val.language,
            created_at: val.created_at,
            modified_at: val.modified_at,
            created_by: val.created_by,
            modified_by: val.modified_by,
            pages: val.pages.map(Into::into),
            format: val.format.map(Into::into),
            image_preprocessing: val.image_preprocessing.map(Into::into),
            json_schema: val.json_schema.as_ref().and_then(|s| serde_json::from_str(s).ok()),
            error: val.error.map(Into::into),
            extraction_duration_ms: val.extraction_duration_ms,
            category: val.category,
            tags: val.tags.map(|v| v.into_iter().collect()),
            document_version: val.document_version,
            abstract_text: val.abstract_text,
            output_format: val.output_format,
            ocr_used: val.ocr_used,
            additional: val
                .additional
                .into_iter()
                .map(|(k, v)| (k.into(), serde_json::from_str(&v).unwrap_or_default()))
                .collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::Metadata> for Metadata {
    fn from(val: kreuzberg::Metadata) -> Self {
        Self {
            title: val.title,
            subject: val.subject,
            authors: val.authors.map(|v| v.into_iter().collect()),
            keywords: val.keywords.map(|v| v.into_iter().collect()),
            language: val.language,
            created_at: val.created_at,
            modified_at: val.modified_at,
            created_by: val.created_by,
            modified_by: val.modified_by,
            pages: val.pages.map(Into::into),
            format: val.format.map(Into::into),
            image_preprocessing: val.image_preprocessing.map(Into::into),
            json_schema: val.json_schema.as_ref().map(ToString::to_string),
            error: val.error.map(Into::into),
            extraction_duration_ms: val.extraction_duration_ms,
            category: val.category,
            tags: val.tags.map(|v| v.into_iter().collect()),
            document_version: val.document_version,
            abstract_text: val.abstract_text,
            output_format: val.output_format,
            ocr_used: val.ocr_used,
            additional: val
                .additional
                .into_iter()
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ExcelMetadata> for kreuzberg::ExcelMetadata {
    fn from(val: ExcelMetadata) -> Self {
        Self {
            sheet_count: val.sheet_count,
            sheet_names: val.sheet_names.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExcelMetadata> for ExcelMetadata {
    fn from(val: kreuzberg::ExcelMetadata) -> Self {
        Self {
            sheet_count: val.sheet_count,
            sheet_names: val.sheet_names.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EmailMetadata> for kreuzberg::EmailMetadata {
    fn from(val: EmailMetadata) -> Self {
        Self {
            from_email: val.from_email,
            from_name: val.from_name,
            to_emails: val.to_emails.into_iter().collect(),
            cc_emails: val.cc_emails.into_iter().collect(),
            bcc_emails: val.bcc_emails.into_iter().collect(),
            message_id: val.message_id,
            attachments: val.attachments.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmailMetadata> for EmailMetadata {
    fn from(val: kreuzberg::EmailMetadata) -> Self {
        Self {
            from_email: val.from_email,
            from_name: val.from_name,
            to_emails: val.to_emails.into_iter().collect(),
            cc_emails: val.cc_emails.into_iter().collect(),
            bcc_emails: val.bcc_emails.into_iter().collect(),
            message_id: val.message_id,
            attachments: val.attachments.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ArchiveMetadata> for kreuzberg::ArchiveMetadata {
    fn from(val: ArchiveMetadata) -> Self {
        Self {
            format: val.format.into(),
            file_count: val.file_count,
            file_list: val.file_list.into_iter().collect(),
            total_size: val.total_size,
            compressed_size: val.compressed_size,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ArchiveMetadata> for ArchiveMetadata {
    fn from(val: kreuzberg::ArchiveMetadata) -> Self {
        Self {
            format: val.format.to_string(),
            file_count: val.file_count,
            file_list: val.file_list.into_iter().collect(),
            total_size: val.total_size,
            compressed_size: val.compressed_size,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ImageMetadata> for kreuzberg::ImageMetadata {
    fn from(val: ImageMetadata) -> Self {
        Self {
            width: val.width,
            height: val.height,
            format: val.format,
            exif: val.exif.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ImageMetadata> for ImageMetadata {
    fn from(val: kreuzberg::ImageMetadata) -> Self {
        Self {
            width: val.width,
            height: val.height,
            format: val.format,
            exif: val.exif.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<XmlMetadata> for kreuzberg::XmlMetadata {
    fn from(val: XmlMetadata) -> Self {
        Self {
            element_count: val.element_count,
            unique_elements: val.unique_elements.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::XmlMetadata> for XmlMetadata {
    fn from(val: kreuzberg::XmlMetadata) -> Self {
        Self {
            element_count: val.element_count,
            unique_elements: val.unique_elements.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TextMetadata> for kreuzberg::TextMetadata {
    fn from(val: TextMetadata) -> Self {
        Self {
            line_count: val.line_count,
            word_count: val.word_count,
            character_count: val.character_count,
            headers: val.headers.map(|v| v.into_iter().collect()),
            links: Default::default(),
            code_blocks: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TextMetadata> for TextMetadata {
    fn from(val: kreuzberg::TextMetadata) -> Self {
        Self {
            line_count: val.line_count,
            word_count: val.word_count,
            character_count: val.character_count,
            headers: val.headers.map(|v| v.into_iter().collect()),
            links: val.links.as_ref().map(|v| {
                v.iter()
                    .map(|(a, b)| vec![a.to_string(), b.to_string()])
                    .collect::<Vec<Vec<String>>>()
            }),
            code_blocks: val.code_blocks.as_ref().map(|v| {
                v.iter()
                    .map(|(a, b)| vec![a.to_string(), b.to_string()])
                    .collect::<Vec<Vec<String>>>()
            }),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<HeaderMetadata> for kreuzberg::HeaderMetadata {
    fn from(val: HeaderMetadata) -> Self {
        Self {
            level: val.level,
            text: val.text,
            id: val.id,
            depth: val.depth,
            html_offset: val.html_offset,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::HeaderMetadata> for HeaderMetadata {
    fn from(val: kreuzberg::HeaderMetadata) -> Self {
        Self {
            level: val.level,
            text: val.text,
            id: val.id,
            depth: val.depth,
            html_offset: val.html_offset,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<LinkMetadata> for kreuzberg::LinkMetadata {
    fn from(val: LinkMetadata) -> Self {
        Self {
            href: val.href,
            text: val.text,
            title: val.title,
            link_type: val.link_type.into(),
            rel: val.rel.into_iter().collect(),
            attributes: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LinkMetadata> for LinkMetadata {
    fn from(val: kreuzberg::LinkMetadata) -> Self {
        Self {
            href: val.href,
            text: val.text,
            title: val.title,
            link_type: val.link_type.into(),
            rel: val.rel.into_iter().collect(),
            attributes: val
                .attributes
                .iter()
                .map(|(a, b)| vec![a.to_string(), b.to_string()])
                .collect::<Vec<Vec<String>>>(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ImageMetadataType> for kreuzberg::ImageMetadataType {
    fn from(val: ImageMetadataType) -> Self {
        Self {
            src: val.src,
            alt: val.alt,
            title: val.title,
            dimensions: Default::default(),
            image_type: val.image_type.into(),
            attributes: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ImageMetadataType> for ImageMetadataType {
    fn from(val: kreuzberg::ImageMetadataType) -> Self {
        Self {
            src: val.src,
            alt: val.alt,
            title: val.title,
            dimensions: val.dimensions.map(|t| {
                let arr: Vec<_> = [t.0, t.1].into_iter().map(|v| v as _).collect();
                arr
            }),
            image_type: val.image_type.into(),
            attributes: val
                .attributes
                .iter()
                .map(|(a, b)| vec![a.to_string(), b.to_string()])
                .collect::<Vec<Vec<String>>>(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<StructuredData> for kreuzberg::StructuredData {
    fn from(val: StructuredData) -> Self {
        Self {
            data_type: val.data_type.into(),
            raw_json: val.raw_json,
            schema_type: val.schema_type,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::StructuredData> for StructuredData {
    fn from(val: kreuzberg::StructuredData) -> Self {
        Self {
            data_type: val.data_type.into(),
            raw_json: val.raw_json,
            schema_type: val.schema_type,
        }
    }
}

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

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

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrMetadata> for kreuzberg::OcrMetadata {
    fn from(val: OcrMetadata) -> Self {
        Self {
            language: val.language,
            psm: val.psm,
            output_format: val.output_format,
            table_count: val.table_count,
            table_rows: val.table_rows,
            table_cols: val.table_cols,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrMetadata> for OcrMetadata {
    fn from(val: kreuzberg::OcrMetadata) -> Self {
        Self {
            language: val.language,
            psm: val.psm,
            output_format: val.output_format,
            table_count: val.table_count,
            table_rows: val.table_rows,
            table_cols: val.table_cols,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ErrorMetadata> for kreuzberg::ErrorMetadata {
    fn from(val: ErrorMetadata) -> Self {
        Self {
            error_type: val.error_type,
            message: val.message,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ErrorMetadata> for ErrorMetadata {
    fn from(val: kreuzberg::ErrorMetadata) -> Self {
        Self {
            error_type: val.error_type,
            message: val.message,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PptxMetadata> for kreuzberg::PptxMetadata {
    fn from(val: PptxMetadata) -> Self {
        Self {
            slide_count: val.slide_count,
            slide_names: val.slide_names.into_iter().collect(),
            image_count: val.image_count,
            table_count: val.table_count,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PptxMetadata> for PptxMetadata {
    fn from(val: kreuzberg::PptxMetadata) -> Self {
        Self {
            slide_count: val.slide_count,
            slide_names: val.slide_names.into_iter().collect(),
            image_count: val.image_count,
            table_count: val.table_count,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DocxMetadata> for kreuzberg::DocxMetadata {
    fn from(val: DocxMetadata) -> Self {
        Self {
            core_properties: val.core_properties.map(Into::into),
            app_properties: val.app_properties.map(Into::into),
            custom_properties: val.custom_properties.map(|m| {
                m.into_iter()
                    .map(|(k, v)| (k.into(), serde_json::from_str(&v).unwrap_or_default()))
                    .collect()
            }),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DocxMetadata> for DocxMetadata {
    fn from(val: kreuzberg::DocxMetadata) -> Self {
        Self {
            core_properties: val.core_properties.map(Into::into),
            app_properties: val.app_properties.map(Into::into),
            custom_properties: val
                .custom_properties
                .map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<CsvMetadata> for kreuzberg::CsvMetadata {
    fn from(val: CsvMetadata) -> Self {
        Self {
            row_count: val.row_count,
            column_count: val.column_count,
            delimiter: val.delimiter,
            has_header: val.has_header,
            column_types: val.column_types.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::CsvMetadata> for CsvMetadata {
    fn from(val: kreuzberg::CsvMetadata) -> Self {
        Self {
            row_count: val.row_count,
            column_count: val.column_count,
            delimiter: val.delimiter,
            has_header: val.has_header,
            column_types: val.column_types.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<BibtexMetadata> for kreuzberg::BibtexMetadata {
    fn from(val: BibtexMetadata) -> Self {
        Self {
            entry_count: val.entry_count,
            citation_keys: val.citation_keys.into_iter().collect(),
            authors: val.authors.into_iter().collect(),
            year_range: val.year_range.map(Into::into),
            entry_types: val.entry_types.map(|m| m.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::BibtexMetadata> for BibtexMetadata {
    fn from(val: kreuzberg::BibtexMetadata) -> Self {
        Self {
            entry_count: val.entry_count,
            citation_keys: val.citation_keys.into_iter().collect(),
            authors: val.authors.into_iter().collect(),
            year_range: val.year_range.map(Into::into),
            entry_types: val.entry_types.map(|m| m.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<CitationMetadata> for kreuzberg::CitationMetadata {
    fn from(val: CitationMetadata) -> Self {
        Self {
            citation_count: val.citation_count,
            format: val.format,
            authors: val.authors.into_iter().collect(),
            year_range: val.year_range.map(Into::into),
            dois: val.dois.into_iter().collect(),
            keywords: val.keywords.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::CitationMetadata> for CitationMetadata {
    fn from(val: kreuzberg::CitationMetadata) -> Self {
        Self {
            citation_count: val.citation_count,
            format: val.format,
            authors: val.authors.into_iter().collect(),
            year_range: val.year_range.map(Into::into),
            dois: val.dois.into_iter().collect(),
            keywords: val.keywords.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<YearRange> for kreuzberg::YearRange {
    fn from(val: YearRange) -> Self {
        Self {
            min: val.min,
            max: val.max,
            years: val.years.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::YearRange> for YearRange {
    fn from(val: kreuzberg::YearRange) -> Self {
        Self {
            min: val.min,
            max: val.max,
            years: val.years.into_iter().collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<FictionBookMetadata> for kreuzberg::FictionBookMetadata {
    fn from(val: FictionBookMetadata) -> Self {
        Self {
            genres: val.genres.into_iter().collect(),
            sequences: val.sequences.into_iter().collect(),
            annotation: val.annotation,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::FictionBookMetadata> for FictionBookMetadata {
    fn from(val: kreuzberg::FictionBookMetadata) -> Self {
        Self {
            genres: val.genres.into_iter().collect(),
            sequences: val.sequences.into_iter().collect(),
            annotation: val.annotation,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DbfMetadata> for kreuzberg::DbfMetadata {
    fn from(val: DbfMetadata) -> Self {
        Self {
            record_count: val.record_count,
            field_count: val.field_count,
            fields: val.fields.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DbfMetadata> for DbfMetadata {
    fn from(val: kreuzberg::DbfMetadata) -> Self {
        Self {
            record_count: val.record_count,
            field_count: val.field_count,
            fields: val.fields.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DbfFieldInfo> for kreuzberg::DbfFieldInfo {
    fn from(val: DbfFieldInfo) -> Self {
        Self {
            name: val.name,
            field_type: val.field_type,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DbfFieldInfo> for DbfFieldInfo {
    fn from(val: kreuzberg::DbfFieldInfo) -> Self {
        Self {
            name: val.name,
            field_type: val.field_type,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<JatsMetadata> for kreuzberg::JatsMetadata {
    fn from(val: JatsMetadata) -> Self {
        Self {
            copyright: val.copyright,
            license: val.license,
            history_dates: val
                .history_dates
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
            contributor_roles: val.contributor_roles.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::JatsMetadata> for JatsMetadata {
    fn from(val: kreuzberg::JatsMetadata) -> Self {
        Self {
            copyright: val.copyright,
            license: val.license,
            history_dates: val
                .history_dates
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
            contributor_roles: val.contributor_roles.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ContributorRole> for kreuzberg::ContributorRole {
    fn from(val: ContributorRole) -> Self {
        Self {
            name: val.name,
            role: val.role,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ContributorRole> for ContributorRole {
    fn from(val: kreuzberg::ContributorRole) -> Self {
        Self {
            name: val.name,
            role: val.role,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EpubMetadata> for kreuzberg::EpubMetadata {
    fn from(val: EpubMetadata) -> Self {
        Self {
            coverage: val.coverage,
            dc_format: val.dc_format,
            relation: val.relation,
            source: val.source,
            dc_type: val.dc_type,
            cover_image: val.cover_image,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EpubMetadata> for EpubMetadata {
    fn from(val: kreuzberg::EpubMetadata) -> Self {
        Self {
            coverage: val.coverage,
            dc_format: val.dc_format,
            relation: val.relation,
            source: val.source,
            dc_type: val.dc_type,
            cover_image: val.cover_image,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PstMetadata> for kreuzberg::PstMetadata {
    fn from(val: PstMetadata) -> Self {
        Self {
            message_count: val.message_count,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PstMetadata> for PstMetadata {
    fn from(val: kreuzberg::PstMetadata) -> Self {
        Self {
            message_count: val.message_count,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrConfidence> for kreuzberg::OcrConfidence {
    fn from(val: OcrConfidence) -> Self {
        Self {
            detection: val.detection,
            recognition: val.recognition,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrConfidence> for OcrConfidence {
    fn from(val: kreuzberg::OcrConfidence) -> Self {
        Self {
            detection: val.detection,
            recognition: val.recognition,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrRotation> for kreuzberg::OcrRotation {
    fn from(val: OcrRotation) -> Self {
        Self {
            angle_degrees: val.angle_degrees,
            confidence: val.confidence,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrRotation> for OcrRotation {
    fn from(val: kreuzberg::OcrRotation) -> Self {
        Self {
            angle_degrees: val.angle_degrees,
            confidence: val.confidence,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrElement> for kreuzberg::OcrElement {
    fn from(val: OcrElement) -> Self {
        Self {
            text: val.text,
            geometry: val.geometry.into(),
            confidence: val.confidence.into(),
            level: val.level.into(),
            rotation: val.rotation.map(Into::into),
            page_number: val.page_number,
            parent_id: val.parent_id,
            backend_metadata: val
                .backend_metadata
                .into_iter()
                .map(|(k, v)| (k.into(), serde_json::from_str(&v).unwrap_or_default()))
                .collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrElement> for OcrElement {
    fn from(val: kreuzberg::OcrElement) -> Self {
        Self {
            text: val.text,
            geometry: val.geometry.into(),
            confidence: val.confidence.into(),
            level: val.level.into(),
            rotation: val.rotation.map(Into::into),
            page_number: val.page_number,
            parent_id: val.parent_id,
            backend_metadata: val
                .backend_metadata
                .into_iter()
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<OcrElementConfig> for kreuzberg::OcrElementConfig {
    fn from(val: OcrElementConfig) -> Self {
        Self {
            include_elements: val.include_elements,
            min_level: val.min_level.into(),
            min_confidence: val.min_confidence,
            build_hierarchy: val.build_hierarchy,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OcrElementConfig> for OcrElementConfig {
    fn from(val: kreuzberg::OcrElementConfig) -> Self {
        Self {
            include_elements: val.include_elements,
            min_level: val.min_level.into(),
            min_confidence: val.min_confidence,
            build_hierarchy: val.build_hierarchy,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PageStructure> for kreuzberg::PageStructure {
    fn from(val: PageStructure) -> Self {
        Self {
            total_count: val.total_count,
            unit_type: val.unit_type.into(),
            boundaries: val.boundaries.map(|v| v.into_iter().map(Into::into).collect()),
            pages: val.pages.map(|v| v.into_iter().map(Into::into).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PageStructure> for PageStructure {
    fn from(val: kreuzberg::PageStructure) -> Self {
        Self {
            total_count: val.total_count,
            unit_type: val.unit_type.into(),
            boundaries: val.boundaries.map(|v| v.into_iter().map(Into::into).collect()),
            pages: val.pages.map(|v| v.into_iter().map(Into::into).collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PageBoundary> for kreuzberg::PageBoundary {
    fn from(val: PageBoundary) -> Self {
        Self {
            byte_start: val.byte_start,
            byte_end: val.byte_end,
            page_number: val.page_number,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PageBoundary> for PageBoundary {
    fn from(val: kreuzberg::PageBoundary) -> Self {
        Self {
            byte_start: val.byte_start,
            byte_end: val.byte_end,
            page_number: val.page_number,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PageInfo> for kreuzberg::PageInfo {
    fn from(val: PageInfo) -> Self {
        Self {
            number: val.number,
            title: val.title,
            dimensions: Default::default(),
            image_count: val.image_count,
            table_count: val.table_count,
            hidden: val.hidden,
            is_blank: val.is_blank,
            has_vector_graphics: val.has_vector_graphics,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PageInfo> for PageInfo {
    fn from(val: kreuzberg::PageInfo) -> Self {
        Self {
            number: val.number,
            title: val.title,
            dimensions: val.dimensions.map(|t| {
                let arr: Vec<_> = [t.0, t.1].into_iter().map(|v| v as _).collect();
                arr
            }),
            image_count: val.image_count,
            table_count: val.table_count,
            hidden: val.hidden,
            is_blank: val.is_blank,
            has_vector_graphics: val.has_vector_graphics,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PageContent> for kreuzberg::PageContent {
    fn from(val: PageContent) -> Self {
        Self {
            page_number: val.page_number,
            content: val.content,
            tables: val.tables.into_iter().map(|v| std::sync::Arc::new(v.into())).collect(),
            image_indices: val.image_indices.into_iter().collect(),
            hierarchy: val.hierarchy.map(Into::into),
            is_blank: val.is_blank,
            layout_regions: val.layout_regions.map(|v| v.into_iter().map(Into::into).collect()),
            speaker_notes: val.speaker_notes,
            section_name: val.section_name,
            sheet_name: val.sheet_name,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PageContent> for PageContent {
    fn from(val: kreuzberg::PageContent) -> Self {
        Self {
            page_number: val.page_number,
            content: val.content,
            tables: val.tables.into_iter().map(|v| (*v).clone().into()).collect(),
            image_indices: val.image_indices.into_iter().collect(),
            hierarchy: val.hierarchy.map(Into::into),
            is_blank: val.is_blank,
            layout_regions: val.layout_regions.map(|v| v.into_iter().map(Into::into).collect()),
            speaker_notes: val.speaker_notes,
            section_name: val.section_name,
            sheet_name: val.sheet_name,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<LayoutRegion> for kreuzberg::LayoutRegion {
    fn from(val: LayoutRegion) -> Self {
        Self {
            class_name: val.class_name,
            confidence: val.confidence,
            bounding_box: val.bounding_box.into(),
            area_fraction: val.area_fraction,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LayoutRegion> for LayoutRegion {
    fn from(val: kreuzberg::LayoutRegion) -> Self {
        Self {
            class_name: val.class_name,
            confidence: val.confidence,
            bounding_box: val.bounding_box.into(),
            area_fraction: val.area_fraction,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PageHierarchy> for kreuzberg::PageHierarchy {
    fn from(val: PageHierarchy) -> Self {
        Self {
            block_count: val.block_count,
            blocks: val.blocks.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PageHierarchy> for PageHierarchy {
    fn from(val: kreuzberg::PageHierarchy) -> Self {
        Self {
            block_count: val.block_count,
            blocks: val.blocks.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<HierarchicalBlock> for kreuzberg::HierarchicalBlock {
    fn from(val: HierarchicalBlock) -> Self {
        Self {
            text: val.text,
            font_size: val.font_size,
            level: val.level,
            bbox: Default::default(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::HierarchicalBlock> for HierarchicalBlock {
    fn from(val: kreuzberg::HierarchicalBlock) -> Self {
        Self {
            text: val.text,
            font_size: val.font_size,
            level: val.level,
            bbox: val.bbox.map(|t| {
                let arr: Vec<_> = [t.0, t.1].into_iter().map(|v| v as _).collect();
                arr
            }),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<CellChange> for kreuzberg::CellChange {
    fn from(val: CellChange) -> Self {
        Self {
            row: val.row,
            col: val.col,
            from: val.from,
            to: val.to,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::CellChange> for CellChange {
    fn from(val: kreuzberg::CellChange) -> Self {
        Self {
            row: val.row,
            col: val.col,
            from: val.from,
            to: val.to,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DocumentRevision> for kreuzberg::DocumentRevision {
    fn from(val: DocumentRevision) -> Self {
        Self {
            revision_id: val.revision_id,
            author: val.author,
            timestamp: val.timestamp,
            kind: val.kind.into(),
            anchor: val.anchor.map(Into::into),
            delta: val.delta.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DocumentRevision> for DocumentRevision {
    fn from(val: kreuzberg::DocumentRevision) -> Self {
        Self {
            revision_id: val.revision_id,
            author: val.author,
            timestamp: val.timestamp,
            kind: val.kind.into(),
            anchor: val.anchor.map(Into::into),
            delta: val.delta.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<RevisionDelta> for kreuzberg::RevisionDelta {
    fn from(val: RevisionDelta) -> Self {
        Self {
            content: val.content.into_iter().map(Into::into).collect(),
            table_changes: val.table_changes.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::RevisionDelta> for RevisionDelta {
    fn from(val: kreuzberg::RevisionDelta) -> Self {
        Self {
            content: val.content.into_iter().map(Into::into).collect(),
            table_changes: val.table_changes.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<Table> for kreuzberg::Table {
    fn from(val: Table) -> Self {
        Self {
            cells: val.cells.into_iter().collect(),
            markdown: val.markdown,
            page_number: val.page_number,
            bounding_box: val.bounding_box.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::Table> for Table {
    fn from(val: kreuzberg::Table) -> Self {
        Self {
            cells: val.cells.into_iter().collect(),
            markdown: val.markdown,
            page_number: val.page_number,
            bounding_box: val.bounding_box.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TableCell> for TableCell {
    fn from(val: kreuzberg::TableCell) -> Self {
        Self {
            content: val.content,
            row_span: val.row_span,
            col_span: val.col_span,
            is_header: val.is_header,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ExtractedUri> for kreuzberg::ExtractedUri {
    fn from(val: ExtractedUri) -> Self {
        Self {
            url: val.url,
            label: val.label,
            page: val.page,
            kind: val.kind.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExtractedUri> for ExtractedUri {
    fn from(val: kreuzberg::ExtractedUri) -> Self {
        Self {
            url: val.url,
            label: val.label,
            page: val.page,
            kind: val.kind.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::api::DetectResponse> for DetectResponse {
    fn from(val: kreuzberg::api::DetectResponse) -> Self {
        Self {
            mime_type: val.mime_type,
            filename: val.filename,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DiffOptions> for kreuzberg::DiffOptions {
    fn from(val: DiffOptions) -> Self {
        Self {
            include_metadata: val.include_metadata,
            include_embedded: val.include_embedded,
            max_content_chars: val.max_content_chars,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DiffOptions> for DiffOptions {
    fn from(val: kreuzberg::DiffOptions) -> Self {
        Self {
            include_metadata: val.include_metadata,
            include_embedded: val.include_embedded,
            max_content_chars: val.max_content_chars,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<ExtractionDiff> for kreuzberg::ExtractionDiff {
    fn from(val: ExtractionDiff) -> Self {
        Self {
            content_diff: val.content_diff.into_iter().map(Into::into).collect(),
            tables_added: val.tables_added.into_iter().map(Into::into).collect(),
            tables_removed: val.tables_removed.into_iter().map(Into::into).collect(),
            tables_changed: val.tables_changed.into_iter().map(Into::into).collect(),
            metadata_changed: serde_json::from_str(&val.metadata_changed).unwrap_or_default(),
            embedded_changes: val.embedded_changes.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ExtractionDiff> for ExtractionDiff {
    fn from(val: kreuzberg::ExtractionDiff) -> Self {
        Self {
            content_diff: val.content_diff.into_iter().map(Into::into).collect(),
            tables_added: val.tables_added.into_iter().map(Into::into).collect(),
            tables_removed: val.tables_removed.into_iter().map(Into::into).collect(),
            tables_changed: val.tables_changed.into_iter().map(Into::into).collect(),
            metadata_changed: val.metadata_changed.to_string(),
            embedded_changes: val.embedded_changes.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<DiffHunk> for kreuzberg::DiffHunk {
    fn from(val: DiffHunk) -> Self {
        Self {
            from_line: val.from_line,
            from_count: val.from_count,
            to_line: val.to_line,
            to_count: val.to_count,
            lines: val.lines.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DiffHunk> for DiffHunk {
    fn from(val: kreuzberg::DiffHunk) -> Self {
        Self {
            from_line: val.from_line,
            from_count: val.from_count,
            to_line: val.to_line,
            to_count: val.to_count,
            lines: val.lines.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<TableDiff> for kreuzberg::TableDiff {
    fn from(val: TableDiff) -> Self {
        Self {
            from_index: val.from_index,
            to_index: val.to_index,
            cell_changes: val.cell_changes.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::TableDiff> for TableDiff {
    fn from(val: kreuzberg::TableDiff) -> Self {
        Self {
            from_index: val.from_index,
            to_index: val.to_index,
            cell_changes: val.cell_changes.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EmbeddedChanges> for kreuzberg::EmbeddedChanges {
    fn from(val: EmbeddedChanges) -> Self {
        Self {
            added: val.added.into_iter().map(Into::into).collect(),
            removed: val.removed.into_iter().map(Into::into).collect(),
            changed: val.changed.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmbeddedChanges> for EmbeddedChanges {
    fn from(val: kreuzberg::EmbeddedChanges) -> Self {
        Self {
            added: val.added.into_iter().map(Into::into).collect(),
            removed: val.removed.into_iter().map(Into::into).collect(),
            changed: val.changed.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EmbeddedDiff> for kreuzberg::EmbeddedDiff {
    fn from(val: EmbeddedDiff) -> Self {
        Self {
            path: val.path,
            diff: Box::new(val.diff.into()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmbeddedDiff> for EmbeddedDiff {
    fn from(val: kreuzberg::EmbeddedDiff) -> Self {
        Self {
            path: val.path,
            diff: (*val.diff).into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<EmbeddingPreset> for kreuzberg::EmbeddingPreset {
    fn from(val: EmbeddingPreset) -> Self {
        Self {
            name: val.name,
            chunk_size: val.chunk_size,
            overlap: val.overlap,
            model_repo: val.model_repo,
            pooling: val.pooling,
            model_file: val.model_file,
            dimensions: val.dimensions,
            description: val.description,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::EmbeddingPreset> for EmbeddingPreset {
    fn from(val: kreuzberg::EmbeddingPreset) -> Self {
        Self {
            name: val.name,
            chunk_size: val.chunk_size,
            overlap: val.overlap,
            model_repo: val.model_repo,
            pooling: val.pooling,
            model_file: val.model_file,
            dimensions: val.dimensions,
            description: val.description,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<YakeParams> for kreuzberg::YakeParams {
    fn from(val: YakeParams) -> Self {
        Self {
            window_size: val.window_size,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::YakeParams> for YakeParams {
    fn from(val: kreuzberg::YakeParams) -> Self {
        Self {
            window_size: val.window_size,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<RakeParams> for kreuzberg::RakeParams {
    fn from(val: RakeParams) -> Self {
        Self {
            min_word_length: val.min_word_length,
            max_words_per_phrase: val.max_words_per_phrase,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::RakeParams> for RakeParams {
    fn from(val: kreuzberg::RakeParams) -> Self {
        Self {
            min_word_length: val.min_word_length,
            max_words_per_phrase: val.max_words_per_phrase,
        }
    }
}

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

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::KeywordConfig> for KeywordConfig {
    fn from(val: kreuzberg::KeywordConfig) -> Self {
        Self {
            algorithm: val.algorithm.into(),
            max_keywords: val.max_keywords,
            min_score: val.min_score,
            ngram_range: vec![val.ngram_range.0 as _, val.ngram_range.1 as _],
            language: val.language,
            yake_params: val.yake_params.map(Into::into),
            rake_params: val.rake_params.map(Into::into),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<Keyword> for kreuzberg::Keyword {
    fn from(val: Keyword) -> Self {
        Self {
            text: val.text,
            score: val.score,
            algorithm: val.algorithm.into(),
            positions: val.positions.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::Keyword> for Keyword {
    fn from(val: kreuzberg::Keyword) -> Self {
        Self {
            text: val.text,
            score: val.score,
            algorithm: val.algorithm.into(),
            positions: val.positions.map(|v| v.into_iter().collect()),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PaddleOcrConfig> for kreuzberg::PaddleOcrConfig {
    fn from(val: PaddleOcrConfig) -> Self {
        Self {
            language: val.language,
            cache_dir: val.cache_dir.map(Into::into),
            use_angle_cls: val.use_angle_cls,
            enable_table_detection: val.enable_table_detection,
            det_db_thresh: val.det_db_thresh,
            det_db_box_thresh: val.det_db_box_thresh,
            det_db_unclip_ratio: val.det_db_unclip_ratio,
            det_limit_side_len: val.det_limit_side_len,
            rec_batch_num: val.rec_batch_num,
            padding: val.padding,
            drop_score: val.drop_score,
            model_tier: val.model_tier,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::PaddleOcrConfig> for PaddleOcrConfig {
    fn from(val: kreuzberg::PaddleOcrConfig) -> Self {
        Self {
            language: val.language,
            cache_dir: val.cache_dir.map(|p| p.to_string_lossy().to_string()),
            use_angle_cls: val.use_angle_cls,
            enable_table_detection: val.enable_table_detection,
            det_db_thresh: val.det_db_thresh,
            det_db_box_thresh: val.det_db_box_thresh,
            det_db_unclip_ratio: val.det_db_unclip_ratio,
            det_limit_side_len: val.det_limit_side_len,
            rec_batch_num: val.rec_batch_num,
            padding: val.padding,
            drop_score: val.drop_score,
            model_tier: val.model_tier,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::ModelPaths> for ModelPaths {
    fn from(val: kreuzberg::ModelPaths) -> Self {
        Self {
            det_model: val.det_model.to_string_lossy().to_string(),
            cls_model: val.cls_model.to_string_lossy().to_string(),
            rec_model: val.rec_model.to_string_lossy().to_string(),
            dict_file: val.dict_file.to_string_lossy().to_string(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::OrientationResult> for OrientationResult {
    fn from(val: kreuzberg::OrientationResult) -> Self {
        Self {
            degrees: val.degrees,
            confidence: val.confidence,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::BBox> for BBox {
    fn from(val: kreuzberg::BBox) -> Self {
        Self {
            x1: val.x1,
            y1: val.y1,
            x2: val.x2,
            y2: val.y2,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::LayoutDetection> for LayoutDetection {
    fn from(val: kreuzberg::LayoutDetection) -> Self {
        Self {
            class_name: val.class_name.into(),
            confidence: val.confidence,
            bbox: val.bbox.into(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::RecognizedTable> for RecognizedTable {
    fn from(val: kreuzberg::RecognizedTable) -> Self {
        Self {
            detection_bbox: val.detection_bbox.into(),
            cells: val.cells.into_iter().collect(),
            markdown: val.markdown,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::DetectionResult> for DetectionResult {
    fn from(val: kreuzberg::DetectionResult) -> Self {
        Self {
            page_width: val.page_width,
            page_height: val.page_height,
            detections: val.detections.into_iter().map(Into::into).collect(),
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::pdf::embedded_files::EmbeddedFile> for EmbeddedFile {
    fn from(val: kreuzberg::pdf::embedded_files::EmbeddedFile) -> Self {
        Self {
            name: val.name,
            data: val.data.to_vec().into(),
            compressed_size: val.compressed_size,
            mime_type: val.mime_type,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<PdfMetadata> for kreuzberg::pdf::metadata::PdfMetadata {
    fn from(val: PdfMetadata) -> Self {
        Self {
            pdf_version: val.pdf_version,
            producer: val.producer,
            is_encrypted: val.is_encrypted,
            width: val.width,
            height: val.height,
            page_count: val.page_count,
        }
    }
}

#[allow(clippy::redundant_closure, clippy::useless_conversion)]
impl From<kreuzberg::pdf::metadata::PdfMetadata> for PdfMetadata {
    fn from(val: kreuzberg::pdf::metadata::PdfMetadata) -> Self {
        Self {
            pdf_version: val.pdf_version,
            producer: val.producer,
            is_encrypted: val.is_encrypted,
            width: val.width,
            height: val.height,
            page_count: val.page_count,
        }
    }
}

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

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

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

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

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

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

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

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

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

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

impl From<ChunkSizing> for kreuzberg::ChunkSizing {
    fn from(val: ChunkSizing) -> Self {
        match val {
            ChunkSizing::Characters => Self::Characters,
            ChunkSizing::Tokenizer { model, cache_dir } => Self::Tokenizer {
                model,
                cache_dir: cache_dir.map(Into::into),
            },
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

impl From<NodeContent> for kreuzberg::NodeContent {
    fn from(val: NodeContent) -> Self {
        match val {
            NodeContent::Title { text } => Self::Title { text },
            NodeContent::Heading { level, text } => Self::Heading { level, text },
            NodeContent::Paragraph { text } => Self::Paragraph { text },
            NodeContent::List { ordered } => Self::List { ordered },
            NodeContent::ListItem { text } => Self::ListItem { text },
            NodeContent::Table { grid } => Self::Table { grid: grid.into() },
            NodeContent::Image {
                description,
                image_index,
                src,
            } => Self::Image {
                description,
                image_index,
                src,
            },
            NodeContent::Code { text, language } => Self::Code { text, language },
            NodeContent::Quote => Self::Quote,
            NodeContent::Formula { text } => Self::Formula { text },
            NodeContent::Footnote { text } => Self::Footnote { text },
            NodeContent::Group {
                label,
                heading_level,
                heading_text,
            } => Self::Group {
                label,
                heading_level,
                heading_text,
            },
            NodeContent::PageBreak => Self::PageBreak,
            NodeContent::Slide { number, title } => Self::Slide { number, title },
            NodeContent::DefinitionList => Self::DefinitionList,
            NodeContent::DefinitionItem { term, definition } => Self::DefinitionItem { term, definition },
            NodeContent::Citation { key, text } => Self::Citation { key, text },
            NodeContent::Admonition { kind, title } => Self::Admonition { kind, title },
            NodeContent::RawBlock { format, content } => Self::RawBlock { format, content },
            NodeContent::MetadataBlock { entries } => Self::MetadataBlock {
                entries: entries
                    .iter()
                    .filter_map(|inner| {
                        let mut it = inner.iter().cloned();
                        Some((it.next()?, it.next()?))
                    })
                    .collect(),
            },
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

impl From<kreuzberg::FormatMetadata> for FormatMetadata {
    fn from(val: kreuzberg::FormatMetadata) -> Self {
        match val {
            kreuzberg::FormatMetadata::Pdf(_0) => Self::Pdf { _0: _0.into() },
            kreuzberg::FormatMetadata::Docx(_0) => Self::Docx { _0: (*_0).into() },
            kreuzberg::FormatMetadata::Excel(_0) => Self::Excel { _0: _0.into() },
            kreuzberg::FormatMetadata::Email(_0) => Self::Email { _0: _0.into() },
            kreuzberg::FormatMetadata::Pptx(_0) => Self::Pptx { _0: _0.into() },
            kreuzberg::FormatMetadata::Archive(_0) => Self::Archive { _0: _0.into() },
            kreuzberg::FormatMetadata::Image(_0) => Self::Image { _0: _0.into() },
            kreuzberg::FormatMetadata::Xml(_0) => Self::Xml { _0: _0.into() },
            kreuzberg::FormatMetadata::Text(_0) => Self::Text { _0: _0.into() },
            kreuzberg::FormatMetadata::Html(_0) => Self::Html { _0: (*_0).into() },
            kreuzberg::FormatMetadata::Ocr(_0) => Self::Ocr { _0: _0.into() },
            kreuzberg::FormatMetadata::Csv(_0) => Self::Csv { _0: _0.into() },
            kreuzberg::FormatMetadata::Bibtex(_0) => Self::Bibtex { _0: _0.into() },
            kreuzberg::FormatMetadata::Citation(_0) => Self::Citation { _0: _0.into() },
            kreuzberg::FormatMetadata::FictionBook(_0) => Self::FictionBook { _0: _0.into() },
            kreuzberg::FormatMetadata::Dbf(_0) => Self::Dbf { _0: _0.into() },
            kreuzberg::FormatMetadata::Jats(_0) => Self::Jats { _0: _0.into() },
            kreuzberg::FormatMetadata::Epub(_0) => Self::Epub { _0: _0.into() },
            kreuzberg::FormatMetadata::Pst(_0) => Self::Pst { _0: _0.into() },
            kreuzberg::FormatMetadata::Code(_0) => Self::Code {
                _0: format!("{:?}", _0),
            },
        }
    }
}

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

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

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

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

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

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

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

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

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

impl From<kreuzberg::OcrBoundingGeometry> for OcrBoundingGeometry {
    fn from(val: kreuzberg::OcrBoundingGeometry) -> Self {
        match val {
            kreuzberg::OcrBoundingGeometry::Rectangle {
                left,
                top,
                width,
                height,
            } => Self::Rectangle {
                left,
                top,
                width,
                height,
            },
            kreuzberg::OcrBoundingGeometry::Quadrilateral { points } => Self::Quadrilateral {
                points: format!("{:?}", points),
            },
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/// Convert a `kreuzberg::error::KreuzbergError` error to a Magnus runtime error.
#[allow(dead_code)]
fn kreuzberg_error_to_magnus_err(e: kreuzberg::error::KreuzbergError) -> magnus::Error {
    let msg = e.to_string();
    magnus::Error::new(unsafe { magnus::Ruby::get_unchecked() }.exception_runtime_error(), msg)
}

#[magnus::init]
fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
    let module = ruby.define_module("Kreuzberg")?;

    // Ensure JSON library is loaded for Hash#to_json
    let _ = ruby.eval::<magnus::Value>("require \"json\"");

    let class = module.define_class("CacheStats", ruby.class_object())?;

    class.define_singleton_method("new", function!(CacheStats::new, -1))?;

    class.define_method("total_files", method!(CacheStats::total_files, 0))?;

    class.define_method("total_size_mb", method!(CacheStats::total_size_mb, 0))?;

    class.define_method("available_space_mb", method!(CacheStats::available_space_mb, 0))?;

    class.define_method("oldest_file_age_days", method!(CacheStats::oldest_file_age_days, 0))?;

    class.define_method("newest_file_age_days", method!(CacheStats::newest_file_age_days, 0))?;

    let class = module.define_class("AccelerationConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(AccelerationConfig::new, -1))?;

    class.define_method("provider", method!(AccelerationConfig::provider, 0))?;

    class.define_method("device_id", method!(AccelerationConfig::device_id, 0))?;

    let class = module.define_class("ContentFilterConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(ContentFilterConfig::new, -1))?;

    class.define_method("include_headers", method!(ContentFilterConfig::include_headers, 0))?;

    class.define_method("include_footers", method!(ContentFilterConfig::include_footers, 0))?;

    class.define_method(
        "strip_repeating_text",
        method!(ContentFilterConfig::strip_repeating_text, 0),
    )?;

    class.define_method(
        "include_watermarks",
        method!(ContentFilterConfig::include_watermarks, 0),
    )?;

    let class = module.define_class("EmailConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmailConfig::new, -1))?;

    class.define_method("msg_fallback_codepage", method!(EmailConfig::msg_fallback_codepage, 0))?;

    let class = module.define_class("ExtractionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExtractionConfig::new, -1))?;

    class.define_method("use_cache", method!(ExtractionConfig::use_cache, 0))?;

    class.define_method(
        "enable_quality_processing",
        method!(ExtractionConfig::enable_quality_processing, 0),
    )?;

    class.define_method("ocr", method!(ExtractionConfig::ocr, 0))?;

    class.define_method("force_ocr", method!(ExtractionConfig::force_ocr, 0))?;

    class.define_method("force_ocr_pages", method!(ExtractionConfig::force_ocr_pages, 0))?;

    class.define_method("disable_ocr", method!(ExtractionConfig::disable_ocr, 0))?;

    class.define_method("chunking", method!(ExtractionConfig::chunking, 0))?;

    class.define_method("content_filter", method!(ExtractionConfig::content_filter, 0))?;

    class.define_method("images", method!(ExtractionConfig::images, 0))?;

    class.define_method("pdf_options", method!(ExtractionConfig::pdf_options, 0))?;

    class.define_method("token_reduction", method!(ExtractionConfig::token_reduction, 0))?;

    class.define_method("language_detection", method!(ExtractionConfig::language_detection, 0))?;

    class.define_method("pages", method!(ExtractionConfig::pages, 0))?;

    class.define_method("keywords", method!(ExtractionConfig::keywords, 0))?;

    class.define_method("postprocessor", method!(ExtractionConfig::postprocessor, 0))?;

    class.define_method("html_options", method!(ExtractionConfig::html_options, 0))?;

    class.define_method("html_output", method!(ExtractionConfig::html_output, 0))?;

    class.define_method(
        "extraction_timeout_secs",
        method!(ExtractionConfig::extraction_timeout_secs, 0),
    )?;

    class.define_method(
        "max_concurrent_extractions",
        method!(ExtractionConfig::max_concurrent_extractions, 0),
    )?;

    class.define_method("result_format", method!(ExtractionConfig::result_format, 0))?;

    class.define_method("security_limits", method!(ExtractionConfig::security_limits, 0))?;

    class.define_method(
        "max_embedded_file_bytes",
        method!(ExtractionConfig::max_embedded_file_bytes, 0),
    )?;

    class.define_method("output_format", method!(ExtractionConfig::output_format, 0))?;

    class.define_method("layout", method!(ExtractionConfig::layout, 0))?;

    class.define_method(
        "use_layout_for_markdown",
        method!(ExtractionConfig::use_layout_for_markdown, 0),
    )?;

    class.define_method(
        "include_document_structure",
        method!(ExtractionConfig::include_document_structure, 0),
    )?;

    class.define_method("acceleration", method!(ExtractionConfig::acceleration, 0))?;

    class.define_method("cache_namespace", method!(ExtractionConfig::cache_namespace, 0))?;

    class.define_method("cache_ttl_secs", method!(ExtractionConfig::cache_ttl_secs, 0))?;

    class.define_method("email", method!(ExtractionConfig::email, 0))?;

    class.define_method("concurrency", method!(ExtractionConfig::concurrency, 0))?;

    class.define_method("max_archive_depth", method!(ExtractionConfig::max_archive_depth, 0))?;

    class.define_method("tree_sitter", method!(ExtractionConfig::tree_sitter, 0))?;

    class.define_method(
        "structured_extraction",
        method!(ExtractionConfig::structured_extraction, 0),
    )?;

    class.define_method("cancel_token", method!(ExtractionConfig::cancel_token, 0))?;

    class.define_method(
        "needs_image_processing",
        method!(ExtractionConfig::needs_image_processing, 0),
    )?;

    let class = module.define_class("FileExtractionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(FileExtractionConfig::new, -1))?;

    class.define_method(
        "enable_quality_processing",
        method!(FileExtractionConfig::enable_quality_processing, 0),
    )?;

    class.define_method("ocr", method!(FileExtractionConfig::ocr, 0))?;

    class.define_method("force_ocr", method!(FileExtractionConfig::force_ocr, 0))?;

    class.define_method("force_ocr_pages", method!(FileExtractionConfig::force_ocr_pages, 0))?;

    class.define_method("disable_ocr", method!(FileExtractionConfig::disable_ocr, 0))?;

    class.define_method("chunking", method!(FileExtractionConfig::chunking, 0))?;

    class.define_method("content_filter", method!(FileExtractionConfig::content_filter, 0))?;

    class.define_method("images", method!(FileExtractionConfig::images, 0))?;

    class.define_method("pdf_options", method!(FileExtractionConfig::pdf_options, 0))?;

    class.define_method("token_reduction", method!(FileExtractionConfig::token_reduction, 0))?;

    class.define_method(
        "language_detection",
        method!(FileExtractionConfig::language_detection, 0),
    )?;

    class.define_method("pages", method!(FileExtractionConfig::pages, 0))?;

    class.define_method("keywords", method!(FileExtractionConfig::keywords, 0))?;

    class.define_method("postprocessor", method!(FileExtractionConfig::postprocessor, 0))?;

    class.define_method("html_options", method!(FileExtractionConfig::html_options, 0))?;

    class.define_method("result_format", method!(FileExtractionConfig::result_format, 0))?;

    class.define_method("output_format", method!(FileExtractionConfig::output_format, 0))?;

    class.define_method(
        "include_document_structure",
        method!(FileExtractionConfig::include_document_structure, 0),
    )?;

    class.define_method("layout", method!(FileExtractionConfig::layout, 0))?;

    class.define_method("timeout_secs", method!(FileExtractionConfig::timeout_secs, 0))?;

    class.define_method("tree_sitter", method!(FileExtractionConfig::tree_sitter, 0))?;

    class.define_method(
        "structured_extraction",
        method!(FileExtractionConfig::structured_extraction, 0),
    )?;

    let class = module.define_class("BatchBytesItem", ruby.class_object())?;

    class.define_singleton_method("new", function!(BatchBytesItem::new, -1))?;

    class.define_method("content", method!(BatchBytesItem::content, 0))?;

    class.define_method("mime_type", method!(BatchBytesItem::mime_type, 0))?;

    class.define_method("config", method!(BatchBytesItem::config, 0))?;

    let class = module.define_class("BatchFileItem", ruby.class_object())?;

    class.define_singleton_method("new", function!(BatchFileItem::new, -1))?;

    class.define_method("path", method!(BatchFileItem::path, 0))?;

    class.define_method("config", method!(BatchFileItem::config, 0))?;

    let class = module.define_class("ImageExtractionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(ImageExtractionConfig::new, -1))?;

    class.define_method("extract_images", method!(ImageExtractionConfig::extract_images, 0))?;

    class.define_method("target_dpi", method!(ImageExtractionConfig::target_dpi, 0))?;

    class.define_method(
        "max_image_dimension",
        method!(ImageExtractionConfig::max_image_dimension, 0),
    )?;

    class.define_method(
        "inject_placeholders",
        method!(ImageExtractionConfig::inject_placeholders, 0),
    )?;

    class.define_method("auto_adjust_dpi", method!(ImageExtractionConfig::auto_adjust_dpi, 0))?;

    class.define_method("min_dpi", method!(ImageExtractionConfig::min_dpi, 0))?;

    class.define_method("max_dpi", method!(ImageExtractionConfig::max_dpi, 0))?;

    class.define_method(
        "max_images_per_page",
        method!(ImageExtractionConfig::max_images_per_page, 0),
    )?;

    class.define_method("classify", method!(ImageExtractionConfig::classify, 0))?;

    class.define_method(
        "include_page_rasters",
        method!(ImageExtractionConfig::include_page_rasters, 0),
    )?;

    class.define_method(
        "run_ocr_on_images",
        method!(ImageExtractionConfig::run_ocr_on_images, 0),
    )?;

    class.define_method("ocr_text_only", method!(ImageExtractionConfig::ocr_text_only, 0))?;

    class.define_method("append_ocr_text", method!(ImageExtractionConfig::append_ocr_text, 0))?;

    let class = module.define_class("TokenReductionOptions", ruby.class_object())?;

    class.define_singleton_method("new", function!(TokenReductionOptions::new, -1))?;

    class.define_method("mode", method!(TokenReductionOptions::mode, 0))?;

    class.define_method(
        "preserve_important_words",
        method!(TokenReductionOptions::preserve_important_words, 0),
    )?;

    let class = module.define_class("LanguageDetectionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(LanguageDetectionConfig::new, -1))?;

    class.define_method("enabled", method!(LanguageDetectionConfig::enabled, 0))?;

    class.define_method("min_confidence", method!(LanguageDetectionConfig::min_confidence, 0))?;

    class.define_method("detect_multiple", method!(LanguageDetectionConfig::detect_multiple, 0))?;

    let class = module.define_class("HtmlOutputConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(HtmlOutputConfig::new, -1))?;

    class.define_method("css", method!(HtmlOutputConfig::css, 0))?;

    class.define_method("css_file", method!(HtmlOutputConfig::css_file, 0))?;

    class.define_method("theme", method!(HtmlOutputConfig::theme, 0))?;

    class.define_method("class_prefix", method!(HtmlOutputConfig::class_prefix, 0))?;

    class.define_method("embed_css", method!(HtmlOutputConfig::embed_css, 0))?;

    let class = module.define_class("LayoutDetectionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(LayoutDetectionConfig::new, -1))?;

    class.define_method(
        "confidence_threshold",
        method!(LayoutDetectionConfig::confidence_threshold, 0),
    )?;

    class.define_method("apply_heuristics", method!(LayoutDetectionConfig::apply_heuristics, 0))?;

    class.define_method("table_model", method!(LayoutDetectionConfig::table_model, 0))?;

    class.define_method("acceleration", method!(LayoutDetectionConfig::acceleration, 0))?;

    let class = module.define_class("LlmConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(LlmConfig::new, -1))?;

    class.define_method("model", method!(LlmConfig::model, 0))?;

    class.define_method("api_key", method!(LlmConfig::api_key, 0))?;

    class.define_method("base_url", method!(LlmConfig::base_url, 0))?;

    class.define_method("timeout_secs", method!(LlmConfig::timeout_secs, 0))?;

    class.define_method("max_retries", method!(LlmConfig::max_retries, 0))?;

    class.define_method("temperature", method!(LlmConfig::temperature, 0))?;

    class.define_method("max_tokens", method!(LlmConfig::max_tokens, 0))?;

    let class = module.define_class("StructuredExtractionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(StructuredExtractionConfig::new, -1))?;

    class.define_method("schema", method!(StructuredExtractionConfig::schema, 0))?;

    class.define_method("schema_name", method!(StructuredExtractionConfig::schema_name, 0))?;

    class.define_method(
        "schema_description",
        method!(StructuredExtractionConfig::schema_description, 0),
    )?;

    class.define_method("strict", method!(StructuredExtractionConfig::strict, 0))?;

    class.define_method("prompt", method!(StructuredExtractionConfig::prompt, 0))?;

    class.define_method("llm", method!(StructuredExtractionConfig::llm, 0))?;

    let class = module.define_class("OcrQualityThresholds", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrQualityThresholds::new, -1))?;

    class.define_method(
        "min_total_non_whitespace",
        method!(OcrQualityThresholds::min_total_non_whitespace, 0),
    )?;

    class.define_method(
        "min_non_whitespace_per_page",
        method!(OcrQualityThresholds::min_non_whitespace_per_page, 0),
    )?;

    class.define_method(
        "min_meaningful_word_len",
        method!(OcrQualityThresholds::min_meaningful_word_len, 0),
    )?;

    class.define_method(
        "min_meaningful_words",
        method!(OcrQualityThresholds::min_meaningful_words, 0),
    )?;

    class.define_method("min_alnum_ratio", method!(OcrQualityThresholds::min_alnum_ratio, 0))?;

    class.define_method("min_garbage_chars", method!(OcrQualityThresholds::min_garbage_chars, 0))?;

    class.define_method(
        "max_fragmented_word_ratio",
        method!(OcrQualityThresholds::max_fragmented_word_ratio, 0),
    )?;

    class.define_method(
        "critical_fragmented_word_ratio",
        method!(OcrQualityThresholds::critical_fragmented_word_ratio, 0),
    )?;

    class.define_method(
        "min_avg_word_length",
        method!(OcrQualityThresholds::min_avg_word_length, 0),
    )?;

    class.define_method(
        "min_words_for_avg_length_check",
        method!(OcrQualityThresholds::min_words_for_avg_length_check, 0),
    )?;

    class.define_method(
        "min_consecutive_repeat_ratio",
        method!(OcrQualityThresholds::min_consecutive_repeat_ratio, 0),
    )?;

    class.define_method(
        "min_words_for_repeat_check",
        method!(OcrQualityThresholds::min_words_for_repeat_check, 0),
    )?;

    class.define_method(
        "substantive_min_chars",
        method!(OcrQualityThresholds::substantive_min_chars, 0),
    )?;

    class.define_method(
        "non_text_min_chars",
        method!(OcrQualityThresholds::non_text_min_chars, 0),
    )?;

    class.define_method(
        "alnum_ws_ratio_threshold",
        method!(OcrQualityThresholds::alnum_ws_ratio_threshold, 0),
    )?;

    class.define_method(
        "pipeline_min_quality",
        method!(OcrQualityThresholds::pipeline_min_quality, 0),
    )?;

    let class = module.define_class("OcrPipelineStage", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrPipelineStage::new, -1))?;

    class.define_method("backend", method!(OcrPipelineStage::backend, 0))?;

    class.define_method("priority", method!(OcrPipelineStage::priority, 0))?;

    class.define_method("language", method!(OcrPipelineStage::language, 0))?;

    class.define_method("tesseract_config", method!(OcrPipelineStage::tesseract_config, 0))?;

    class.define_method("paddle_ocr_config", method!(OcrPipelineStage::paddle_ocr_config, 0))?;

    class.define_method("vlm_config", method!(OcrPipelineStage::vlm_config, 0))?;

    class.define_method("backend_options", method!(OcrPipelineStage::backend_options, 0))?;

    let class = module.define_class("OcrPipelineConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrPipelineConfig::new, -1))?;

    class.define_method("stages", method!(OcrPipelineConfig::stages, 0))?;

    class.define_method("quality_thresholds", method!(OcrPipelineConfig::quality_thresholds, 0))?;

    let class = module.define_class("OcrConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrConfig::new, -1))?;

    class.define_method("enabled", method!(OcrConfig::enabled, 0))?;

    class.define_method("backend", method!(OcrConfig::backend, 0))?;

    class.define_method("language", method!(OcrConfig::language, 0))?;

    class.define_method("tesseract_config", method!(OcrConfig::tesseract_config, 0))?;

    class.define_method("output_format", method!(OcrConfig::output_format, 0))?;

    class.define_method("paddle_ocr_config", method!(OcrConfig::paddle_ocr_config, 0))?;

    class.define_method("backend_options", method!(OcrConfig::backend_options, 0))?;

    class.define_method("element_config", method!(OcrConfig::element_config, 0))?;

    class.define_method("quality_thresholds", method!(OcrConfig::quality_thresholds, 0))?;

    class.define_method("pipeline", method!(OcrConfig::pipeline, 0))?;

    class.define_method("auto_rotate", method!(OcrConfig::auto_rotate, 0))?;

    class.define_method("vlm_config", method!(OcrConfig::vlm_config, 0))?;

    class.define_method("vlm_prompt", method!(OcrConfig::vlm_prompt, 0))?;

    class.define_method("acceleration", method!(OcrConfig::acceleration, 0))?;

    class.define_method("tessdata_bytes", method!(OcrConfig::tessdata_bytes, 0))?;

    let class = module.define_class("PageConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(PageConfig::new, -1))?;

    class.define_method("extract_pages", method!(PageConfig::extract_pages, 0))?;

    class.define_method("insert_page_markers", method!(PageConfig::insert_page_markers, 0))?;

    class.define_method("marker_format", method!(PageConfig::marker_format, 0))?;

    let class = module.define_class("PdfConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(PdfConfig::new, -1))?;

    class.define_method("extract_images", method!(PdfConfig::extract_images, 0))?;

    class.define_method("extract_tables", method!(PdfConfig::extract_tables, 0))?;

    class.define_method("passwords", method!(PdfConfig::passwords, 0))?;

    class.define_method("extract_metadata", method!(PdfConfig::extract_metadata, 0))?;

    class.define_method("hierarchy", method!(PdfConfig::hierarchy, 0))?;

    class.define_method("extract_annotations", method!(PdfConfig::extract_annotations, 0))?;

    class.define_method("top_margin_fraction", method!(PdfConfig::top_margin_fraction, 0))?;

    class.define_method("bottom_margin_fraction", method!(PdfConfig::bottom_margin_fraction, 0))?;

    class.define_method(
        "allow_single_column_tables",
        method!(PdfConfig::allow_single_column_tables, 0),
    )?;

    class.define_method("ocr_inline_images", method!(PdfConfig::ocr_inline_images, 0))?;

    let class = module.define_class("HierarchyConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(HierarchyConfig::new, -1))?;

    class.define_method("enabled", method!(HierarchyConfig::enabled, 0))?;

    class.define_method("k_clusters", method!(HierarchyConfig::k_clusters, 0))?;

    class.define_method("include_bbox", method!(HierarchyConfig::include_bbox, 0))?;

    class.define_method(
        "ocr_coverage_threshold",
        method!(HierarchyConfig::ocr_coverage_threshold, 0),
    )?;

    let class = module.define_class("PostProcessorConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(PostProcessorConfig::new, -1))?;

    class.define_method("enabled", method!(PostProcessorConfig::enabled, 0))?;

    class.define_method(
        "enabled_processors",
        method!(PostProcessorConfig::enabled_processors, 0),
    )?;

    class.define_method(
        "disabled_processors",
        method!(PostProcessorConfig::disabled_processors, 0),
    )?;

    class.define_method("enabled_set", method!(PostProcessorConfig::enabled_set, 0))?;

    class.define_method("disabled_set", method!(PostProcessorConfig::disabled_set, 0))?;

    let class = module.define_class("ChunkingConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(ChunkingConfig::new, -1))?;

    class.define_method("max_characters", method!(ChunkingConfig::max_characters, 0))?;

    class.define_method("overlap", method!(ChunkingConfig::overlap, 0))?;

    class.define_method("trim", method!(ChunkingConfig::trim, 0))?;

    class.define_method("chunker_type", method!(ChunkingConfig::chunker_type, 0))?;

    class.define_method("embedding", method!(ChunkingConfig::embedding, 0))?;

    class.define_method("preset", method!(ChunkingConfig::preset, 0))?;

    class.define_method("sizing", method!(ChunkingConfig::sizing, 0))?;

    class.define_method(
        "prepend_heading_context",
        method!(ChunkingConfig::prepend_heading_context, 0),
    )?;

    class.define_method("topic_threshold", method!(ChunkingConfig::topic_threshold, 0))?;

    let class = module.define_class("EmbeddingConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmbeddingConfig::new, -1))?;

    class.define_method("model", method!(EmbeddingConfig::model, 0))?;

    class.define_method("normalize", method!(EmbeddingConfig::normalize, 0))?;

    class.define_method("batch_size", method!(EmbeddingConfig::batch_size, 0))?;

    class.define_method(
        "show_download_progress",
        method!(EmbeddingConfig::show_download_progress, 0),
    )?;

    class.define_method("cache_dir", method!(EmbeddingConfig::cache_dir, 0))?;

    class.define_method("acceleration", method!(EmbeddingConfig::acceleration, 0))?;

    class.define_method(
        "max_embed_duration_secs",
        method!(EmbeddingConfig::max_embed_duration_secs, 0),
    )?;

    let class = module.define_class("TreeSitterConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(TreeSitterConfig::new, -1))?;

    class.define_method("enabled", method!(TreeSitterConfig::enabled, 0))?;

    class.define_method("cache_dir", method!(TreeSitterConfig::cache_dir, 0))?;

    class.define_method("languages", method!(TreeSitterConfig::languages, 0))?;

    class.define_method("groups", method!(TreeSitterConfig::groups, 0))?;

    class.define_method("process", method!(TreeSitterConfig::process, 0))?;

    let class = module.define_class("TreeSitterProcessConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(TreeSitterProcessConfig::new, -1))?;

    class.define_method("structure", method!(TreeSitterProcessConfig::structure, 0))?;

    class.define_method("imports", method!(TreeSitterProcessConfig::imports, 0))?;

    class.define_method("exports", method!(TreeSitterProcessConfig::exports, 0))?;

    class.define_method("comments", method!(TreeSitterProcessConfig::comments, 0))?;

    class.define_method("docstrings", method!(TreeSitterProcessConfig::docstrings, 0))?;

    class.define_method("symbols", method!(TreeSitterProcessConfig::symbols, 0))?;

    class.define_method("diagnostics", method!(TreeSitterProcessConfig::diagnostics, 0))?;

    class.define_method("chunk_max_size", method!(TreeSitterProcessConfig::chunk_max_size, 0))?;

    class.define_method("content_mode", method!(TreeSitterProcessConfig::content_mode, 0))?;

    let class = module.define_class("SupportedFormat", ruby.class_object())?;

    class.define_singleton_method("new", function!(SupportedFormat::new, -1))?;

    class.define_method("extension", method!(SupportedFormat::extension, 0))?;

    class.define_method("mime_type", method!(SupportedFormat::mime_type, 0))?;

    let class = module.define_class("ServerConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(ServerConfig::new, -1))?;

    class.define_method("host", method!(ServerConfig::host, 0))?;

    class.define_method("port", method!(ServerConfig::port, 0))?;

    class.define_method("cors_origins", method!(ServerConfig::cors_origins, 0))?;

    class.define_method(
        "max_request_body_bytes",
        method!(ServerConfig::max_request_body_bytes, 0),
    )?;

    class.define_method(
        "max_multipart_field_bytes",
        method!(ServerConfig::max_multipart_field_bytes, 0),
    )?;

    class.define_method("listen_addr", method!(ServerConfig::listen_addr, 0))?;

    class.define_method("cors_allows_all", method!(ServerConfig::cors_allows_all, 0))?;

    class.define_method("is_origin_allowed", method!(ServerConfig::is_origin_allowed, 1))?;

    class.define_method("max_request_body_mb", method!(ServerConfig::max_request_body_mb, 0))?;

    class.define_method(
        "max_multipart_field_mb",
        method!(ServerConfig::max_multipart_field_mb, 0),
    )?;

    let class = module.define_class("StructuredDataResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(StructuredDataResult::new, -1))?;

    class.define_method("content", method!(StructuredDataResult::content, 0))?;

    class.define_method("format", method!(StructuredDataResult::format, 0))?;

    class.define_method("metadata", method!(StructuredDataResult::metadata, 0))?;

    class.define_method("text_fields", method!(StructuredDataResult::text_fields, 0))?;

    class.define_method("to_s", method!(StructuredDataResult::to_s, 0))?;

    let class = module.define_class("DocxAppProperties", ruby.class_object())?;

    class.define_singleton_method("new", function!(DocxAppProperties::new, -1))?;

    class.define_method("application", method!(DocxAppProperties::application, 0))?;

    class.define_method("app_version", method!(DocxAppProperties::app_version, 0))?;

    class.define_method("template", method!(DocxAppProperties::template, 0))?;

    class.define_method("total_time", method!(DocxAppProperties::total_time, 0))?;

    class.define_method("pages", method!(DocxAppProperties::pages, 0))?;

    class.define_method("words", method!(DocxAppProperties::words, 0))?;

    class.define_method("characters", method!(DocxAppProperties::characters, 0))?;

    class.define_method(
        "characters_with_spaces",
        method!(DocxAppProperties::characters_with_spaces, 0),
    )?;

    class.define_method("lines", method!(DocxAppProperties::lines, 0))?;

    class.define_method("paragraphs", method!(DocxAppProperties::paragraphs, 0))?;

    class.define_method("company", method!(DocxAppProperties::company, 0))?;

    class.define_method("doc_security", method!(DocxAppProperties::doc_security, 0))?;

    class.define_method("scale_crop", method!(DocxAppProperties::scale_crop, 0))?;

    class.define_method("links_up_to_date", method!(DocxAppProperties::links_up_to_date, 0))?;

    class.define_method("shared_doc", method!(DocxAppProperties::shared_doc, 0))?;

    class.define_method("hyperlinks_changed", method!(DocxAppProperties::hyperlinks_changed, 0))?;

    let class = module.define_class("XlsxAppProperties", ruby.class_object())?;

    class.define_singleton_method("new", function!(XlsxAppProperties::new, -1))?;

    class.define_method("application", method!(XlsxAppProperties::application, 0))?;

    class.define_method("app_version", method!(XlsxAppProperties::app_version, 0))?;

    class.define_method("doc_security", method!(XlsxAppProperties::doc_security, 0))?;

    class.define_method("scale_crop", method!(XlsxAppProperties::scale_crop, 0))?;

    class.define_method("links_up_to_date", method!(XlsxAppProperties::links_up_to_date, 0))?;

    class.define_method("shared_doc", method!(XlsxAppProperties::shared_doc, 0))?;

    class.define_method("hyperlinks_changed", method!(XlsxAppProperties::hyperlinks_changed, 0))?;

    class.define_method("company", method!(XlsxAppProperties::company, 0))?;

    class.define_method("worksheet_names", method!(XlsxAppProperties::worksheet_names, 0))?;

    let class = module.define_class("PptxAppProperties", ruby.class_object())?;

    class.define_singleton_method("new", function!(PptxAppProperties::new, -1))?;

    class.define_method("application", method!(PptxAppProperties::application, 0))?;

    class.define_method("app_version", method!(PptxAppProperties::app_version, 0))?;

    class.define_method("total_time", method!(PptxAppProperties::total_time, 0))?;

    class.define_method("company", method!(PptxAppProperties::company, 0))?;

    class.define_method("doc_security", method!(PptxAppProperties::doc_security, 0))?;

    class.define_method("scale_crop", method!(PptxAppProperties::scale_crop, 0))?;

    class.define_method("links_up_to_date", method!(PptxAppProperties::links_up_to_date, 0))?;

    class.define_method("shared_doc", method!(PptxAppProperties::shared_doc, 0))?;

    class.define_method("hyperlinks_changed", method!(PptxAppProperties::hyperlinks_changed, 0))?;

    class.define_method("slides", method!(PptxAppProperties::slides, 0))?;

    class.define_method("notes", method!(PptxAppProperties::notes, 0))?;

    class.define_method("hidden_slides", method!(PptxAppProperties::hidden_slides, 0))?;

    class.define_method("multimedia_clips", method!(PptxAppProperties::multimedia_clips, 0))?;

    class.define_method(
        "presentation_format",
        method!(PptxAppProperties::presentation_format, 0),
    )?;

    class.define_method("slide_titles", method!(PptxAppProperties::slide_titles, 0))?;

    let class = module.define_class("CoreProperties", ruby.class_object())?;

    class.define_singleton_method("new", function!(CoreProperties::new, -1))?;

    class.define_method("title", method!(CoreProperties::title, 0))?;

    class.define_method("subject", method!(CoreProperties::subject, 0))?;

    class.define_method("creator", method!(CoreProperties::creator, 0))?;

    class.define_method("keywords", method!(CoreProperties::keywords, 0))?;

    class.define_method("description", method!(CoreProperties::description, 0))?;

    class.define_method("last_modified_by", method!(CoreProperties::last_modified_by, 0))?;

    class.define_method("revision", method!(CoreProperties::revision, 0))?;

    class.define_method("created", method!(CoreProperties::created, 0))?;

    class.define_method("modified", method!(CoreProperties::modified, 0))?;

    class.define_method("category", method!(CoreProperties::category, 0))?;

    class.define_method("content_status", method!(CoreProperties::content_status, 0))?;

    class.define_method("language", method!(CoreProperties::language, 0))?;

    class.define_method("identifier", method!(CoreProperties::identifier, 0))?;

    class.define_method("version", method!(CoreProperties::version, 0))?;

    class.define_method("last_printed", method!(CoreProperties::last_printed, 0))?;

    let class = module.define_class("SecurityLimits", ruby.class_object())?;

    class.define_singleton_method("new", function!(SecurityLimits::new, -1))?;

    class.define_method("max_archive_size", method!(SecurityLimits::max_archive_size, 0))?;

    class.define_method(
        "max_compression_ratio",
        method!(SecurityLimits::max_compression_ratio, 0),
    )?;

    class.define_method("max_files_in_archive", method!(SecurityLimits::max_files_in_archive, 0))?;

    class.define_method("max_nesting_depth", method!(SecurityLimits::max_nesting_depth, 0))?;

    class.define_method("max_entity_length", method!(SecurityLimits::max_entity_length, 0))?;

    class.define_method("max_content_size", method!(SecurityLimits::max_content_size, 0))?;

    class.define_method("max_iterations", method!(SecurityLimits::max_iterations, 0))?;

    class.define_method("max_xml_depth", method!(SecurityLimits::max_xml_depth, 0))?;

    class.define_method("max_table_cells", method!(SecurityLimits::max_table_cells, 0))?;

    let class = module.define_class("TokenReductionConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(TokenReductionConfig::new, -1))?;

    class.define_method("level", method!(TokenReductionConfig::level, 0))?;

    class.define_method("language_hint", method!(TokenReductionConfig::language_hint, 0))?;

    class.define_method("preserve_markdown", method!(TokenReductionConfig::preserve_markdown, 0))?;

    class.define_method("preserve_code", method!(TokenReductionConfig::preserve_code, 0))?;

    class.define_method(
        "semantic_threshold",
        method!(TokenReductionConfig::semantic_threshold, 0),
    )?;

    class.define_method("enable_parallel", method!(TokenReductionConfig::enable_parallel, 0))?;

    class.define_method("use_simd", method!(TokenReductionConfig::use_simd, 0))?;

    class.define_method("custom_stopwords", method!(TokenReductionConfig::custom_stopwords, 0))?;

    class.define_method("preserve_patterns", method!(TokenReductionConfig::preserve_patterns, 0))?;

    class.define_method("target_reduction", method!(TokenReductionConfig::target_reduction, 0))?;

    class.define_method(
        "enable_semantic_clustering",
        method!(TokenReductionConfig::enable_semantic_clustering, 0),
    )?;

    let class = module.define_class("PdfAnnotation", ruby.class_object())?;

    class.define_singleton_method("new", function!(PdfAnnotation::new, -1))?;

    class.define_method("annotation_type", method!(PdfAnnotation::annotation_type, 0))?;

    class.define_method("content", method!(PdfAnnotation::content, 0))?;

    class.define_method("page_number", method!(PdfAnnotation::page_number, 0))?;

    class.define_method("bounding_box", method!(PdfAnnotation::bounding_box, 0))?;

    class.define_method("to_s", method!(PdfAnnotation::to_s, 0))?;

    let class = module.define_class("DjotContent", ruby.class_object())?;

    class.define_singleton_method("new", function!(DjotContent::new, -1))?;

    class.define_method("plain_text", method!(DjotContent::plain_text, 0))?;

    class.define_method("blocks", method!(DjotContent::blocks, 0))?;

    class.define_method("metadata", method!(DjotContent::metadata, 0))?;

    class.define_method("tables", method!(DjotContent::tables, 0))?;

    class.define_method("images", method!(DjotContent::images, 0))?;

    class.define_method("links", method!(DjotContent::links, 0))?;

    class.define_method("footnotes", method!(DjotContent::footnotes, 0))?;

    class.define_method("attributes", method!(DjotContent::attributes, 0))?;

    let class = module.define_class("FormattedBlock", ruby.class_object())?;

    class.define_singleton_method("new", function!(FormattedBlock::new, -1))?;

    class.define_method("block_type", method!(FormattedBlock::block_type, 0))?;

    class.define_method("level", method!(FormattedBlock::level, 0))?;

    class.define_method("inline_content", method!(FormattedBlock::inline_content, 0))?;

    class.define_method("attributes", method!(FormattedBlock::attributes, 0))?;

    class.define_method("language", method!(FormattedBlock::language, 0))?;

    class.define_method("code", method!(FormattedBlock::code, 0))?;

    class.define_method("children", method!(FormattedBlock::children, 0))?;

    let class = module.define_class("InlineElement", ruby.class_object())?;

    class.define_singleton_method("new", function!(InlineElement::new, -1))?;

    class.define_method("element_type", method!(InlineElement::element_type, 0))?;

    class.define_method("content", method!(InlineElement::content, 0))?;

    class.define_method("attributes", method!(InlineElement::attributes, 0))?;

    class.define_method("metadata", method!(InlineElement::metadata, 0))?;

    class.define_method("to_s", method!(InlineElement::to_s, 0))?;

    let class = module.define_class("DjotImage", ruby.class_object())?;

    class.define_singleton_method("new", function!(DjotImage::new, -1))?;

    class.define_method("src", method!(DjotImage::src, 0))?;

    class.define_method("alt", method!(DjotImage::alt, 0))?;

    class.define_method("title", method!(DjotImage::title, 0))?;

    class.define_method("attributes", method!(DjotImage::attributes, 0))?;

    let class = module.define_class("DjotLink", ruby.class_object())?;

    class.define_singleton_method("new", function!(DjotLink::new, -1))?;

    class.define_method("url", method!(DjotLink::url, 0))?;

    class.define_method("text", method!(DjotLink::text, 0))?;

    class.define_method("title", method!(DjotLink::title, 0))?;

    class.define_method("attributes", method!(DjotLink::attributes, 0))?;

    let class = module.define_class("Footnote", ruby.class_object())?;

    class.define_singleton_method("new", function!(Footnote::new, -1))?;

    class.define_method("label", method!(Footnote::label, 0))?;

    class.define_method("content", method!(Footnote::content, 0))?;

    let class = module.define_class("DocumentStructure", ruby.class_object())?;

    class.define_singleton_method("new", function!(DocumentStructure::new, -1))?;

    class.define_method("nodes", method!(DocumentStructure::nodes, 0))?;

    class.define_method("source_format", method!(DocumentStructure::source_format, 0))?;

    class.define_method("relationships", method!(DocumentStructure::relationships, 0))?;

    class.define_method("node_types", method!(DocumentStructure::node_types, 0))?;

    class.define_method("is_empty", method!(DocumentStructure::is_empty, 0))?;

    let class = module.define_class("DocumentRelationship", ruby.class_object())?;

    class.define_singleton_method("new", function!(DocumentRelationship::new, -1))?;

    class.define_method("source", method!(DocumentRelationship::source, 0))?;

    class.define_method("target", method!(DocumentRelationship::target, 0))?;

    class.define_method("kind", method!(DocumentRelationship::kind, 0))?;

    let class = module.define_class("DocumentNode", ruby.class_object())?;

    class.define_singleton_method("new", function!(DocumentNode::new, -1))?;

    class.define_method("id", method!(DocumentNode::id, 0))?;

    class.define_method("content", method!(DocumentNode::content, 0))?;

    class.define_method("parent", method!(DocumentNode::parent, 0))?;

    class.define_method("children", method!(DocumentNode::children, 0))?;

    class.define_method("content_layer", method!(DocumentNode::content_layer, 0))?;

    class.define_method("page", method!(DocumentNode::page, 0))?;

    class.define_method("page_end", method!(DocumentNode::page_end, 0))?;

    class.define_method("bbox", method!(DocumentNode::bbox, 0))?;

    class.define_method("annotations", method!(DocumentNode::annotations, 0))?;

    class.define_method("attributes", method!(DocumentNode::attributes, 0))?;

    let class = module.define_class("TableGrid", ruby.class_object())?;

    class.define_singleton_method("new", function!(TableGrid::new, -1))?;

    class.define_method("rows", method!(TableGrid::rows, 0))?;

    class.define_method("cols", method!(TableGrid::cols, 0))?;

    class.define_method("cells", method!(TableGrid::cells, 0))?;

    let class = module.define_class("GridCell", ruby.class_object())?;

    class.define_singleton_method("new", function!(GridCell::new, -1))?;

    class.define_method("content", method!(GridCell::content, 0))?;

    class.define_method("row", method!(GridCell::row, 0))?;

    class.define_method("col", method!(GridCell::col, 0))?;

    class.define_method("row_span", method!(GridCell::row_span, 0))?;

    class.define_method("col_span", method!(GridCell::col_span, 0))?;

    class.define_method("is_header", method!(GridCell::is_header, 0))?;

    class.define_method("bbox", method!(GridCell::bbox, 0))?;

    class.define_method("to_s", method!(GridCell::to_s, 0))?;

    let class = module.define_class("TextAnnotation", ruby.class_object())?;

    class.define_singleton_method("new", function!(TextAnnotation::new, -1))?;

    class.define_method("start", method!(TextAnnotation::start, 0))?;

    class.define_method("end", method!(TextAnnotation::end, 0))?;

    class.define_method("kind", method!(TextAnnotation::kind, 0))?;

    let class = module.define_class("ExtractionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExtractionResult::new, -1))?;

    class.define_method("content", method!(ExtractionResult::content, 0))?;

    class.define_method("mime_type", method!(ExtractionResult::mime_type, 0))?;

    class.define_method("metadata", method!(ExtractionResult::metadata, 0))?;

    class.define_method("extraction_method", method!(ExtractionResult::extraction_method, 0))?;

    class.define_method("tables", method!(ExtractionResult::tables, 0))?;

    class.define_method("detected_languages", method!(ExtractionResult::detected_languages, 0))?;

    class.define_method("chunks", method!(ExtractionResult::chunks, 0))?;

    class.define_method("images", method!(ExtractionResult::images, 0))?;

    class.define_method("pages", method!(ExtractionResult::pages, 0))?;

    class.define_method("elements", method!(ExtractionResult::elements, 0))?;

    class.define_method("djot_content", method!(ExtractionResult::djot_content, 0))?;

    class.define_method("ocr_elements", method!(ExtractionResult::ocr_elements, 0))?;

    class.define_method("document", method!(ExtractionResult::document, 0))?;

    class.define_method("extracted_keywords", method!(ExtractionResult::extracted_keywords, 0))?;

    class.define_method("quality_score", method!(ExtractionResult::quality_score, 0))?;

    class.define_method("processing_warnings", method!(ExtractionResult::processing_warnings, 0))?;

    class.define_method("annotations", method!(ExtractionResult::annotations, 0))?;

    class.define_method("children", method!(ExtractionResult::children, 0))?;

    class.define_method("uris", method!(ExtractionResult::uris, 0))?;

    class.define_method("revisions", method!(ExtractionResult::revisions, 0))?;

    class.define_method("structured_output", method!(ExtractionResult::structured_output, 0))?;

    class.define_method("code_intelligence", method!(ExtractionResult::code_intelligence, 0))?;

    class.define_method("llm_usage", method!(ExtractionResult::llm_usage, 0))?;

    class.define_method("formatted_content", method!(ExtractionResult::formatted_content, 0))?;

    class.define_method(
        "ocr_internal_document",
        method!(ExtractionResult::ocr_internal_document, 0),
    )?;

    class.define_method("to_s", method!(ExtractionResult::to_s, 0))?;

    let class = module.define_class("ArchiveEntry", ruby.class_object())?;

    class.define_singleton_method("new", function!(ArchiveEntry::new, -1))?;

    class.define_method("path", method!(ArchiveEntry::path, 0))?;

    class.define_method("mime_type", method!(ArchiveEntry::mime_type, 0))?;

    class.define_method("result", method!(ArchiveEntry::result, 0))?;

    let class = module.define_class("ProcessingWarning", ruby.class_object())?;

    class.define_singleton_method("new", function!(ProcessingWarning::new, -1))?;

    class.define_method("source", method!(ProcessingWarning::source, 0))?;

    class.define_method("message", method!(ProcessingWarning::message, 0))?;

    let class = module.define_class("LlmUsage", ruby.class_object())?;

    class.define_singleton_method("new", function!(LlmUsage::new, -1))?;

    class.define_method("model", method!(LlmUsage::model, 0))?;

    class.define_method("source", method!(LlmUsage::source, 0))?;

    class.define_method("input_tokens", method!(LlmUsage::input_tokens, 0))?;

    class.define_method("output_tokens", method!(LlmUsage::output_tokens, 0))?;

    class.define_method("total_tokens", method!(LlmUsage::total_tokens, 0))?;

    class.define_method("estimated_cost", method!(LlmUsage::estimated_cost, 0))?;

    class.define_method("finish_reason", method!(LlmUsage::finish_reason, 0))?;

    let class = module.define_class("Chunk", ruby.class_object())?;

    class.define_singleton_method("new", function!(Chunk::new, -1))?;

    class.define_method("content", method!(Chunk::content, 0))?;

    class.define_method("chunk_type", method!(Chunk::chunk_type, 0))?;

    class.define_method("embedding", method!(Chunk::embedding, 0))?;

    class.define_method("metadata", method!(Chunk::metadata, 0))?;

    class.define_method("to_s", method!(Chunk::to_s, 0))?;

    let class = module.define_class("HeadingContext", ruby.class_object())?;

    class.define_singleton_method("new", function!(HeadingContext::new, -1))?;

    class.define_method("headings", method!(HeadingContext::headings, 0))?;

    let class = module.define_class("HeadingLevel", ruby.class_object())?;

    class.define_singleton_method("new", function!(HeadingLevel::new, -1))?;

    class.define_method("level", method!(HeadingLevel::level, 0))?;

    class.define_method("text", method!(HeadingLevel::text, 0))?;

    let class = module.define_class("ChunkMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ChunkMetadata::new, -1))?;

    class.define_method("byte_start", method!(ChunkMetadata::byte_start, 0))?;

    class.define_method("byte_end", method!(ChunkMetadata::byte_end, 0))?;

    class.define_method("token_count", method!(ChunkMetadata::token_count, 0))?;

    class.define_method("chunk_index", method!(ChunkMetadata::chunk_index, 0))?;

    class.define_method("total_chunks", method!(ChunkMetadata::total_chunks, 0))?;

    class.define_method("first_page", method!(ChunkMetadata::first_page, 0))?;

    class.define_method("last_page", method!(ChunkMetadata::last_page, 0))?;

    class.define_method("heading_context", method!(ChunkMetadata::heading_context, 0))?;

    class.define_method("image_indices", method!(ChunkMetadata::image_indices, 0))?;

    let class = module.define_class("ExtractedImage", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExtractedImage::new, -1))?;

    class.define_method("data", method!(ExtractedImage::data, 0))?;

    class.define_method("format", method!(ExtractedImage::format, 0))?;

    class.define_method("image_index", method!(ExtractedImage::image_index, 0))?;

    class.define_method("page_number", method!(ExtractedImage::page_number, 0))?;

    class.define_method("width", method!(ExtractedImage::width, 0))?;

    class.define_method("height", method!(ExtractedImage::height, 0))?;

    class.define_method("colorspace", method!(ExtractedImage::colorspace, 0))?;

    class.define_method("bits_per_component", method!(ExtractedImage::bits_per_component, 0))?;

    class.define_method("is_mask", method!(ExtractedImage::is_mask, 0))?;

    class.define_method("description", method!(ExtractedImage::description, 0))?;

    class.define_method("ocr_result", method!(ExtractedImage::ocr_result, 0))?;

    class.define_method("bounding_box", method!(ExtractedImage::bounding_box, 0))?;

    class.define_method("source_path", method!(ExtractedImage::source_path, 0))?;

    class.define_method("image_kind", method!(ExtractedImage::image_kind, 0))?;

    class.define_method("kind_confidence", method!(ExtractedImage::kind_confidence, 0))?;

    class.define_method("cluster_id", method!(ExtractedImage::cluster_id, 0))?;

    let class = module.define_class("BoundingBox", ruby.class_object())?;

    class.define_singleton_method("new", function!(BoundingBox::new, -1))?;

    class.define_method("x0", method!(BoundingBox::x0, 0))?;

    class.define_method("y0", method!(BoundingBox::y0, 0))?;

    class.define_method("x1", method!(BoundingBox::x1, 0))?;

    class.define_method("y1", method!(BoundingBox::y1, 0))?;

    let class = module.define_class("ElementMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ElementMetadata::new, -1))?;

    class.define_method("page_number", method!(ElementMetadata::page_number, 0))?;

    class.define_method("filename", method!(ElementMetadata::filename, 0))?;

    class.define_method("coordinates", method!(ElementMetadata::coordinates, 0))?;

    class.define_method("element_index", method!(ElementMetadata::element_index, 0))?;

    class.define_method("additional", method!(ElementMetadata::additional, 0))?;

    let class = module.define_class("Element", ruby.class_object())?;

    class.define_singleton_method("new", function!(Element::new, -1))?;

    class.define_method("element_id", method!(Element::element_id, 0))?;

    class.define_method("element_type", method!(Element::element_type, 0))?;

    class.define_method("text", method!(Element::text, 0))?;

    class.define_method("metadata", method!(Element::metadata, 0))?;

    let class = module.define_class("ExcelWorkbook", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExcelWorkbook::new, -1))?;

    class.define_method("sheets", method!(ExcelWorkbook::sheets, 0))?;

    class.define_method("metadata", method!(ExcelWorkbook::metadata, 0))?;

    class.define_method("revisions", method!(ExcelWorkbook::revisions, 0))?;

    let class = module.define_class("ExcelSheet", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExcelSheet::new, -1))?;

    class.define_method("name", method!(ExcelSheet::name, 0))?;

    class.define_method("markdown", method!(ExcelSheet::markdown, 0))?;

    class.define_method("row_count", method!(ExcelSheet::row_count, 0))?;

    class.define_method("col_count", method!(ExcelSheet::col_count, 0))?;

    class.define_method("cell_count", method!(ExcelSheet::cell_count, 0))?;

    class.define_method("table_cells", method!(ExcelSheet::table_cells, 0))?;

    let class = module.define_class("XmlExtractionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(XmlExtractionResult::new, -1))?;

    class.define_method("content", method!(XmlExtractionResult::content, 0))?;

    class.define_method("element_count", method!(XmlExtractionResult::element_count, 0))?;

    class.define_method("unique_elements", method!(XmlExtractionResult::unique_elements, 0))?;

    class.define_method("to_s", method!(XmlExtractionResult::to_s, 0))?;

    let class = module.define_class("TextExtractionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(TextExtractionResult::new, -1))?;

    class.define_method("content", method!(TextExtractionResult::content, 0))?;

    class.define_method("line_count", method!(TextExtractionResult::line_count, 0))?;

    class.define_method("word_count", method!(TextExtractionResult::word_count, 0))?;

    class.define_method("character_count", method!(TextExtractionResult::character_count, 0))?;

    class.define_method("headers", method!(TextExtractionResult::headers, 0))?;

    class.define_method("links", method!(TextExtractionResult::links, 0))?;

    class.define_method("code_blocks", method!(TextExtractionResult::code_blocks, 0))?;

    class.define_method("to_s", method!(TextExtractionResult::to_s, 0))?;

    let class = module.define_class("PptxExtractionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(PptxExtractionResult::new, -1))?;

    class.define_method("content", method!(PptxExtractionResult::content, 0))?;

    class.define_method("metadata", method!(PptxExtractionResult::metadata, 0))?;

    class.define_method("slide_count", method!(PptxExtractionResult::slide_count, 0))?;

    class.define_method("image_count", method!(PptxExtractionResult::image_count, 0))?;

    class.define_method("table_count", method!(PptxExtractionResult::table_count, 0))?;

    class.define_method("images", method!(PptxExtractionResult::images, 0))?;

    class.define_method("page_structure", method!(PptxExtractionResult::page_structure, 0))?;

    class.define_method("page_contents", method!(PptxExtractionResult::page_contents, 0))?;

    class.define_method("document", method!(PptxExtractionResult::document, 0))?;

    class.define_method("hyperlinks", method!(PptxExtractionResult::hyperlinks, 0))?;

    class.define_method("office_metadata", method!(PptxExtractionResult::office_metadata, 0))?;

    class.define_method("revisions", method!(PptxExtractionResult::revisions, 0))?;

    class.define_method("to_s", method!(PptxExtractionResult::to_s, 0))?;

    let class = module.define_class("EmailExtractionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmailExtractionResult::new, -1))?;

    class.define_method("subject", method!(EmailExtractionResult::subject, 0))?;

    class.define_method("from_email", method!(EmailExtractionResult::from_email, 0))?;

    class.define_method("to_emails", method!(EmailExtractionResult::to_emails, 0))?;

    class.define_method("cc_emails", method!(EmailExtractionResult::cc_emails, 0))?;

    class.define_method("bcc_emails", method!(EmailExtractionResult::bcc_emails, 0))?;

    class.define_method("date", method!(EmailExtractionResult::date, 0))?;

    class.define_method("message_id", method!(EmailExtractionResult::message_id, 0))?;

    class.define_method("plain_text", method!(EmailExtractionResult::plain_text, 0))?;

    class.define_method("html_content", method!(EmailExtractionResult::html_content, 0))?;

    class.define_method("content", method!(EmailExtractionResult::content, 0))?;

    class.define_method("attachments", method!(EmailExtractionResult::attachments, 0))?;

    class.define_method("metadata", method!(EmailExtractionResult::metadata, 0))?;

    class.define_method("to_s", method!(EmailExtractionResult::to_s, 0))?;

    let class = module.define_class("EmailAttachment", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmailAttachment::new, -1))?;

    class.define_method("name", method!(EmailAttachment::name, 0))?;

    class.define_method("filename", method!(EmailAttachment::filename, 0))?;

    class.define_method("mime_type", method!(EmailAttachment::mime_type, 0))?;

    class.define_method("size", method!(EmailAttachment::size, 0))?;

    class.define_method("is_image", method!(EmailAttachment::is_image, 0))?;

    class.define_method("data", method!(EmailAttachment::data, 0))?;

    let class = module.define_class("OcrExtractionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrExtractionResult::new, -1))?;

    class.define_method("content", method!(OcrExtractionResult::content, 0))?;

    class.define_method("mime_type", method!(OcrExtractionResult::mime_type, 0))?;

    class.define_method("metadata", method!(OcrExtractionResult::metadata, 0))?;

    class.define_method("tables", method!(OcrExtractionResult::tables, 0))?;

    class.define_method("ocr_elements", method!(OcrExtractionResult::ocr_elements, 0))?;

    class.define_method("internal_document", method!(OcrExtractionResult::internal_document, 0))?;

    class.define_method("to_s", method!(OcrExtractionResult::to_s, 0))?;

    let class = module.define_class("OcrTable", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrTable::new, -1))?;

    class.define_method("cells", method!(OcrTable::cells, 0))?;

    class.define_method("markdown", method!(OcrTable::markdown, 0))?;

    class.define_method("page_number", method!(OcrTable::page_number, 0))?;

    class.define_method("bounding_box", method!(OcrTable::bounding_box, 0))?;

    let class = module.define_class("OcrTableBoundingBox", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrTableBoundingBox::new, -1))?;

    class.define_method("left", method!(OcrTableBoundingBox::left, 0))?;

    class.define_method("top", method!(OcrTableBoundingBox::top, 0))?;

    class.define_method("right", method!(OcrTableBoundingBox::right, 0))?;

    class.define_method("bottom", method!(OcrTableBoundingBox::bottom, 0))?;

    let class = module.define_class("ImagePreprocessingConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(ImagePreprocessingConfig::new, -1))?;

    class.define_method("target_dpi", method!(ImagePreprocessingConfig::target_dpi, 0))?;

    class.define_method("auto_rotate", method!(ImagePreprocessingConfig::auto_rotate, 0))?;

    class.define_method("deskew", method!(ImagePreprocessingConfig::deskew, 0))?;

    class.define_method("denoise", method!(ImagePreprocessingConfig::denoise, 0))?;

    class.define_method(
        "contrast_enhance",
        method!(ImagePreprocessingConfig::contrast_enhance, 0),
    )?;

    class.define_method(
        "binarization_method",
        method!(ImagePreprocessingConfig::binarization_method, 0),
    )?;

    class.define_method("invert_colors", method!(ImagePreprocessingConfig::invert_colors, 0))?;

    let class = module.define_class("TesseractConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(TesseractConfig::new, -1))?;

    class.define_method("language", method!(TesseractConfig::language, 0))?;

    class.define_method("psm", method!(TesseractConfig::psm, 0))?;

    class.define_method("output_format", method!(TesseractConfig::output_format, 0))?;

    class.define_method("oem", method!(TesseractConfig::oem, 0))?;

    class.define_method("min_confidence", method!(TesseractConfig::min_confidence, 0))?;

    class.define_method("preprocessing", method!(TesseractConfig::preprocessing, 0))?;

    class.define_method(
        "enable_table_detection",
        method!(TesseractConfig::enable_table_detection, 0),
    )?;

    class.define_method(
        "table_min_confidence",
        method!(TesseractConfig::table_min_confidence, 0),
    )?;

    class.define_method(
        "table_column_threshold",
        method!(TesseractConfig::table_column_threshold, 0),
    )?;

    class.define_method(
        "table_row_threshold_ratio",
        method!(TesseractConfig::table_row_threshold_ratio, 0),
    )?;

    class.define_method("use_cache", method!(TesseractConfig::use_cache, 0))?;

    class.define_method(
        "classify_use_pre_adapted_templates",
        method!(TesseractConfig::classify_use_pre_adapted_templates, 0),
    )?;

    class.define_method(
        "language_model_ngram_on",
        method!(TesseractConfig::language_model_ngram_on, 0),
    )?;

    class.define_method(
        "tessedit_dont_blkrej_good_wds",
        method!(TesseractConfig::tessedit_dont_blkrej_good_wds, 0),
    )?;

    class.define_method(
        "tessedit_dont_rowrej_good_wds",
        method!(TesseractConfig::tessedit_dont_rowrej_good_wds, 0),
    )?;

    class.define_method(
        "tessedit_enable_dict_correction",
        method!(TesseractConfig::tessedit_enable_dict_correction, 0),
    )?;

    class.define_method(
        "tessedit_char_whitelist",
        method!(TesseractConfig::tessedit_char_whitelist, 0),
    )?;

    class.define_method(
        "tessedit_char_blacklist",
        method!(TesseractConfig::tessedit_char_blacklist, 0),
    )?;

    class.define_method(
        "tessedit_use_primary_params_model",
        method!(TesseractConfig::tessedit_use_primary_params_model, 0),
    )?;

    class.define_method(
        "textord_space_size_is_variable",
        method!(TesseractConfig::textord_space_size_is_variable, 0),
    )?;

    class.define_method("thresholding_method", method!(TesseractConfig::thresholding_method, 0))?;

    let class = module.define_class("ImagePreprocessingMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ImagePreprocessingMetadata::new, -1))?;

    class.define_method(
        "original_dimensions",
        method!(ImagePreprocessingMetadata::original_dimensions, 0),
    )?;

    class.define_method("original_dpi", method!(ImagePreprocessingMetadata::original_dpi, 0))?;

    class.define_method("target_dpi", method!(ImagePreprocessingMetadata::target_dpi, 0))?;

    class.define_method("scale_factor", method!(ImagePreprocessingMetadata::scale_factor, 0))?;

    class.define_method("auto_adjusted", method!(ImagePreprocessingMetadata::auto_adjusted, 0))?;

    class.define_method("final_dpi", method!(ImagePreprocessingMetadata::final_dpi, 0))?;

    class.define_method("new_dimensions", method!(ImagePreprocessingMetadata::new_dimensions, 0))?;

    class.define_method(
        "resample_method",
        method!(ImagePreprocessingMetadata::resample_method, 0),
    )?;

    class.define_method(
        "dimension_clamped",
        method!(ImagePreprocessingMetadata::dimension_clamped, 0),
    )?;

    class.define_method("calculated_dpi", method!(ImagePreprocessingMetadata::calculated_dpi, 0))?;

    class.define_method("skipped_resize", method!(ImagePreprocessingMetadata::skipped_resize, 0))?;

    class.define_method("resize_error", method!(ImagePreprocessingMetadata::resize_error, 0))?;

    let class = module.define_class("Metadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(Metadata::new, -1))?;

    class.define_method("title", method!(Metadata::title, 0))?;

    class.define_method("subject", method!(Metadata::subject, 0))?;

    class.define_method("authors", method!(Metadata::authors, 0))?;

    class.define_method("keywords", method!(Metadata::keywords, 0))?;

    class.define_method("language", method!(Metadata::language, 0))?;

    class.define_method("created_at", method!(Metadata::created_at, 0))?;

    class.define_method("modified_at", method!(Metadata::modified_at, 0))?;

    class.define_method("created_by", method!(Metadata::created_by, 0))?;

    class.define_method("modified_by", method!(Metadata::modified_by, 0))?;

    class.define_method("pages", method!(Metadata::pages, 0))?;

    class.define_method("format", method!(Metadata::format, 0))?;

    class.define_method("image_preprocessing", method!(Metadata::image_preprocessing, 0))?;

    class.define_method("json_schema", method!(Metadata::json_schema, 0))?;

    class.define_method("error", method!(Metadata::error, 0))?;

    class.define_method("extraction_duration_ms", method!(Metadata::extraction_duration_ms, 0))?;

    class.define_method("category", method!(Metadata::category, 0))?;

    class.define_method("tags", method!(Metadata::tags, 0))?;

    class.define_method("document_version", method!(Metadata::document_version, 0))?;

    class.define_method("abstract_text", method!(Metadata::abstract_text, 0))?;

    class.define_method("output_format", method!(Metadata::output_format, 0))?;

    class.define_method("ocr_used", method!(Metadata::ocr_used, 0))?;

    class.define_method("additional", method!(Metadata::additional, 0))?;

    class.define_method("is_empty", method!(Metadata::is_empty, 0))?;

    let class = module.define_class("ExcelMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExcelMetadata::new, -1))?;

    class.define_method("sheet_count", method!(ExcelMetadata::sheet_count, 0))?;

    class.define_method("sheet_names", method!(ExcelMetadata::sheet_names, 0))?;

    let class = module.define_class("EmailMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmailMetadata::new, -1))?;

    class.define_method("from_email", method!(EmailMetadata::from_email, 0))?;

    class.define_method("from_name", method!(EmailMetadata::from_name, 0))?;

    class.define_method("to_emails", method!(EmailMetadata::to_emails, 0))?;

    class.define_method("cc_emails", method!(EmailMetadata::cc_emails, 0))?;

    class.define_method("bcc_emails", method!(EmailMetadata::bcc_emails, 0))?;

    class.define_method("message_id", method!(EmailMetadata::message_id, 0))?;

    class.define_method("attachments", method!(EmailMetadata::attachments, 0))?;

    let class = module.define_class("ArchiveMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ArchiveMetadata::new, -1))?;

    class.define_method("format", method!(ArchiveMetadata::format, 0))?;

    class.define_method("file_count", method!(ArchiveMetadata::file_count, 0))?;

    class.define_method("file_list", method!(ArchiveMetadata::file_list, 0))?;

    class.define_method("total_size", method!(ArchiveMetadata::total_size, 0))?;

    class.define_method("compressed_size", method!(ArchiveMetadata::compressed_size, 0))?;

    let class = module.define_class("ImageMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ImageMetadata::new, -1))?;

    class.define_method("width", method!(ImageMetadata::width, 0))?;

    class.define_method("height", method!(ImageMetadata::height, 0))?;

    class.define_method("format", method!(ImageMetadata::format, 0))?;

    class.define_method("exif", method!(ImageMetadata::exif, 0))?;

    let class = module.define_class("XmlMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(XmlMetadata::new, -1))?;

    class.define_method("element_count", method!(XmlMetadata::element_count, 0))?;

    class.define_method("unique_elements", method!(XmlMetadata::unique_elements, 0))?;

    let class = module.define_class("TextMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(TextMetadata::new, -1))?;

    class.define_method("line_count", method!(TextMetadata::line_count, 0))?;

    class.define_method("word_count", method!(TextMetadata::word_count, 0))?;

    class.define_method("character_count", method!(TextMetadata::character_count, 0))?;

    class.define_method("headers", method!(TextMetadata::headers, 0))?;

    class.define_method("links", method!(TextMetadata::links, 0))?;

    class.define_method("code_blocks", method!(TextMetadata::code_blocks, 0))?;

    let class = module.define_class("HeaderMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(HeaderMetadata::new, -1))?;

    class.define_method("level", method!(HeaderMetadata::level, 0))?;

    class.define_method("text", method!(HeaderMetadata::text, 0))?;

    class.define_method("id", method!(HeaderMetadata::id, 0))?;

    class.define_method("depth", method!(HeaderMetadata::depth, 0))?;

    class.define_method("html_offset", method!(HeaderMetadata::html_offset, 0))?;

    let class = module.define_class("LinkMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(LinkMetadata::new, -1))?;

    class.define_method("href", method!(LinkMetadata::href, 0))?;

    class.define_method("text", method!(LinkMetadata::text, 0))?;

    class.define_method("title", method!(LinkMetadata::title, 0))?;

    class.define_method("link_type", method!(LinkMetadata::link_type, 0))?;

    class.define_method("rel", method!(LinkMetadata::rel, 0))?;

    class.define_method("attributes", method!(LinkMetadata::attributes, 0))?;

    let class = module.define_class("ImageMetadataType", ruby.class_object())?;

    class.define_singleton_method("new", function!(ImageMetadataType::new, -1))?;

    class.define_method("src", method!(ImageMetadataType::src, 0))?;

    class.define_method("alt", method!(ImageMetadataType::alt, 0))?;

    class.define_method("title", method!(ImageMetadataType::title, 0))?;

    class.define_method("dimensions", method!(ImageMetadataType::dimensions, 0))?;

    class.define_method("image_type", method!(ImageMetadataType::image_type, 0))?;

    class.define_method("attributes", method!(ImageMetadataType::attributes, 0))?;

    let class = module.define_class("StructuredData", ruby.class_object())?;

    class.define_singleton_method("new", function!(StructuredData::new, -1))?;

    class.define_method("data_type", method!(StructuredData::data_type, 0))?;

    class.define_method("raw_json", method!(StructuredData::raw_json, 0))?;

    class.define_method("schema_type", method!(StructuredData::schema_type, 0))?;

    let class = module.define_class("HtmlMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(HtmlMetadata::new, -1))?;

    class.define_method("title", method!(HtmlMetadata::title, 0))?;

    class.define_method("description", method!(HtmlMetadata::description, 0))?;

    class.define_method("keywords", method!(HtmlMetadata::keywords, 0))?;

    class.define_method("author", method!(HtmlMetadata::author, 0))?;

    class.define_method("canonical_url", method!(HtmlMetadata::canonical_url, 0))?;

    class.define_method("base_href", method!(HtmlMetadata::base_href, 0))?;

    class.define_method("language", method!(HtmlMetadata::language, 0))?;

    class.define_method("text_direction", method!(HtmlMetadata::text_direction, 0))?;

    class.define_method("open_graph", method!(HtmlMetadata::open_graph, 0))?;

    class.define_method("twitter_card", method!(HtmlMetadata::twitter_card, 0))?;

    class.define_method("meta_tags", method!(HtmlMetadata::meta_tags, 0))?;

    class.define_method("headers", method!(HtmlMetadata::headers, 0))?;

    class.define_method("links", method!(HtmlMetadata::links, 0))?;

    class.define_method("images", method!(HtmlMetadata::images, 0))?;

    class.define_method("structured_data", method!(HtmlMetadata::structured_data, 0))?;

    let class = module.define_class("OcrMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrMetadata::new, -1))?;

    class.define_method("language", method!(OcrMetadata::language, 0))?;

    class.define_method("psm", method!(OcrMetadata::psm, 0))?;

    class.define_method("output_format", method!(OcrMetadata::output_format, 0))?;

    class.define_method("table_count", method!(OcrMetadata::table_count, 0))?;

    class.define_method("table_rows", method!(OcrMetadata::table_rows, 0))?;

    class.define_method("table_cols", method!(OcrMetadata::table_cols, 0))?;

    let class = module.define_class("ErrorMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(ErrorMetadata::new, -1))?;

    class.define_method("error_type", method!(ErrorMetadata::error_type, 0))?;

    class.define_method("message", method!(ErrorMetadata::message, 0))?;

    let class = module.define_class("PptxMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(PptxMetadata::new, -1))?;

    class.define_method("slide_count", method!(PptxMetadata::slide_count, 0))?;

    class.define_method("slide_names", method!(PptxMetadata::slide_names, 0))?;

    class.define_method("image_count", method!(PptxMetadata::image_count, 0))?;

    class.define_method("table_count", method!(PptxMetadata::table_count, 0))?;

    let class = module.define_class("DocxMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(DocxMetadata::new, -1))?;

    class.define_method("core_properties", method!(DocxMetadata::core_properties, 0))?;

    class.define_method("app_properties", method!(DocxMetadata::app_properties, 0))?;

    class.define_method("custom_properties", method!(DocxMetadata::custom_properties, 0))?;

    let class = module.define_class("CsvMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(CsvMetadata::new, -1))?;

    class.define_method("row_count", method!(CsvMetadata::row_count, 0))?;

    class.define_method("column_count", method!(CsvMetadata::column_count, 0))?;

    class.define_method("delimiter", method!(CsvMetadata::delimiter, 0))?;

    class.define_method("has_header", method!(CsvMetadata::has_header, 0))?;

    class.define_method("column_types", method!(CsvMetadata::column_types, 0))?;

    let class = module.define_class("BibtexMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(BibtexMetadata::new, -1))?;

    class.define_method("entry_count", method!(BibtexMetadata::entry_count, 0))?;

    class.define_method("citation_keys", method!(BibtexMetadata::citation_keys, 0))?;

    class.define_method("authors", method!(BibtexMetadata::authors, 0))?;

    class.define_method("year_range", method!(BibtexMetadata::year_range, 0))?;

    class.define_method("entry_types", method!(BibtexMetadata::entry_types, 0))?;

    let class = module.define_class("CitationMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(CitationMetadata::new, -1))?;

    class.define_method("citation_count", method!(CitationMetadata::citation_count, 0))?;

    class.define_method("format", method!(CitationMetadata::format, 0))?;

    class.define_method("authors", method!(CitationMetadata::authors, 0))?;

    class.define_method("year_range", method!(CitationMetadata::year_range, 0))?;

    class.define_method("dois", method!(CitationMetadata::dois, 0))?;

    class.define_method("keywords", method!(CitationMetadata::keywords, 0))?;

    let class = module.define_class("YearRange", ruby.class_object())?;

    class.define_singleton_method("new", function!(YearRange::new, -1))?;

    class.define_method("min", method!(YearRange::min, 0))?;

    class.define_method("max", method!(YearRange::max, 0))?;

    class.define_method("years", method!(YearRange::years, 0))?;

    let class = module.define_class("FictionBookMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(FictionBookMetadata::new, -1))?;

    class.define_method("genres", method!(FictionBookMetadata::genres, 0))?;

    class.define_method("sequences", method!(FictionBookMetadata::sequences, 0))?;

    class.define_method("annotation", method!(FictionBookMetadata::annotation, 0))?;

    let class = module.define_class("DbfMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(DbfMetadata::new, -1))?;

    class.define_method("record_count", method!(DbfMetadata::record_count, 0))?;

    class.define_method("field_count", method!(DbfMetadata::field_count, 0))?;

    class.define_method("fields", method!(DbfMetadata::fields, 0))?;

    let class = module.define_class("DbfFieldInfo", ruby.class_object())?;

    class.define_singleton_method("new", function!(DbfFieldInfo::new, -1))?;

    class.define_method("name", method!(DbfFieldInfo::name, 0))?;

    class.define_method("field_type", method!(DbfFieldInfo::field_type, 0))?;

    let class = module.define_class("JatsMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(JatsMetadata::new, -1))?;

    class.define_method("copyright", method!(JatsMetadata::copyright, 0))?;

    class.define_method("license", method!(JatsMetadata::license, 0))?;

    class.define_method("history_dates", method!(JatsMetadata::history_dates, 0))?;

    class.define_method("contributor_roles", method!(JatsMetadata::contributor_roles, 0))?;

    let class = module.define_class("ContributorRole", ruby.class_object())?;

    class.define_singleton_method("new", function!(ContributorRole::new, -1))?;

    class.define_method("name", method!(ContributorRole::name, 0))?;

    class.define_method("role", method!(ContributorRole::role, 0))?;

    let class = module.define_class("EpubMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(EpubMetadata::new, -1))?;

    class.define_method("coverage", method!(EpubMetadata::coverage, 0))?;

    class.define_method("dc_format", method!(EpubMetadata::dc_format, 0))?;

    class.define_method("relation", method!(EpubMetadata::relation, 0))?;

    class.define_method("source", method!(EpubMetadata::source, 0))?;

    class.define_method("dc_type", method!(EpubMetadata::dc_type, 0))?;

    class.define_method("cover_image", method!(EpubMetadata::cover_image, 0))?;

    let class = module.define_class("PstMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(PstMetadata::new, -1))?;

    class.define_method("message_count", method!(PstMetadata::message_count, 0))?;

    let class = module.define_class("OcrConfidence", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrConfidence::new, -1))?;

    class.define_method("detection", method!(OcrConfidence::detection, 0))?;

    class.define_method("recognition", method!(OcrConfidence::recognition, 0))?;

    let class = module.define_class("OcrRotation", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrRotation::new, -1))?;

    class.define_method("angle_degrees", method!(OcrRotation::angle_degrees, 0))?;

    class.define_method("confidence", method!(OcrRotation::confidence, 0))?;

    let class = module.define_class("OcrElement", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrElement::new, -1))?;

    class.define_method("text", method!(OcrElement::text, 0))?;

    class.define_method("geometry", method!(OcrElement::geometry, 0))?;

    class.define_method("confidence", method!(OcrElement::confidence, 0))?;

    class.define_method("level", method!(OcrElement::level, 0))?;

    class.define_method("rotation", method!(OcrElement::rotation, 0))?;

    class.define_method("page_number", method!(OcrElement::page_number, 0))?;

    class.define_method("parent_id", method!(OcrElement::parent_id, 0))?;

    class.define_method("backend_metadata", method!(OcrElement::backend_metadata, 0))?;

    let class = module.define_class("OcrElementConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(OcrElementConfig::new, -1))?;

    class.define_method("include_elements", method!(OcrElementConfig::include_elements, 0))?;

    class.define_method("min_level", method!(OcrElementConfig::min_level, 0))?;

    class.define_method("min_confidence", method!(OcrElementConfig::min_confidence, 0))?;

    class.define_method("build_hierarchy", method!(OcrElementConfig::build_hierarchy, 0))?;

    let class = module.define_class("PageStructure", ruby.class_object())?;

    class.define_singleton_method("new", function!(PageStructure::new, -1))?;

    class.define_method("total_count", method!(PageStructure::total_count, 0))?;

    class.define_method("unit_type", method!(PageStructure::unit_type, 0))?;

    class.define_method("boundaries", method!(PageStructure::boundaries, 0))?;

    class.define_method("pages", method!(PageStructure::pages, 0))?;

    let class = module.define_class("PageBoundary", ruby.class_object())?;

    class.define_singleton_method("new", function!(PageBoundary::new, -1))?;

    class.define_method("byte_start", method!(PageBoundary::byte_start, 0))?;

    class.define_method("byte_end", method!(PageBoundary::byte_end, 0))?;

    class.define_method("page_number", method!(PageBoundary::page_number, 0))?;

    let class = module.define_class("PageInfo", ruby.class_object())?;

    class.define_singleton_method("new", function!(PageInfo::new, -1))?;

    class.define_method("number", method!(PageInfo::number, 0))?;

    class.define_method("title", method!(PageInfo::title, 0))?;

    class.define_method("dimensions", method!(PageInfo::dimensions, 0))?;

    class.define_method("image_count", method!(PageInfo::image_count, 0))?;

    class.define_method("table_count", method!(PageInfo::table_count, 0))?;

    class.define_method("hidden", method!(PageInfo::hidden, 0))?;

    class.define_method("is_blank", method!(PageInfo::is_blank, 0))?;

    class.define_method("has_vector_graphics", method!(PageInfo::has_vector_graphics, 0))?;

    let class = module.define_class("PageContent", ruby.class_object())?;

    class.define_singleton_method("new", function!(PageContent::new, -1))?;

    class.define_method("page_number", method!(PageContent::page_number, 0))?;

    class.define_method("content", method!(PageContent::content, 0))?;

    class.define_method("tables", method!(PageContent::tables, 0))?;

    class.define_method("image_indices", method!(PageContent::image_indices, 0))?;

    class.define_method("hierarchy", method!(PageContent::hierarchy, 0))?;

    class.define_method("is_blank", method!(PageContent::is_blank, 0))?;

    class.define_method("layout_regions", method!(PageContent::layout_regions, 0))?;

    class.define_method("speaker_notes", method!(PageContent::speaker_notes, 0))?;

    class.define_method("section_name", method!(PageContent::section_name, 0))?;

    class.define_method("sheet_name", method!(PageContent::sheet_name, 0))?;

    class.define_method("to_s", method!(PageContent::to_s, 0))?;

    let class = module.define_class("LayoutRegion", ruby.class_object())?;

    class.define_singleton_method("new", function!(LayoutRegion::new, -1))?;

    class.define_method("class_name", method!(LayoutRegion::class_name, 0))?;

    class.define_method("confidence", method!(LayoutRegion::confidence, 0))?;

    class.define_method("bounding_box", method!(LayoutRegion::bounding_box, 0))?;

    class.define_method("area_fraction", method!(LayoutRegion::area_fraction, 0))?;

    let class = module.define_class("PageHierarchy", ruby.class_object())?;

    class.define_singleton_method("new", function!(PageHierarchy::new, -1))?;

    class.define_method("block_count", method!(PageHierarchy::block_count, 0))?;

    class.define_method("blocks", method!(PageHierarchy::blocks, 0))?;

    let class = module.define_class("HierarchicalBlock", ruby.class_object())?;

    class.define_singleton_method("new", function!(HierarchicalBlock::new, -1))?;

    class.define_method("text", method!(HierarchicalBlock::text, 0))?;

    class.define_method("font_size", method!(HierarchicalBlock::font_size, 0))?;

    class.define_method("level", method!(HierarchicalBlock::level, 0))?;

    class.define_method("bbox", method!(HierarchicalBlock::bbox, 0))?;

    let class = module.define_class("CellChange", ruby.class_object())?;

    class.define_singleton_method("new", function!(CellChange::new, -1))?;

    class.define_method("row", method!(CellChange::row, 0))?;

    class.define_method("col", method!(CellChange::col, 0))?;

    class.define_method("from", method!(CellChange::from, 0))?;

    class.define_method("to", method!(CellChange::to, 0))?;

    let class = module.define_class("DocumentRevision", ruby.class_object())?;

    class.define_singleton_method("new", function!(DocumentRevision::new, -1))?;

    class.define_method("revision_id", method!(DocumentRevision::revision_id, 0))?;

    class.define_method("author", method!(DocumentRevision::author, 0))?;

    class.define_method("timestamp", method!(DocumentRevision::timestamp, 0))?;

    class.define_method("kind", method!(DocumentRevision::kind, 0))?;

    class.define_method("anchor", method!(DocumentRevision::anchor, 0))?;

    class.define_method("delta", method!(DocumentRevision::delta, 0))?;

    let class = module.define_class("RevisionDelta", ruby.class_object())?;

    class.define_singleton_method("new", function!(RevisionDelta::new, -1))?;

    class.define_method("content", method!(RevisionDelta::content, 0))?;

    class.define_method("table_changes", method!(RevisionDelta::table_changes, 0))?;

    let class = module.define_class("Table", ruby.class_object())?;

    class.define_singleton_method("new", function!(Table::new, -1))?;

    class.define_method("cells", method!(Table::cells, 0))?;

    class.define_method("markdown", method!(Table::markdown, 0))?;

    class.define_method("page_number", method!(Table::page_number, 0))?;

    class.define_method("bounding_box", method!(Table::bounding_box, 0))?;

    let class = module.define_class("TableCell", ruby.class_object())?;

    class.define_singleton_method("new", function!(TableCell::new, -1))?;

    class.define_method("content", method!(TableCell::content, 0))?;

    class.define_method("row_span", method!(TableCell::row_span, 0))?;

    class.define_method("col_span", method!(TableCell::col_span, 0))?;

    class.define_method("is_header", method!(TableCell::is_header, 0))?;

    class.define_method("to_s", method!(TableCell::to_s, 0))?;

    let class = module.define_class("ExtractedUri", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExtractedUri::new, -1))?;

    class.define_method("url", method!(ExtractedUri::url, 0))?;

    class.define_method("label", method!(ExtractedUri::label, 0))?;

    class.define_method("page", method!(ExtractedUri::page, 0))?;

    class.define_method("kind", method!(ExtractedUri::kind, 0))?;

    let class = module.define_class("DetectResponse", ruby.class_object())?;

    class.define_singleton_method("new", function!(DetectResponse::new, -1))?;

    class.define_method("mime_type", method!(DetectResponse::mime_type, 0))?;

    class.define_method("filename", method!(DetectResponse::filename, 0))?;

    let class = module.define_class("DiffOptions", ruby.class_object())?;

    class.define_singleton_method("new", function!(DiffOptions::new, -1))?;

    class.define_method("include_metadata", method!(DiffOptions::include_metadata, 0))?;

    class.define_method("include_embedded", method!(DiffOptions::include_embedded, 0))?;

    class.define_method("max_content_chars", method!(DiffOptions::max_content_chars, 0))?;

    let class = module.define_class("ExtractionDiff", ruby.class_object())?;

    class.define_singleton_method("new", function!(ExtractionDiff::new, -1))?;

    class.define_method("content_diff", method!(ExtractionDiff::content_diff, 0))?;

    class.define_method("tables_added", method!(ExtractionDiff::tables_added, 0))?;

    class.define_method("tables_removed", method!(ExtractionDiff::tables_removed, 0))?;

    class.define_method("tables_changed", method!(ExtractionDiff::tables_changed, 0))?;

    class.define_method("metadata_changed", method!(ExtractionDiff::metadata_changed, 0))?;

    class.define_method("embedded_changes", method!(ExtractionDiff::embedded_changes, 0))?;

    let class = module.define_class("DiffHunk", ruby.class_object())?;

    class.define_singleton_method("new", function!(DiffHunk::new, -1))?;

    class.define_method("from_line", method!(DiffHunk::from_line, 0))?;

    class.define_method("from_count", method!(DiffHunk::from_count, 0))?;

    class.define_method("to_line", method!(DiffHunk::to_line, 0))?;

    class.define_method("to_count", method!(DiffHunk::to_count, 0))?;

    class.define_method("lines", method!(DiffHunk::lines, 0))?;

    let class = module.define_class("TableDiff", ruby.class_object())?;

    class.define_singleton_method("new", function!(TableDiff::new, -1))?;

    class.define_method("from_index", method!(TableDiff::from_index, 0))?;

    class.define_method("to_index", method!(TableDiff::to_index, 0))?;

    class.define_method("cell_changes", method!(TableDiff::cell_changes, 0))?;

    let class = module.define_class("EmbeddedChanges", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmbeddedChanges::new, -1))?;

    class.define_method("added", method!(EmbeddedChanges::added, 0))?;

    class.define_method("removed", method!(EmbeddedChanges::removed, 0))?;

    class.define_method("changed", method!(EmbeddedChanges::changed, 0))?;

    let class = module.define_class("EmbeddedDiff", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmbeddedDiff::new, -1))?;

    class.define_method("path", method!(EmbeddedDiff::path, 0))?;

    class.define_method("diff", method!(EmbeddedDiff::diff, 0))?;

    let class = module.define_class("EmbeddingPreset", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmbeddingPreset::new, -1))?;

    class.define_method("name", method!(EmbeddingPreset::name, 0))?;

    class.define_method("chunk_size", method!(EmbeddingPreset::chunk_size, 0))?;

    class.define_method("overlap", method!(EmbeddingPreset::overlap, 0))?;

    class.define_method("model_repo", method!(EmbeddingPreset::model_repo, 0))?;

    class.define_method("pooling", method!(EmbeddingPreset::pooling, 0))?;

    class.define_method("model_file", method!(EmbeddingPreset::model_file, 0))?;

    class.define_method("dimensions", method!(EmbeddingPreset::dimensions, 0))?;

    class.define_method("description", method!(EmbeddingPreset::description, 0))?;

    let class = module.define_class("YakeParams", ruby.class_object())?;

    class.define_singleton_method("new", function!(YakeParams::new, -1))?;

    class.define_method("window_size", method!(YakeParams::window_size, 0))?;

    let class = module.define_class("RakeParams", ruby.class_object())?;

    class.define_singleton_method("new", function!(RakeParams::new, -1))?;

    class.define_method("min_word_length", method!(RakeParams::min_word_length, 0))?;

    class.define_method("max_words_per_phrase", method!(RakeParams::max_words_per_phrase, 0))?;

    let class = module.define_class("KeywordConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(KeywordConfig::new, -1))?;

    class.define_method("algorithm", method!(KeywordConfig::algorithm, 0))?;

    class.define_method("max_keywords", method!(KeywordConfig::max_keywords, 0))?;

    class.define_method("min_score", method!(KeywordConfig::min_score, 0))?;

    class.define_method("ngram_range", method!(KeywordConfig::ngram_range, 0))?;

    class.define_method("language", method!(KeywordConfig::language, 0))?;

    class.define_method("yake_params", method!(KeywordConfig::yake_params, 0))?;

    class.define_method("rake_params", method!(KeywordConfig::rake_params, 0))?;

    let class = module.define_class("Keyword", ruby.class_object())?;

    class.define_singleton_method("new", function!(Keyword::new, -1))?;

    class.define_method("text", method!(Keyword::text, 0))?;

    class.define_method("score", method!(Keyword::score, 0))?;

    class.define_method("algorithm", method!(Keyword::algorithm, 0))?;

    class.define_method("positions", method!(Keyword::positions, 0))?;

    let class = module.define_class("PaddleOcrConfig", ruby.class_object())?;

    class.define_singleton_method("new", function!(PaddleOcrConfig::new, -1))?;

    class.define_method("language", method!(PaddleOcrConfig::language, 0))?;

    class.define_method("cache_dir", method!(PaddleOcrConfig::cache_dir, 0))?;

    class.define_method("use_angle_cls", method!(PaddleOcrConfig::use_angle_cls, 0))?;

    class.define_method(
        "enable_table_detection",
        method!(PaddleOcrConfig::enable_table_detection, 0),
    )?;

    class.define_method("det_db_thresh", method!(PaddleOcrConfig::det_db_thresh, 0))?;

    class.define_method("det_db_box_thresh", method!(PaddleOcrConfig::det_db_box_thresh, 0))?;

    class.define_method("det_db_unclip_ratio", method!(PaddleOcrConfig::det_db_unclip_ratio, 0))?;

    class.define_method("det_limit_side_len", method!(PaddleOcrConfig::det_limit_side_len, 0))?;

    class.define_method("rec_batch_num", method!(PaddleOcrConfig::rec_batch_num, 0))?;

    class.define_method("padding", method!(PaddleOcrConfig::padding, 0))?;

    class.define_method("drop_score", method!(PaddleOcrConfig::drop_score, 0))?;

    class.define_method("model_tier", method!(PaddleOcrConfig::model_tier, 0))?;

    class.define_method("with_cache_dir", method!(PaddleOcrConfig::with_cache_dir, 1))?;

    class.define_method(
        "with_table_detection",
        method!(PaddleOcrConfig::with_table_detection, 1),
    )?;

    class.define_method("with_angle_cls", method!(PaddleOcrConfig::with_angle_cls, 1))?;

    class.define_method("with_det_db_thresh", method!(PaddleOcrConfig::with_det_db_thresh, 1))?;

    class.define_method(
        "with_det_db_box_thresh",
        method!(PaddleOcrConfig::with_det_db_box_thresh, 1),
    )?;

    class.define_method(
        "with_det_db_unclip_ratio",
        method!(PaddleOcrConfig::with_det_db_unclip_ratio, 1),
    )?;

    class.define_method(
        "with_det_limit_side_len",
        method!(PaddleOcrConfig::with_det_limit_side_len, 1),
    )?;

    class.define_method("with_rec_batch_num", method!(PaddleOcrConfig::with_rec_batch_num, 1))?;

    class.define_method("with_drop_score", method!(PaddleOcrConfig::with_drop_score, 1))?;

    class.define_method("with_padding", method!(PaddleOcrConfig::with_padding, 1))?;

    class.define_method("with_model_tier", method!(PaddleOcrConfig::with_model_tier, 1))?;

    let class = module.define_class("ModelPaths", ruby.class_object())?;

    class.define_singleton_method("new", function!(ModelPaths::new, -1))?;

    class.define_method("det_model", method!(ModelPaths::det_model, 0))?;

    class.define_method("cls_model", method!(ModelPaths::cls_model, 0))?;

    class.define_method("rec_model", method!(ModelPaths::rec_model, 0))?;

    class.define_method("dict_file", method!(ModelPaths::dict_file, 0))?;

    let class = module.define_class("OrientationResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(OrientationResult::new, -1))?;

    class.define_method("degrees", method!(OrientationResult::degrees, 0))?;

    class.define_method("confidence", method!(OrientationResult::confidence, 0))?;

    let class = module.define_class("BBox", ruby.class_object())?;

    class.define_singleton_method("new", function!(BBox::new, -1))?;

    class.define_method("x1", method!(BBox::x1, 0))?;

    class.define_method("y1", method!(BBox::y1, 0))?;

    class.define_method("x2", method!(BBox::x2, 0))?;

    class.define_method("y2", method!(BBox::y2, 0))?;

    let class = module.define_class("LayoutDetection", ruby.class_object())?;

    class.define_singleton_method("new", function!(LayoutDetection::new, -1))?;

    class.define_method("class_name", method!(LayoutDetection::class_name, 0))?;

    class.define_method("confidence", method!(LayoutDetection::confidence, 0))?;

    class.define_method("bbox", method!(LayoutDetection::bbox, 0))?;

    let class = module.define_class("RecognizedTable", ruby.class_object())?;

    class.define_singleton_method("new", function!(RecognizedTable::new, -1))?;

    class.define_method("detection_bbox", method!(RecognizedTable::detection_bbox, 0))?;

    class.define_method("cells", method!(RecognizedTable::cells, 0))?;

    class.define_method("markdown", method!(RecognizedTable::markdown, 0))?;

    let class = module.define_class("DetectionResult", ruby.class_object())?;

    class.define_singleton_method("new", function!(DetectionResult::new, -1))?;

    class.define_method("page_width", method!(DetectionResult::page_width, 0))?;

    class.define_method("page_height", method!(DetectionResult::page_height, 0))?;

    class.define_method("detections", method!(DetectionResult::detections, 0))?;

    let class = module.define_class("EmbeddedFile", ruby.class_object())?;

    class.define_singleton_method("new", function!(EmbeddedFile::new, -1))?;

    class.define_method("name", method!(EmbeddedFile::name, 0))?;

    class.define_method("data", method!(EmbeddedFile::data, 0))?;

    class.define_method("compressed_size", method!(EmbeddedFile::compressed_size, 0))?;

    class.define_method("mime_type", method!(EmbeddedFile::mime_type, 0))?;

    let class = module.define_class("PdfMetadata", ruby.class_object())?;

    class.define_singleton_method("new", function!(PdfMetadata::new, -1))?;

    class.define_method("pdf_version", method!(PdfMetadata::pdf_version, 0))?;

    class.define_method("producer", method!(PdfMetadata::producer, 0))?;

    class.define_method("is_encrypted", method!(PdfMetadata::is_encrypted, 0))?;

    class.define_method("width", method!(PdfMetadata::width, 0))?;

    class.define_method("height", method!(PdfMetadata::height, 0))?;

    class.define_method("page_count", method!(PdfMetadata::page_count, 0))?;

    module.define_module_function("extract_bytes", function!(extract_bytes, -1))?;

    module.define_module_function("extract_bytes_async", function!(extract_bytes_async, -1))?;

    module.define_module_function("extract_file", function!(extract_file, -1))?;

    module.define_module_function("extract_file_async", function!(extract_file_async, -1))?;

    module.define_module_function("extract_file_sync", function!(extract_file_sync, -1))?;

    module.define_module_function("extract_bytes_sync", function!(extract_bytes_sync, -1))?;

    module.define_module_function("batch_extract_files_sync", function!(batch_extract_files_sync, -1))?;

    module.define_module_function("batch_extract_bytes_sync", function!(batch_extract_bytes_sync, -1))?;

    module.define_module_function("batch_extract_files", function!(batch_extract_files, -1))?;

    module.define_module_function("batch_extract_files_async", function!(batch_extract_files_async, -1))?;

    module.define_module_function("batch_extract_bytes", function!(batch_extract_bytes, -1))?;

    module.define_module_function("batch_extract_bytes_async", function!(batch_extract_bytes_async, -1))?;

    module.define_module_function("detect_mime_type_from_bytes", function!(detect_mime_type_from_bytes, 1))?;

    module.define_module_function("get_extensions_for_mime", function!(get_extensions_for_mime, 1))?;

    module.define_module_function("list_embedding_backends", function!(list_embedding_backends, 0))?;

    module.define_module_function("list_document_extractors", function!(list_document_extractors, 0))?;

    module.define_module_function("list_ocr_backends", function!(list_ocr_backends, 0))?;

    module.define_module_function("list_post_processors", function!(list_post_processors, 0))?;

    module.define_module_function("list_renderers", function!(list_renderers, 0))?;

    module.define_module_function("list_validators", function!(list_validators, 0))?;

    module.define_module_function("compare", function!(compare, -1))?;

    module.define_module_function("embed_texts_async", function!(embed_texts_async, -1))?;

    module.define_module_function("embed_texts_async_async", function!(embed_texts_async_async, -1))?;

    module.define_module_function("render_pdf_page_to_png", function!(render_pdf_page_to_png, -1))?;

    module.define_module_function("detect_mime_type", function!(detect_mime_type, 2))?;

    module.define_module_function("embed_texts", function!(embed_texts, -1))?;

    module.define_module_function("get_embedding_preset", function!(get_embedding_preset, 1))?;

    module.define_module_function("list_embedding_presets", function!(list_embedding_presets, 0))?;

    module.define_module_function("register_ocr_backend", function!(register_ocr_backend, 2))?;

    module.define_module_function("unregister_ocr_backend", function!(unregister_ocr_backend, 1))?;

    module.define_module_function("clear_ocr_backends", function!(clear_ocr_backends, 0))?;

    module.define_module_function("register_post_processor", function!(register_post_processor, 2))?;

    module.define_module_function("unregister_post_processor", function!(unregister_post_processor, 1))?;

    module.define_module_function("clear_post_processors", function!(clear_post_processors, 0))?;

    module.define_module_function("register_validator", function!(register_validator, 2))?;

    module.define_module_function("unregister_validator", function!(unregister_validator, 1))?;

    module.define_module_function("clear_validators", function!(clear_validators, 0))?;

    module.define_module_function("register_embedding_backend", function!(register_embedding_backend, 2))?;

    module.define_module_function(
        "unregister_embedding_backend",
        function!(unregister_embedding_backend, 1),
    )?;

    module.define_module_function("clear_embedding_backends", function!(clear_embedding_backends, 0))?;

    module.define_module_function("register_document_extractor", function!(register_document_extractor, 2))?;

    module.define_module_function(
        "unregister_document_extractor",
        function!(unregister_document_extractor, 1),
    )?;

    module.define_module_function("clear_document_extractors", function!(clear_document_extractors, 0))?;

    module.define_module_function("register_renderer", function!(register_renderer, 2))?;

    module.define_module_function("unregister_renderer", function!(unregister_renderer, 1))?;

    module.define_module_function("clear_renderers", function!(clear_renderers, 0))?;

    Ok(())
}
