// 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, unused_mut, noop_method_call)]
#![allow(
    clippy::too_many_arguments,
    clippy::let_unit_value,
    clippy::needless_borrow,
    clippy::redundant_locals,
    dropping_references,
    clippy::unnecessary_cast,
    clippy::unused_unit,
    clippy::unwrap_or_default,
    clippy::derivable_impls,
    clippy::needless_borrows_for_generic_args,
    clippy::unnecessary_fallible_conversions,
    clippy::useless_conversion,
    clippy::type_complexity,
    clippy::clone_on_copy
)]
#![allow(
    clippy::missing_safety_doc,
    clippy::doc_lazy_continuation,
    clippy::doc_overindented_list_items
)]

use std::cell::RefCell;
use std::ffi::c_void;
use std::ffi::{c_char, CStr, CString};
use std::sync::Arc;

thread_local! {
    static LAST_ERROR_CODE: RefCell<i32> = const { RefCell::new(0) };
    static LAST_ERROR_CONTEXT: RefCell<Option<CString>> = const { RefCell::new(None) };
    static LAST_RETURN_LEN: RefCell<usize> = const { RefCell::new(0) };
}

fn set_last_error(code: i32, message: &str) {
    LAST_ERROR_CODE.with_borrow_mut(|c| *c = code);
    LAST_ERROR_CONTEXT.with_borrow_mut(|c| *c = CString::new(message).ok());
    LAST_RETURN_LEN.with_borrow_mut(|c| *c = 0);
}

fn clear_last_error() {
    LAST_ERROR_CODE.with_borrow_mut(|c| *c = 0);
    LAST_ERROR_CONTEXT.with_borrow_mut(|c| *c = None);
    LAST_RETURN_LEN.with_borrow_mut(|c| *c = 0);
}

fn set_last_return_len(len: usize) {
    LAST_RETURN_LEN.with_borrow_mut(|c| *c = len);
}

fn last_return_len() -> usize {
    LAST_RETURN_LEN.with_borrow(|c| *c)
}

/// Return the last error code (0 means no error).
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// This function does not allocate and returns no owned pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_last_error_code() -> i32 {
    LAST_ERROR_CODE.with_borrow(|c| *c)
}

/// Return the last error message. The pointer is borrowed and valid until the next FFI call on this thread.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// The returned pointer is borrowed from thread-local storage and must NOT be freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_last_error_context() -> *const c_char {
    LAST_ERROR_CONTEXT.with_borrow(|ctx| ctx.as_ref().map_or(std::ptr::null(), |c| c.as_ptr()))
}

/// Free a string previously returned by this library.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_free_string(ptr: *mut c_char) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by CString::into_raw; caller ensures no aliases.
        unsafe {
            drop(CString::from_raw(ptr));
        }
    }
}

/// Free a byte buffer previously returned by this library via out-params.
/// `ptr`, `len`, and `cap` must match the values written by the library function,
/// or the call must pass `ptr = null` (in which case it is a no-op).
/// # Safety
/// Pointer must have been returned by this library (via out_ptr / out_len / out_cap
/// out-params), or be null. The len and cap values must be unchanged since the call.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_free_bytes(ptr: *mut u8, len: usize, cap: usize) {
    if !ptr.is_null() {
        // SAFETY: ptr/len/cap were produced by Vec::into_raw_parts (or equivalent)
        // by this library; caller must not have mutated them.
        unsafe {
            drop(Vec::from_raw_parts(ptr, len, cap));
        }
    }
}

/// Return the library version string. The pointer is static and must NOT be freed.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_version() -> *const c_char {
    static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
    VERSION.as_ptr() as *const c_char
}

#[allow(dead_code)]
fn execution_provider_type_from_i32_rs(v: i32) -> Option<kreuzberg::ExecutionProviderType> {
    match v {
        0 => Some(kreuzberg::ExecutionProviderType::Auto),
        1 => Some(kreuzberg::ExecutionProviderType::Cpu),
        2 => Some(kreuzberg::ExecutionProviderType::CoreMl),
        3 => Some(kreuzberg::ExecutionProviderType::Cuda),
        4 => Some(kreuzberg::ExecutionProviderType::TensorRt),
        _ => None,
    }
}

#[allow(dead_code)]
fn html_theme_from_i32_rs(v: i32) -> Option<kreuzberg::HtmlTheme> {
    match v {
        0 => Some(kreuzberg::HtmlTheme::Default),
        1 => Some(kreuzberg::HtmlTheme::GitHub),
        2 => Some(kreuzberg::HtmlTheme::Dark),
        3 => Some(kreuzberg::HtmlTheme::Light),
        4 => Some(kreuzberg::HtmlTheme::Unstyled),
        _ => None,
    }
}

#[allow(dead_code)]
fn table_model_from_i32_rs(v: i32) -> Option<kreuzberg::TableModel> {
    match v {
        0 => Some(kreuzberg::TableModel::Tatr),
        1 => Some(kreuzberg::TableModel::SlanetWired),
        2 => Some(kreuzberg::TableModel::SlanetWireless),
        3 => Some(kreuzberg::TableModel::SlanetPlus),
        4 => Some(kreuzberg::TableModel::SlanetAuto),
        5 => Some(kreuzberg::TableModel::Disabled),
        _ => None,
    }
}

#[allow(dead_code)]
fn chunker_type_from_i32_rs(v: i32) -> Option<kreuzberg::ChunkerType> {
    match v {
        0 => Some(kreuzberg::ChunkerType::Text),
        1 => Some(kreuzberg::ChunkerType::Markdown),
        2 => Some(kreuzberg::ChunkerType::Yaml),
        3 => Some(kreuzberg::ChunkerType::Semantic),
        _ => None,
    }
}

#[allow(dead_code)]
fn code_content_mode_from_i32_rs(v: i32) -> Option<kreuzberg::CodeContentMode> {
    match v {
        0 => Some(kreuzberg::CodeContentMode::Chunks),
        1 => Some(kreuzberg::CodeContentMode::Raw),
        2 => Some(kreuzberg::CodeContentMode::Structure),
        _ => None,
    }
}

#[allow(dead_code)]
fn list_type_from_i32_rs(v: i32) -> Option<kreuzberg::extraction::transform::ListType> {
    match v {
        0 => Some(kreuzberg::extraction::transform::ListType::Bullet),
        1 => Some(kreuzberg::extraction::transform::ListType::Numbered),
        2 => Some(kreuzberg::extraction::transform::ListType::Lettered),
        3 => Some(kreuzberg::extraction::transform::ListType::Indented),
        _ => None,
    }
}

#[allow(dead_code)]
fn ocr_backend_type_from_i32_rs(v: i32) -> Option<kreuzberg::OcrBackendType> {
    match v {
        0 => Some(kreuzberg::OcrBackendType::Tesseract),
        1 => Some(kreuzberg::OcrBackendType::EasyOCR),
        2 => Some(kreuzberg::OcrBackendType::PaddleOCR),
        3 => Some(kreuzberg::OcrBackendType::Custom),
        _ => None,
    }
}

#[allow(dead_code)]
fn processing_stage_from_i32_rs(v: i32) -> Option<kreuzberg::ProcessingStage> {
    match v {
        0 => Some(kreuzberg::ProcessingStage::Early),
        1 => Some(kreuzberg::ProcessingStage::Middle),
        2 => Some(kreuzberg::ProcessingStage::Late),
        _ => None,
    }
}

#[allow(dead_code)]
fn reduction_level_from_i32_rs(v: i32) -> Option<kreuzberg::ReductionLevel> {
    match v {
        0 => Some(kreuzberg::ReductionLevel::Off),
        1 => Some(kreuzberg::ReductionLevel::Light),
        2 => Some(kreuzberg::ReductionLevel::Moderate),
        3 => Some(kreuzberg::ReductionLevel::Aggressive),
        4 => Some(kreuzberg::ReductionLevel::Maximum),
        _ => None,
    }
}

#[allow(dead_code)]
fn pdf_annotation_type_from_i32_rs(v: i32) -> Option<kreuzberg::PdfAnnotationType> {
    match v {
        0 => Some(kreuzberg::PdfAnnotationType::Text),
        1 => Some(kreuzberg::PdfAnnotationType::Highlight),
        2 => Some(kreuzberg::PdfAnnotationType::Link),
        3 => Some(kreuzberg::PdfAnnotationType::Stamp),
        4 => Some(kreuzberg::PdfAnnotationType::Underline),
        5 => Some(kreuzberg::PdfAnnotationType::StrikeOut),
        6 => Some(kreuzberg::PdfAnnotationType::Other),
        _ => None,
    }
}

#[allow(dead_code)]
fn block_type_from_i32_rs(v: i32) -> Option<kreuzberg::BlockType> {
    match v {
        0 => Some(kreuzberg::BlockType::Paragraph),
        1 => Some(kreuzberg::BlockType::Heading),
        2 => Some(kreuzberg::BlockType::Blockquote),
        3 => Some(kreuzberg::BlockType::CodeBlock),
        4 => Some(kreuzberg::BlockType::ListItem),
        5 => Some(kreuzberg::BlockType::OrderedList),
        6 => Some(kreuzberg::BlockType::BulletList),
        7 => Some(kreuzberg::BlockType::TaskList),
        8 => Some(kreuzberg::BlockType::DefinitionList),
        9 => Some(kreuzberg::BlockType::DefinitionTerm),
        10 => Some(kreuzberg::BlockType::DefinitionDescription),
        11 => Some(kreuzberg::BlockType::Div),
        12 => Some(kreuzberg::BlockType::Section),
        13 => Some(kreuzberg::BlockType::ThematicBreak),
        14 => Some(kreuzberg::BlockType::RawBlock),
        15 => Some(kreuzberg::BlockType::MathDisplay),
        _ => None,
    }
}

#[allow(dead_code)]
fn inline_type_from_i32_rs(v: i32) -> Option<kreuzberg::InlineType> {
    match v {
        0 => Some(kreuzberg::InlineType::Text),
        1 => Some(kreuzberg::InlineType::Strong),
        2 => Some(kreuzberg::InlineType::Emphasis),
        3 => Some(kreuzberg::InlineType::Highlight),
        4 => Some(kreuzberg::InlineType::Subscript),
        5 => Some(kreuzberg::InlineType::Superscript),
        6 => Some(kreuzberg::InlineType::Insert),
        7 => Some(kreuzberg::InlineType::Delete),
        8 => Some(kreuzberg::InlineType::Code),
        9 => Some(kreuzberg::InlineType::Link),
        10 => Some(kreuzberg::InlineType::Image),
        11 => Some(kreuzberg::InlineType::Span),
        12 => Some(kreuzberg::InlineType::Math),
        13 => Some(kreuzberg::InlineType::RawInline),
        14 => Some(kreuzberg::InlineType::FootnoteRef),
        15 => Some(kreuzberg::InlineType::Symbol),
        _ => None,
    }
}

#[allow(dead_code)]
fn relationship_kind_from_i32_rs(v: i32) -> Option<kreuzberg::RelationshipKind> {
    match v {
        0 => Some(kreuzberg::RelationshipKind::FootnoteReference),
        1 => Some(kreuzberg::RelationshipKind::CitationReference),
        2 => Some(kreuzberg::RelationshipKind::InternalLink),
        3 => Some(kreuzberg::RelationshipKind::Caption),
        4 => Some(kreuzberg::RelationshipKind::Label),
        5 => Some(kreuzberg::RelationshipKind::TocEntry),
        6 => Some(kreuzberg::RelationshipKind::CrossReference),
        _ => None,
    }
}

#[allow(dead_code)]
fn content_layer_from_i32_rs(v: i32) -> Option<kreuzberg::ContentLayer> {
    match v {
        0 => Some(kreuzberg::ContentLayer::Body),
        1 => Some(kreuzberg::ContentLayer::Header),
        2 => Some(kreuzberg::ContentLayer::Footer),
        3 => Some(kreuzberg::ContentLayer::Footnote),
        _ => None,
    }
}

#[allow(dead_code)]
fn extraction_method_from_i32_rs(v: i32) -> Option<kreuzberg::ExtractionMethod> {
    match v {
        0 => Some(kreuzberg::ExtractionMethod::Native),
        1 => Some(kreuzberg::ExtractionMethod::Ocr),
        2 => Some(kreuzberg::ExtractionMethod::Mixed),
        _ => None,
    }
}

#[allow(dead_code)]
fn chunk_type_from_i32_rs(v: i32) -> Option<kreuzberg::ChunkType> {
    match v {
        0 => Some(kreuzberg::ChunkType::Heading),
        1 => Some(kreuzberg::ChunkType::PartyList),
        2 => Some(kreuzberg::ChunkType::Definitions),
        3 => Some(kreuzberg::ChunkType::OperativeClause),
        4 => Some(kreuzberg::ChunkType::SignatureBlock),
        5 => Some(kreuzberg::ChunkType::Schedule),
        6 => Some(kreuzberg::ChunkType::TableLike),
        7 => Some(kreuzberg::ChunkType::Formula),
        8 => Some(kreuzberg::ChunkType::CodeBlock),
        9 => Some(kreuzberg::ChunkType::Image),
        10 => Some(kreuzberg::ChunkType::OrgChart),
        11 => Some(kreuzberg::ChunkType::Diagram),
        12 => Some(kreuzberg::ChunkType::Unknown),
        _ => None,
    }
}

#[allow(dead_code)]
fn image_kind_from_i32_rs(v: i32) -> Option<kreuzberg::ImageKind> {
    match v {
        0 => Some(kreuzberg::ImageKind::Photograph),
        1 => Some(kreuzberg::ImageKind::Diagram),
        2 => Some(kreuzberg::ImageKind::Chart),
        3 => Some(kreuzberg::ImageKind::Drawing),
        4 => Some(kreuzberg::ImageKind::TextBlock),
        5 => Some(kreuzberg::ImageKind::Decoration),
        6 => Some(kreuzberg::ImageKind::Logo),
        7 => Some(kreuzberg::ImageKind::Icon),
        8 => Some(kreuzberg::ImageKind::TileFragment),
        9 => Some(kreuzberg::ImageKind::Mask),
        10 => Some(kreuzberg::ImageKind::PageRaster),
        11 => Some(kreuzberg::ImageKind::Unknown),
        _ => None,
    }
}

#[allow(dead_code)]
fn result_format_from_i32_rs(v: i32) -> Option<kreuzberg::ResultFormat> {
    match v {
        0 => Some(kreuzberg::ResultFormat::Unified),
        1 => Some(kreuzberg::ResultFormat::ElementBased),
        _ => None,
    }
}

#[allow(dead_code)]
fn element_type_from_i32_rs(v: i32) -> Option<kreuzberg::ElementType> {
    match v {
        0 => Some(kreuzberg::ElementType::Title),
        1 => Some(kreuzberg::ElementType::NarrativeText),
        2 => Some(kreuzberg::ElementType::Heading),
        3 => Some(kreuzberg::ElementType::ListItem),
        4 => Some(kreuzberg::ElementType::Table),
        5 => Some(kreuzberg::ElementType::Image),
        6 => Some(kreuzberg::ElementType::PageBreak),
        7 => Some(kreuzberg::ElementType::CodeBlock),
        8 => Some(kreuzberg::ElementType::BlockQuote),
        9 => Some(kreuzberg::ElementType::Footer),
        10 => Some(kreuzberg::ElementType::Header),
        _ => None,
    }
}

#[allow(dead_code)]
fn text_direction_from_i32_rs(v: i32) -> Option<kreuzberg::TextDirection> {
    match v {
        0 => Some(kreuzberg::TextDirection::LeftToRight),
        1 => Some(kreuzberg::TextDirection::RightToLeft),
        2 => Some(kreuzberg::TextDirection::Auto),
        _ => None,
    }
}

#[allow(dead_code)]
fn link_type_from_i32_rs(v: i32) -> Option<kreuzberg::LinkType> {
    match v {
        0 => Some(kreuzberg::LinkType::Anchor),
        1 => Some(kreuzberg::LinkType::Internal),
        2 => Some(kreuzberg::LinkType::External),
        3 => Some(kreuzberg::LinkType::Email),
        4 => Some(kreuzberg::LinkType::Phone),
        5 => Some(kreuzberg::LinkType::Other),
        _ => None,
    }
}

#[allow(dead_code)]
fn image_type_from_i32_rs(v: i32) -> Option<kreuzberg::ImageType> {
    match v {
        0 => Some(kreuzberg::ImageType::DataUri),
        1 => Some(kreuzberg::ImageType::InlineSvg),
        2 => Some(kreuzberg::ImageType::External),
        3 => Some(kreuzberg::ImageType::Relative),
        _ => None,
    }
}

#[allow(dead_code)]
fn structured_data_type_from_i32_rs(v: i32) -> Option<kreuzberg::StructuredDataType> {
    match v {
        0 => Some(kreuzberg::StructuredDataType::JsonLd),
        1 => Some(kreuzberg::StructuredDataType::Microdata),
        2 => Some(kreuzberg::StructuredDataType::RDFa),
        _ => None,
    }
}

#[allow(dead_code)]
fn ocr_element_level_from_i32_rs(v: i32) -> Option<kreuzberg::OcrElementLevel> {
    match v {
        0 => Some(kreuzberg::OcrElementLevel::Word),
        1 => Some(kreuzberg::OcrElementLevel::Line),
        2 => Some(kreuzberg::OcrElementLevel::Block),
        3 => Some(kreuzberg::OcrElementLevel::Page),
        _ => None,
    }
}

#[allow(dead_code)]
fn page_unit_type_from_i32_rs(v: i32) -> Option<kreuzberg::PageUnitType> {
    match v {
        0 => Some(kreuzberg::PageUnitType::Page),
        1 => Some(kreuzberg::PageUnitType::Slide),
        2 => Some(kreuzberg::PageUnitType::Sheet),
        _ => None,
    }
}

#[allow(dead_code)]
fn revision_kind_from_i32_rs(v: i32) -> Option<kreuzberg::RevisionKind> {
    match v {
        0 => Some(kreuzberg::RevisionKind::Insertion),
        1 => Some(kreuzberg::RevisionKind::Deletion),
        2 => Some(kreuzberg::RevisionKind::FormatChange),
        3 => Some(kreuzberg::RevisionKind::Comment),
        _ => None,
    }
}

#[allow(dead_code)]
fn uri_kind_from_i32_rs(v: i32) -> Option<kreuzberg::UriKind> {
    match v {
        0 => Some(kreuzberg::UriKind::Hyperlink),
        1 => Some(kreuzberg::UriKind::Image),
        2 => Some(kreuzberg::UriKind::Anchor),
        3 => Some(kreuzberg::UriKind::Citation),
        4 => Some(kreuzberg::UriKind::Reference),
        5 => Some(kreuzberg::UriKind::Email),
        _ => None,
    }
}

#[allow(dead_code)]
fn keyword_algorithm_from_i32_rs(v: i32) -> Option<kreuzberg::KeywordAlgorithm> {
    match v {
        0 => Some(kreuzberg::KeywordAlgorithm::Yake),
        1 => Some(kreuzberg::KeywordAlgorithm::Rake),
        _ => None,
    }
}

#[allow(dead_code)]
fn psm_mode_from_i32_rs(v: i32) -> Option<kreuzberg::PSMMode> {
    match v {
        0 => Some(kreuzberg::PSMMode::OsdOnly),
        1 => Some(kreuzberg::PSMMode::AutoOsd),
        2 => Some(kreuzberg::PSMMode::AutoOnly),
        3 => Some(kreuzberg::PSMMode::Auto),
        4 => Some(kreuzberg::PSMMode::SingleColumn),
        5 => Some(kreuzberg::PSMMode::SingleBlockVertical),
        6 => Some(kreuzberg::PSMMode::SingleBlock),
        7 => Some(kreuzberg::PSMMode::SingleLine),
        8 => Some(kreuzberg::PSMMode::SingleWord),
        9 => Some(kreuzberg::PSMMode::CircleWord),
        10 => Some(kreuzberg::PSMMode::SingleChar),
        _ => None,
    }
}

#[allow(dead_code)]
fn paddle_language_from_i32_rs(v: i32) -> Option<kreuzberg::PaddleLanguage> {
    match v {
        0 => Some(kreuzberg::PaddleLanguage::English),
        1 => Some(kreuzberg::PaddleLanguage::Chinese),
        2 => Some(kreuzberg::PaddleLanguage::Japanese),
        3 => Some(kreuzberg::PaddleLanguage::Korean),
        4 => Some(kreuzberg::PaddleLanguage::German),
        5 => Some(kreuzberg::PaddleLanguage::French),
        6 => Some(kreuzberg::PaddleLanguage::Latin),
        7 => Some(kreuzberg::PaddleLanguage::Cyrillic),
        8 => Some(kreuzberg::PaddleLanguage::TraditionalChinese),
        9 => Some(kreuzberg::PaddleLanguage::Thai),
        10 => Some(kreuzberg::PaddleLanguage::Greek),
        11 => Some(kreuzberg::PaddleLanguage::EastSlavic),
        12 => Some(kreuzberg::PaddleLanguage::Arabic),
        13 => Some(kreuzberg::PaddleLanguage::Devanagari),
        14 => Some(kreuzberg::PaddleLanguage::Tamil),
        15 => Some(kreuzberg::PaddleLanguage::Telugu),
        _ => None,
    }
}

#[allow(dead_code)]
fn layout_class_from_i32_rs(v: i32) -> Option<kreuzberg::LayoutClass> {
    match v {
        0 => Some(kreuzberg::LayoutClass::Caption),
        1 => Some(kreuzberg::LayoutClass::Footnote),
        2 => Some(kreuzberg::LayoutClass::Formula),
        3 => Some(kreuzberg::LayoutClass::ListItem),
        4 => Some(kreuzberg::LayoutClass::PageFooter),
        5 => Some(kreuzberg::LayoutClass::PageHeader),
        6 => Some(kreuzberg::LayoutClass::Picture),
        7 => Some(kreuzberg::LayoutClass::SectionHeader),
        8 => Some(kreuzberg::LayoutClass::Table),
        9 => Some(kreuzberg::LayoutClass::Text),
        10 => Some(kreuzberg::LayoutClass::Title),
        11 => Some(kreuzberg::LayoutClass::DocumentIndex),
        12 => Some(kreuzberg::LayoutClass::Code),
        13 => Some(kreuzberg::LayoutClass::CheckboxSelected),
        14 => Some(kreuzberg::LayoutClass::CheckboxUnselected),
        15 => Some(kreuzberg::LayoutClass::Form),
        16 => Some(kreuzberg::LayoutClass::KeyValueRegion),
        _ => None,
    }
}

/// Create a `CacheStats` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_cache_stats_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_from_json(json: *const c_char) -> *mut kreuzberg::CacheStats {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::CacheStats>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `CacheStats` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_to_json(ptr: *const kreuzberg::CacheStats) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `CacheStats` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_free(ptr: *mut kreuzberg::CacheStats) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `total_files` field from a `CacheStats`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_total_files(ptr: *const kreuzberg::CacheStats) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.total_files
}

/// Get the `total_size_mb` field from a `CacheStats`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_total_size_mb(ptr: *const kreuzberg::CacheStats) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.total_size_mb
}

/// Get the `available_space_mb` field from a `CacheStats`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_available_space_mb(ptr: *const kreuzberg::CacheStats) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.available_space_mb
}

/// Get the `oldest_file_age_days` field from a `CacheStats`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_oldest_file_age_days(ptr: *const kreuzberg::CacheStats) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.oldest_file_age_days
}

/// Get the `newest_file_age_days` field from a `CacheStats`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cache_stats_newest_file_age_days(ptr: *const kreuzberg::CacheStats) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.newest_file_age_days
}

/// Create a `AccelerationConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_acceleration_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_acceleration_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::AccelerationConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::AccelerationConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `AccelerationConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_acceleration_config_to_json(
    ptr: *const kreuzberg::AccelerationConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `AccelerationConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_acceleration_config_free(ptr: *mut kreuzberg::AccelerationConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `provider` field from a `AccelerationConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_acceleration_config_provider(
    ptr: *const kreuzberg::AccelerationConfig,
) -> *mut kreuzberg::ExecutionProviderType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.provider.clone()))
}

/// Get the `device_id` field from a `AccelerationConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_acceleration_config_device_id(ptr: *const kreuzberg::AccelerationConfig) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.device_id
}

/// Create a `ContentFilterConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_content_filter_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ContentFilterConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ContentFilterConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ContentFilterConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_to_json(
    ptr: *const kreuzberg::ContentFilterConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ContentFilterConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_free(ptr: *mut kreuzberg::ContentFilterConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `include_headers` field from a `ContentFilterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_include_headers(
    ptr: *const kreuzberg::ContentFilterConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_headers as i32
}

/// Get the `include_footers` field from a `ContentFilterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_include_footers(
    ptr: *const kreuzberg::ContentFilterConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_footers as i32
}

/// Get the `strip_repeating_text` field from a `ContentFilterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_strip_repeating_text(
    ptr: *const kreuzberg::ContentFilterConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.strip_repeating_text as i32
}

/// Get the `include_watermarks` field from a `ContentFilterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_include_watermarks(
    ptr: *const kreuzberg::ContentFilterConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_watermarks as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_filter_config_default() -> *mut kreuzberg::ContentFilterConfig {
    clear_last_error();
    let result = kreuzberg::ContentFilterConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `EmailConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_email_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_config_from_json(json: *const c_char) -> *mut kreuzberg::EmailConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmailConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmailConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_config_to_json(ptr: *const kreuzberg::EmailConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmailConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_config_free(ptr: *mut kreuzberg::EmailConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `msg_fallback_codepage` field from a `EmailConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_config_msg_fallback_codepage(ptr: *const kreuzberg::EmailConfig) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.msg_fallback_codepage {
        Some(val) => *val,
        None => 0,
    }
}

/// Create a `ExtractionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_extraction_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ExtractionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExtractionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExtractionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_to_json(ptr: *const kreuzberg::ExtractionConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExtractionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_free(ptr: *mut kreuzberg::ExtractionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `use_cache` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_use_cache(ptr: *const kreuzberg::ExtractionConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.use_cache as i32
}

/// Get the `enable_quality_processing` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_enable_quality_processing(
    ptr: *const kreuzberg::ExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enable_quality_processing as i32
}

/// Get the `ocr` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_ocr(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::OcrConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.ocr {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `force_ocr` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_force_ocr(ptr: *const kreuzberg::ExtractionConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.force_ocr as i32
}

/// Get the `force_ocr_pages` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_force_ocr_pages(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.force_ocr_pages {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `disable_ocr` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_disable_ocr(ptr: *const kreuzberg::ExtractionConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.disable_ocr as i32
}

/// Get the `chunking` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_chunking(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ChunkingConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.chunking {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `content_filter` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_content_filter(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ContentFilterConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.content_filter {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `images` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_images(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ImageExtractionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.images {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `pdf_options` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_pdf_options(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::PdfConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pdf_options {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `token_reduction` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_token_reduction(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::TokenReductionOptions {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.token_reduction {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `language_detection` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_language_detection(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::LanguageDetectionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language_detection {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `pages` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_pages(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::PageConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pages {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `keywords` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_keywords(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::KeywordConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.keywords {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `postprocessor` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_postprocessor(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::PostProcessorConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.postprocessor {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `html_output` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_html_output(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::HtmlOutputConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.html_output {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `extraction_timeout_secs` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_extraction_timeout_secs(
    ptr: *const kreuzberg::ExtractionConfig,
) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.extraction_timeout_secs {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `max_concurrent_extractions` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_max_concurrent_extractions(
    ptr: *const kreuzberg::ExtractionConfig,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_concurrent_extractions {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `result_format` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_result_format(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ResultFormat {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.result_format))
}

/// Get the `security_limits` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_security_limits(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::SecurityLimits {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.security_limits {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `max_embedded_file_bytes` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_max_embedded_file_bytes(
    ptr: *const kreuzberg::ExtractionConfig,
) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_embedded_file_bytes {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `output_format` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_output_format(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::OutputFormat {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.output_format.clone()))
}

/// Get the `layout` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_layout(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::LayoutDetectionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.layout {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `use_layout_for_markdown` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_use_layout_for_markdown(
    ptr: *const kreuzberg::ExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.use_layout_for_markdown as i32
}

/// Get the `include_document_structure` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_include_document_structure(
    ptr: *const kreuzberg::ExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_document_structure as i32
}

/// Get the `acceleration` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_acceleration(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::AccelerationConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.acceleration {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `cache_namespace` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_cache_namespace(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cache_namespace {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `cache_ttl_secs` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_cache_ttl_secs(ptr: *const kreuzberg::ExtractionConfig) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cache_ttl_secs {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `email` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_email(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::EmailConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.email {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `max_archive_depth` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_max_archive_depth(
    ptr: *const kreuzberg::ExtractionConfig,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_archive_depth
}

/// Get the `tree_sitter` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_tree_sitter(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::TreeSitterConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.tree_sitter {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `structured_extraction` field from a `ExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_structured_extraction(
    ptr: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::StructuredExtractionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.structured_extraction {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_default() -> *mut kreuzberg::ExtractionConfig {
    clear_last_error();
    let result = kreuzberg::ExtractionConfig::default();
    Box::into_raw(Box::new(result))
}

/// Check if image processing is needed by examining OCR and image extraction settings.
///
/// Returns `true` if either OCR is enabled or image extraction is configured,
/// indicating that image decompression and processing should occur.
/// Returns `false` if both are disabled, allowing optimization to skip unnecessary
/// image decompression for text-only extraction workflows.
///
/// # Optimization Impact
/// For text-only extractions (no OCR, no image extraction), skipping image
/// decompression can improve CPU utilization by 5-10% by avoiding wasteful
/// image I/O and processing when results won't be used.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_config_needs_image_processing(
    this: *const kreuzberg::ExtractionConfig,
) -> i32 {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    let result = obj.needs_image_processing();
    if result {
        1
    } else {
        0
    }
}

/// Create a `FileExtractionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_file_extraction_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::FileExtractionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::FileExtractionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `FileExtractionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_to_json(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `FileExtractionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_free(ptr: *mut kreuzberg::FileExtractionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `enable_quality_processing` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_enable_quality_processing(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.enable_quality_processing {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `ocr` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_ocr(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::OcrConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.ocr {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `force_ocr` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_force_ocr(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.force_ocr {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `force_ocr_pages` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_force_ocr_pages(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.force_ocr_pages {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `disable_ocr` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_disable_ocr(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.disable_ocr {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `chunking` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_chunking(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::ChunkingConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.chunking {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `content_filter` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_content_filter(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::ContentFilterConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.content_filter {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `images` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_images(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::ImageExtractionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.images {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `pdf_options` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_pdf_options(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::PdfConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pdf_options {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `token_reduction` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_token_reduction(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::TokenReductionOptions {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.token_reduction {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `language_detection` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_language_detection(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::LanguageDetectionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language_detection {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `pages` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_pages(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::PageConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pages {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `keywords` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_keywords(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::KeywordConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.keywords {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `postprocessor` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_postprocessor(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::PostProcessorConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.postprocessor {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `result_format` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_result_format(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::ResultFormat {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.result_format {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `output_format` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_output_format(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::OutputFormat {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.output_format {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `include_document_structure` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_include_document_structure(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.include_document_structure {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `layout` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_layout(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::LayoutDetectionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.layout {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `timeout_secs` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_timeout_secs(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.timeout_secs {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `tree_sitter` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_tree_sitter(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::TreeSitterConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.tree_sitter {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `structured_extraction` field from a `FileExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_file_extraction_config_structured_extraction(
    ptr: *const kreuzberg::FileExtractionConfig,
) -> *mut kreuzberg::StructuredExtractionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.structured_extraction {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Create a `BatchBytesItem` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_batch_bytes_item_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_bytes_item_from_json(json: *const c_char) -> *mut kreuzberg::BatchBytesItem {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::BatchBytesItem>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `BatchBytesItem` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_bytes_item_to_json(ptr: *const kreuzberg::BatchBytesItem) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `BatchBytesItem` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_bytes_item_free(ptr: *mut kreuzberg::BatchBytesItem) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `BatchBytesItem`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_bytes_item_content(
    ptr: *const kreuzberg::BatchBytesItem,
    out_len: *mut usize,
) -> *mut u8 {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    let data = &obj.content;
    if !out_len.is_null() {
        // SAFETY: null check above guarantees out_len is a valid pointer.
        unsafe {
            *out_len = data.len();
        }
    }
    data.as_ptr() as *mut u8
}

/// Get the `mime_type` field from a `BatchBytesItem`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_bytes_item_mime_type(
    ptr: *const kreuzberg::BatchBytesItem,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mime_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `config` field from a `BatchBytesItem`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_bytes_item_config(
    ptr: *const kreuzberg::BatchBytesItem,
) -> *mut kreuzberg::FileExtractionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Create a `BatchFileItem` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_batch_file_item_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_file_item_from_json(json: *const c_char) -> *mut kreuzberg::BatchFileItem {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::BatchFileItem>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `BatchFileItem` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_file_item_to_json(ptr: *const kreuzberg::BatchFileItem) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `BatchFileItem` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_file_item_free(ptr: *mut kreuzberg::BatchFileItem) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `path` field from a `BatchFileItem`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_file_item_path(ptr: *const kreuzberg::BatchFileItem) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.path.to_string_lossy().to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `config` field from a `BatchFileItem`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_file_item_config(
    ptr: *const kreuzberg::BatchFileItem,
) -> *mut kreuzberg::FileExtractionConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Create a `ImageExtractionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_image_extraction_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ImageExtractionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ImageExtractionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ImageExtractionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_to_json(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ImageExtractionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_free(ptr: *mut kreuzberg::ImageExtractionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `extract_images` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_extract_images(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.extract_images as i32
}

/// Get the `target_dpi` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_target_dpi(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.target_dpi
}

/// Get the `max_image_dimension` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_max_image_dimension(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_image_dimension
}

/// Get the `inject_placeholders` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_inject_placeholders(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.inject_placeholders as i32
}

/// Get the `auto_adjust_dpi` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_auto_adjust_dpi(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.auto_adjust_dpi as i32
}

/// Get the `min_dpi` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_min_dpi(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_dpi
}

/// Get the `max_dpi` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_max_dpi(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_dpi
}

/// Get the `max_images_per_page` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_max_images_per_page(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_images_per_page {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `classify` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_classify(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.classify as i32
}

/// Get the `include_page_rasters` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_include_page_rasters(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_page_rasters as i32
}

/// Get the `run_ocr_on_images` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_run_ocr_on_images(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.run_ocr_on_images as i32
}

/// Get the `ocr_text_only` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_ocr_text_only(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.ocr_text_only as i32
}

/// Get the `append_ocr_text` field from a `ImageExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_append_ocr_text(
    ptr: *const kreuzberg::ImageExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.append_ocr_text as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_extraction_config_default() -> *mut kreuzberg::ImageExtractionConfig {
    clear_last_error();
    let result = kreuzberg::ImageExtractionConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `TokenReductionOptions` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_token_reduction_options_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_options_from_json(
    json: *const c_char,
) -> *mut kreuzberg::TokenReductionOptions {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TokenReductionOptions>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TokenReductionOptions` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_options_to_json(
    ptr: *const kreuzberg::TokenReductionOptions,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TokenReductionOptions` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_options_free(ptr: *mut kreuzberg::TokenReductionOptions) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `mode` field from a `TokenReductionOptions`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_options_mode(
    ptr: *const kreuzberg::TokenReductionOptions,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mode.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `preserve_important_words` field from a `TokenReductionOptions`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_options_preserve_important_words(
    ptr: *const kreuzberg::TokenReductionOptions,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.preserve_important_words as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_options_default() -> *mut kreuzberg::TokenReductionOptions {
    clear_last_error();
    let result = kreuzberg::TokenReductionOptions::default();
    Box::into_raw(Box::new(result))
}

/// Create a `LanguageDetectionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_language_detection_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::LanguageDetectionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LanguageDetectionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LanguageDetectionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_to_json(
    ptr: *const kreuzberg::LanguageDetectionConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LanguageDetectionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_free(ptr: *mut kreuzberg::LanguageDetectionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `enabled` field from a `LanguageDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_enabled(
    ptr: *const kreuzberg::LanguageDetectionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enabled as i32
}

/// Get the `min_confidence` field from a `LanguageDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_min_confidence(
    ptr: *const kreuzberg::LanguageDetectionConfig,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_confidence
}

/// Get the `detect_multiple` field from a `LanguageDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_detect_multiple(
    ptr: *const kreuzberg::LanguageDetectionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.detect_multiple as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_language_detection_config_default() -> *mut kreuzberg::LanguageDetectionConfig {
    clear_last_error();
    let result = kreuzberg::LanguageDetectionConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `HtmlOutputConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_html_output_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::HtmlOutputConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HtmlOutputConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HtmlOutputConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_to_json(ptr: *const kreuzberg::HtmlOutputConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HtmlOutputConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_free(ptr: *mut kreuzberg::HtmlOutputConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `css` field from a `HtmlOutputConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_css(
    ptr: *const kreuzberg::HtmlOutputConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.css {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `css_file` field from a `HtmlOutputConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_css_file(
    ptr: *const kreuzberg::HtmlOutputConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.css_file {
        Some(val) => match CString::new(val.to_string_lossy().to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `theme` field from a `HtmlOutputConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_theme(
    ptr: *const kreuzberg::HtmlOutputConfig,
) -> *mut kreuzberg::HtmlTheme {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.theme.clone()))
}

/// Get the `class_prefix` field from a `HtmlOutputConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_class_prefix(
    ptr: *const kreuzberg::HtmlOutputConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.class_prefix.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `embed_css` field from a `HtmlOutputConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_embed_css(ptr: *const kreuzberg::HtmlOutputConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.embed_css as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_output_config_default() -> *mut kreuzberg::HtmlOutputConfig {
    clear_last_error();
    let result = kreuzberg::HtmlOutputConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `LayoutDetectionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_layout_detection_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::LayoutDetectionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LayoutDetectionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LayoutDetectionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_to_json(
    ptr: *const kreuzberg::LayoutDetectionConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LayoutDetectionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_free(ptr: *mut kreuzberg::LayoutDetectionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `confidence_threshold` field from a `LayoutDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_confidence_threshold(
    ptr: *const kreuzberg::LayoutDetectionConfig,
) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.confidence_threshold {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `apply_heuristics` field from a `LayoutDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_apply_heuristics(
    ptr: *const kreuzberg::LayoutDetectionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.apply_heuristics as i32
}

/// Get the `table_model` field from a `LayoutDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_table_model(
    ptr: *const kreuzberg::LayoutDetectionConfig,
) -> *mut kreuzberg::TableModel {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.table_model))
}

/// Get the `acceleration` field from a `LayoutDetectionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_acceleration(
    ptr: *const kreuzberg::LayoutDetectionConfig,
) -> *mut kreuzberg::AccelerationConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.acceleration {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_config_default() -> *mut kreuzberg::LayoutDetectionConfig {
    clear_last_error();
    let result = kreuzberg::LayoutDetectionConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `LlmConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_llm_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_from_json(json: *const c_char) -> *mut kreuzberg::LlmConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LlmConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LlmConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_to_json(ptr: *const kreuzberg::LlmConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LlmConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_free(ptr: *mut kreuzberg::LlmConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `model` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_model(ptr: *const kreuzberg::LlmConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.model.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `api_key` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_api_key(ptr: *const kreuzberg::LlmConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.api_key {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `base_url` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_base_url(ptr: *const kreuzberg::LlmConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.base_url {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `timeout_secs` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_timeout_secs(ptr: *const kreuzberg::LlmConfig) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.timeout_secs {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `max_retries` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_max_retries(ptr: *const kreuzberg::LlmConfig) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_retries {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `temperature` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_temperature(ptr: *const kreuzberg::LlmConfig) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.temperature {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `max_tokens` field from a `LlmConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_config_max_tokens(ptr: *const kreuzberg::LlmConfig) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_tokens {
        Some(val) => *val,
        None => 0,
    }
}

/// Create a `StructuredExtractionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_structured_extraction_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::StructuredExtractionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::StructuredExtractionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `StructuredExtractionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_to_json(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `StructuredExtractionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_free(ptr: *mut kreuzberg::StructuredExtractionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `schema` field from a `StructuredExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_schema(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.schema) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `schema_name` field from a `StructuredExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_schema_name(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.schema_name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `schema_description` field from a `StructuredExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_schema_description(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.schema_description {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `strict` field from a `StructuredExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_strict(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.strict as i32
}

/// Get the `prompt` field from a `StructuredExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_prompt(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.prompt {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `llm` field from a `StructuredExtractionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_extraction_config_llm(
    ptr: *const kreuzberg::StructuredExtractionConfig,
) -> *mut kreuzberg::LlmConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.llm.clone()))
}

/// Create a `OcrQualityThresholds` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_quality_thresholds_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OcrQualityThresholds {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrQualityThresholds>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrQualityThresholds` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_to_json(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrQualityThresholds` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_free(ptr: *mut kreuzberg::OcrQualityThresholds) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `min_total_non_whitespace` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_total_non_whitespace(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_total_non_whitespace
}

/// Get the `min_non_whitespace_per_page` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_non_whitespace_per_page(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_non_whitespace_per_page
}

/// Get the `min_meaningful_word_len` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_meaningful_word_len(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_meaningful_word_len
}

/// Get the `min_meaningful_words` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_meaningful_words(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_meaningful_words
}

/// Get the `min_alnum_ratio` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_alnum_ratio(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_alnum_ratio
}

/// Get the `min_garbage_chars` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_garbage_chars(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_garbage_chars
}

/// Get the `max_fragmented_word_ratio` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_max_fragmented_word_ratio(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_fragmented_word_ratio
}

/// Get the `critical_fragmented_word_ratio` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_critical_fragmented_word_ratio(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.critical_fragmented_word_ratio
}

/// Get the `min_avg_word_length` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_avg_word_length(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_avg_word_length
}

/// Get the `min_words_for_avg_length_check` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_words_for_avg_length_check(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_words_for_avg_length_check
}

/// Get the `min_consecutive_repeat_ratio` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_consecutive_repeat_ratio(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_consecutive_repeat_ratio
}

/// Get the `min_words_for_repeat_check` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_min_words_for_repeat_check(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_words_for_repeat_check
}

/// Get the `substantive_min_chars` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_substantive_min_chars(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.substantive_min_chars
}

/// Get the `non_text_min_chars` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_non_text_min_chars(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.non_text_min_chars
}

/// Get the `alnum_ws_ratio_threshold` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_alnum_ws_ratio_threshold(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.alnum_ws_ratio_threshold
}

/// Get the `pipeline_min_quality` field from a `OcrQualityThresholds`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_pipeline_min_quality(
    ptr: *const kreuzberg::OcrQualityThresholds,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.pipeline_min_quality
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_quality_thresholds_default() -> *mut kreuzberg::OcrQualityThresholds {
    clear_last_error();
    let result = kreuzberg::OcrQualityThresholds::default();
    Box::into_raw(Box::new(result))
}

/// Create a `OcrPipelineStage` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_pipeline_stage_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OcrPipelineStage {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrPipelineStage>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrPipelineStage` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_to_json(ptr: *const kreuzberg::OcrPipelineStage) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrPipelineStage` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_free(ptr: *mut kreuzberg::OcrPipelineStage) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `backend` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_backend(
    ptr: *const kreuzberg::OcrPipelineStage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.backend.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `priority` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_priority(ptr: *const kreuzberg::OcrPipelineStage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.priority
}

/// Get the `language` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_language(
    ptr: *const kreuzberg::OcrPipelineStage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `tesseract_config` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_tesseract_config(
    ptr: *const kreuzberg::OcrPipelineStage,
) -> *mut kreuzberg::TesseractConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.tesseract_config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `paddle_ocr_config` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_paddle_ocr_config(
    ptr: *const kreuzberg::OcrPipelineStage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.paddle_ocr_config {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `vlm_config` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_vlm_config(
    ptr: *const kreuzberg::OcrPipelineStage,
) -> *mut kreuzberg::LlmConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.vlm_config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `backend_options` field from a `OcrPipelineStage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_stage_backend_options(
    ptr: *const kreuzberg::OcrPipelineStage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.backend_options {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `OcrPipelineConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_pipeline_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OcrPipelineConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrPipelineConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrPipelineConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_config_to_json(
    ptr: *const kreuzberg::OcrPipelineConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrPipelineConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_config_free(ptr: *mut kreuzberg::OcrPipelineConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `stages` field from a `OcrPipelineConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_config_stages(
    ptr: *const kreuzberg::OcrPipelineConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.stages) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `quality_thresholds` field from a `OcrPipelineConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_pipeline_config_quality_thresholds(
    ptr: *const kreuzberg::OcrPipelineConfig,
) -> *mut kreuzberg::OcrQualityThresholds {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.quality_thresholds.clone()))
}

/// Create a `OcrConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_from_json(json: *const c_char) -> *mut kreuzberg::OcrConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_to_json(ptr: *const kreuzberg::OcrConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_free(ptr: *mut kreuzberg::OcrConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `enabled` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_enabled(ptr: *const kreuzberg::OcrConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enabled as i32
}

/// Get the `backend` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_backend(ptr: *const kreuzberg::OcrConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.backend.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `language` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_language(ptr: *const kreuzberg::OcrConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.language.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tesseract_config` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_tesseract_config(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::TesseractConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.tesseract_config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `output_format` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_output_format(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::OutputFormat {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.output_format {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `paddle_ocr_config` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_paddle_ocr_config(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.paddle_ocr_config {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `backend_options` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_backend_options(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.backend_options {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `element_config` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_element_config(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::OcrElementConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.element_config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `quality_thresholds` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_quality_thresholds(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::OcrQualityThresholds {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.quality_thresholds {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `pipeline` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_pipeline(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::OcrPipelineConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pipeline {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `auto_rotate` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_auto_rotate(ptr: *const kreuzberg::OcrConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.auto_rotate as i32
}

/// Get the `vlm_config` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_vlm_config(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::LlmConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.vlm_config {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `vlm_prompt` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_vlm_prompt(ptr: *const kreuzberg::OcrConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.vlm_prompt {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `acceleration` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_acceleration(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut kreuzberg::AccelerationConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.acceleration {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `tessdata_bytes` field from a `OcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_tessdata_bytes(
    ptr: *const kreuzberg::OcrConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.tessdata_bytes {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_config_default() -> *mut kreuzberg::OcrConfig {
    clear_last_error();
    let result = kreuzberg::OcrConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `PageConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_page_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_from_json(json: *const c_char) -> *mut kreuzberg::PageConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PageConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PageConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_to_json(ptr: *const kreuzberg::PageConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PageConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_free(ptr: *mut kreuzberg::PageConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `extract_pages` field from a `PageConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_extract_pages(ptr: *const kreuzberg::PageConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.extract_pages as i32
}

/// Get the `insert_page_markers` field from a `PageConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_insert_page_markers(ptr: *const kreuzberg::PageConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.insert_page_markers as i32
}

/// Get the `marker_format` field from a `PageConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_marker_format(
    ptr: *const kreuzberg::PageConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.marker_format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_config_default() -> *mut kreuzberg::PageConfig {
    clear_last_error();
    let result = kreuzberg::PageConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `PdfConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pdf_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_from_json(json: *const c_char) -> *mut kreuzberg::PdfConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PdfConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PdfConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_to_json(ptr: *const kreuzberg::PdfConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PdfConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_free(ptr: *mut kreuzberg::PdfConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `extract_images` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_extract_images(ptr: *const kreuzberg::PdfConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.extract_images as i32
}

/// Get the `extract_tables` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_extract_tables(ptr: *const kreuzberg::PdfConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.extract_tables as i32
}

/// Get the `passwords` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_passwords(ptr: *const kreuzberg::PdfConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.passwords {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `extract_metadata` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_extract_metadata(ptr: *const kreuzberg::PdfConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.extract_metadata as i32
}

/// Get the `hierarchy` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_hierarchy(
    ptr: *const kreuzberg::PdfConfig,
) -> *mut kreuzberg::HierarchyConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hierarchy {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `extract_annotations` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_extract_annotations(ptr: *const kreuzberg::PdfConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.extract_annotations as i32
}

/// Get the `top_margin_fraction` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_top_margin_fraction(ptr: *const kreuzberg::PdfConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.top_margin_fraction {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `bottom_margin_fraction` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_bottom_margin_fraction(ptr: *const kreuzberg::PdfConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bottom_margin_fraction {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `allow_single_column_tables` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_allow_single_column_tables(ptr: *const kreuzberg::PdfConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.allow_single_column_tables as i32
}

/// Get the `ocr_inline_images` field from a `PdfConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_ocr_inline_images(ptr: *const kreuzberg::PdfConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.ocr_inline_images as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_config_default() -> *mut kreuzberg::PdfConfig {
    clear_last_error();
    let result = kreuzberg::PdfConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `HierarchyConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_hierarchy_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_from_json(json: *const c_char) -> *mut kreuzberg::HierarchyConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HierarchyConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HierarchyConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_to_json(ptr: *const kreuzberg::HierarchyConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HierarchyConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_free(ptr: *mut kreuzberg::HierarchyConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `enabled` field from a `HierarchyConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_enabled(ptr: *const kreuzberg::HierarchyConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enabled as i32
}

/// Get the `k_clusters` field from a `HierarchyConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_k_clusters(ptr: *const kreuzberg::HierarchyConfig) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.k_clusters
}

/// Get the `include_bbox` field from a `HierarchyConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_include_bbox(ptr: *const kreuzberg::HierarchyConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_bbox as i32
}

/// Get the `ocr_coverage_threshold` field from a `HierarchyConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_ocr_coverage_threshold(
    ptr: *const kreuzberg::HierarchyConfig,
) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.ocr_coverage_threshold {
        Some(val) => *val,
        None => 0.0,
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchy_config_default() -> *mut kreuzberg::HierarchyConfig {
    clear_last_error();
    let result = kreuzberg::HierarchyConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `PostProcessorConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_post_processor_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::PostProcessorConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PostProcessorConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PostProcessorConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_to_json(
    ptr: *const kreuzberg::PostProcessorConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PostProcessorConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_free(ptr: *mut kreuzberg::PostProcessorConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `enabled` field from a `PostProcessorConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_enabled(ptr: *const kreuzberg::PostProcessorConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enabled as i32
}

/// Get the `enabled_processors` field from a `PostProcessorConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_enabled_processors(
    ptr: *const kreuzberg::PostProcessorConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.enabled_processors {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `disabled_processors` field from a `PostProcessorConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_disabled_processors(
    ptr: *const kreuzberg::PostProcessorConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.disabled_processors {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `enabled_set` field from a `PostProcessorConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_enabled_set(
    ptr: *const kreuzberg::PostProcessorConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.enabled_set {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `disabled_set` field from a `PostProcessorConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_disabled_set(
    ptr: *const kreuzberg::PostProcessorConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.disabled_set {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_post_processor_config_default() -> *mut kreuzberg::PostProcessorConfig {
    clear_last_error();
    let result = kreuzberg::PostProcessorConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `ChunkingConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_chunking_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_from_json(json: *const c_char) -> *mut kreuzberg::ChunkingConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ChunkingConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ChunkingConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_to_json(ptr: *const kreuzberg::ChunkingConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ChunkingConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_free(ptr: *mut kreuzberg::ChunkingConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `max_characters` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_max_characters(ptr: *const kreuzberg::ChunkingConfig) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_characters
}

/// Get the `overlap` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_overlap(ptr: *const kreuzberg::ChunkingConfig) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.overlap
}

/// Get the `trim` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_trim(ptr: *const kreuzberg::ChunkingConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.trim as i32
}

/// Get the `chunker_type` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_chunker_type(
    ptr: *const kreuzberg::ChunkingConfig,
) -> *mut kreuzberg::ChunkerType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.chunker_type))
}

/// Get the `embedding` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_embedding(
    ptr: *const kreuzberg::ChunkingConfig,
) -> *mut kreuzberg::EmbeddingConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.embedding {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `preset` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_preset(
    ptr: *const kreuzberg::ChunkingConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.preset {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `sizing` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_sizing(
    ptr: *const kreuzberg::ChunkingConfig,
) -> *mut kreuzberg::ChunkSizing {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.sizing.clone()))
}

/// Get the `prepend_heading_context` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_prepend_heading_context(
    ptr: *const kreuzberg::ChunkingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.prepend_heading_context as i32
}

/// Get the `topic_threshold` field from a `ChunkingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_topic_threshold(ptr: *const kreuzberg::ChunkingConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.topic_threshold {
        Some(val) => *val,
        None => 0.0,
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunking_config_default() -> *mut kreuzberg::ChunkingConfig {
    clear_last_error();
    let result = kreuzberg::ChunkingConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `EmbeddingConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_embedding_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_from_json(json: *const c_char) -> *mut kreuzberg::EmbeddingConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmbeddingConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmbeddingConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_to_json(ptr: *const kreuzberg::EmbeddingConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmbeddingConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_free(ptr: *mut kreuzberg::EmbeddingConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `model` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_model(
    ptr: *const kreuzberg::EmbeddingConfig,
) -> *mut kreuzberg::EmbeddingModelType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.model.clone()))
}

/// Get the `normalize` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_normalize(ptr: *const kreuzberg::EmbeddingConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.normalize as i32
}

/// Get the `batch_size` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_batch_size(ptr: *const kreuzberg::EmbeddingConfig) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.batch_size
}

/// Get the `show_download_progress` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_show_download_progress(
    ptr: *const kreuzberg::EmbeddingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.show_download_progress as i32
}

/// Get the `cache_dir` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_cache_dir(
    ptr: *const kreuzberg::EmbeddingConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cache_dir {
        Some(val) => match CString::new(val.to_string_lossy().to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `acceleration` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_acceleration(
    ptr: *const kreuzberg::EmbeddingConfig,
) -> *mut kreuzberg::AccelerationConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.acceleration {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `max_embed_duration_secs` field from a `EmbeddingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_max_embed_duration_secs(
    ptr: *const kreuzberg::EmbeddingConfig,
) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_embed_duration_secs {
        Some(val) => *val,
        None => 0,
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_config_default() -> *mut kreuzberg::EmbeddingConfig {
    clear_last_error();
    let result = kreuzberg::EmbeddingConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `TreeSitterConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_tree_sitter_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::TreeSitterConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TreeSitterConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TreeSitterConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_to_json(ptr: *const kreuzberg::TreeSitterConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TreeSitterConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_free(ptr: *mut kreuzberg::TreeSitterConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `enabled` field from a `TreeSitterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_enabled(ptr: *const kreuzberg::TreeSitterConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enabled as i32
}

/// Get the `cache_dir` field from a `TreeSitterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_cache_dir(
    ptr: *const kreuzberg::TreeSitterConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cache_dir {
        Some(val) => match CString::new(val.to_string_lossy().to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `languages` field from a `TreeSitterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_languages(
    ptr: *const kreuzberg::TreeSitterConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.languages {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `groups` field from a `TreeSitterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_groups(
    ptr: *const kreuzberg::TreeSitterConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.groups {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `process` field from a `TreeSitterConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_process(
    ptr: *const kreuzberg::TreeSitterConfig,
) -> *mut kreuzberg::TreeSitterProcessConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.process.clone()))
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_config_default() -> *mut kreuzberg::TreeSitterConfig {
    clear_last_error();
    let result = kreuzberg::TreeSitterConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `TreeSitterProcessConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_tree_sitter_process_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::TreeSitterProcessConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TreeSitterProcessConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TreeSitterProcessConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_to_json(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TreeSitterProcessConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_free(ptr: *mut kreuzberg::TreeSitterProcessConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `structure` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_structure(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.structure as i32
}

/// Get the `imports` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_imports(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.imports as i32
}

/// Get the `exports` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_exports(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.exports as i32
}

/// Get the `comments` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_comments(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.comments as i32
}

/// Get the `docstrings` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_docstrings(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.docstrings as i32
}

/// Get the `symbols` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_symbols(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.symbols as i32
}

/// Get the `diagnostics` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_diagnostics(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.diagnostics as i32
}

/// Get the `chunk_max_size` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_chunk_max_size(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.chunk_max_size {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `content_mode` field from a `TreeSitterProcessConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_content_mode(
    ptr: *const kreuzberg::TreeSitterProcessConfig,
) -> *mut kreuzberg::CodeContentMode {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.content_mode))
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tree_sitter_process_config_default() -> *mut kreuzberg::TreeSitterProcessConfig {
    clear_last_error();
    let result = kreuzberg::TreeSitterProcessConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `SupportedFormat` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_supported_format_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_supported_format_from_json(json: *const c_char) -> *mut kreuzberg::SupportedFormat {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::SupportedFormat>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `SupportedFormat` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_supported_format_to_json(ptr: *const kreuzberg::SupportedFormat) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `SupportedFormat` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_supported_format_free(ptr: *mut kreuzberg::SupportedFormat) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `extension` field from a `SupportedFormat`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_supported_format_extension(
    ptr: *const kreuzberg::SupportedFormat,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.extension.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `mime_type` field from a `SupportedFormat`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_supported_format_mime_type(
    ptr: *const kreuzberg::SupportedFormat,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mime_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `ServerConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_server_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_from_json(json: *const c_char) -> *mut kreuzberg::ServerConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ServerConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ServerConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_to_json(ptr: *const kreuzberg::ServerConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ServerConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_free(ptr: *mut kreuzberg::ServerConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `host` field from a `ServerConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_host(ptr: *const kreuzberg::ServerConfig) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.host.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `port` field from a `ServerConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_port(ptr: *const kreuzberg::ServerConfig) -> u16 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.port
}

/// Get the `cors_origins` field from a `ServerConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_cors_origins(
    ptr: *const kreuzberg::ServerConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cors_origins) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `max_request_body_bytes` field from a `ServerConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_max_request_body_bytes(ptr: *const kreuzberg::ServerConfig) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_request_body_bytes
}

/// Get the `max_multipart_field_bytes` field from a `ServerConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_max_multipart_field_bytes(
    ptr: *const kreuzberg::ServerConfig,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_multipart_field_bytes
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_default() -> *mut kreuzberg::ServerConfig {
    clear_last_error();
    let result = kreuzberg::ServerConfig::default();
    Box::into_raw(Box::new(result))
}

/// Get the server listen address (host:port).
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::ServerConfig;
///
/// let config = ServerConfig::default();
/// assert_eq!(config.listen_addr(), "127.0.0.1:8000");
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_listen_addr(
    this: *const kreuzberg::ServerConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    let result = obj.listen_addr();
    match CString::new(result.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Check if CORS allows all origins.
///
/// Returns `true` if the `cors_origins` vector is empty, meaning all origins
/// are allowed. Returns `false` if specific origins are configured.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::ServerConfig;
///
/// let mut config = ServerConfig::default();
/// assert!(config.cors_allows_all());
///
/// config.cors_origins.push("https://example.com".to_string());
/// assert!(!config.cors_allows_all());
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_cors_allows_all(this: *const kreuzberg::ServerConfig) -> i32 {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    let result = obj.cors_allows_all();
    if result {
        1
    } else {
        0
    }
}

/// Check if a given origin is allowed by CORS configuration.
///
/// Returns `true` if:
/// - CORS allows all origins (empty origins list), or
/// - The given origin is in the allowed origins list
/// \param origin The origin to check (e.g., "https://example.com")
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::ServerConfig;
///
/// let mut config = ServerConfig::default();
/// assert!(config.is_origin_allowed("https://example.com"));
///
/// config.cors_origins.push("https://allowed.com".to_string());
/// assert!(config.is_origin_allowed("https://allowed.com"));
/// assert!(!config.is_origin_allowed("https://denied.com"));
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_is_origin_allowed(
    this: *const kreuzberg::ServerConfig,
    origin: *const std::ffi::c_char,
) -> i32 {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    if origin.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'origin'");
        return 0;
    }
    // SAFETY: null check above guarantees origin is a valid pointer; string is valid UTF-8 from caller.
    let origin_rs = match unsafe { CStr::from_ptr(origin) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'origin'");
            return 0;
        }
    };
    let result = obj.is_origin_allowed(&origin_rs);
    if result {
        1
    } else {
        0
    }
}

/// Get maximum request body size in megabytes (rounded up).
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::ServerConfig;
///
/// let mut config = ServerConfig::default();
/// assert_eq!(config.max_request_body_mb(), 100);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_max_request_body_mb(this: *const kreuzberg::ServerConfig) -> usize {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    obj.max_request_body_mb()
}

/// Get maximum multipart field size in megabytes (rounded up).
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::ServerConfig;
///
/// let mut config = ServerConfig::default();
/// assert_eq!(config.max_multipart_field_mb(), 100);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_server_config_max_multipart_field_mb(this: *const kreuzberg::ServerConfig) -> usize {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    obj.max_multipart_field_mb()
}

/// Create a `StructuredDataResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_structured_data_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::extraction::structured::StructuredDataResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::extraction::structured::StructuredDataResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `StructuredDataResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_to_json(
    ptr: *const kreuzberg::extraction::structured::StructuredDataResult,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `StructuredDataResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_free(
    ptr: *mut kreuzberg::extraction::structured::StructuredDataResult,
) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `StructuredDataResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_content(
    ptr: *const kreuzberg::extraction::structured::StructuredDataResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `format` field from a `StructuredDataResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_format(
    ptr: *const kreuzberg::extraction::structured::StructuredDataResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `StructuredDataResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_metadata(
    ptr: *const kreuzberg::extraction::structured::StructuredDataResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.metadata) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `text_fields` field from a `StructuredDataResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_result_text_fields(
    ptr: *const kreuzberg::extraction::structured::StructuredDataResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.text_fields) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `DocxAppProperties` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_docx_app_properties_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_from_json(
    json: *const c_char,
) -> *mut kreuzberg::DocxAppProperties {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DocxAppProperties>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DocxAppProperties` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_to_json(
    ptr: *const kreuzberg::DocxAppProperties,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DocxAppProperties` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_free(ptr: *mut kreuzberg::DocxAppProperties) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `application` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_application(
    ptr: *const kreuzberg::DocxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.application {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `app_version` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_app_version(
    ptr: *const kreuzberg::DocxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.app_version {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `template` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_template(
    ptr: *const kreuzberg::DocxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.template {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `total_time` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_total_time(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.total_time {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `pages` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_pages(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pages {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `words` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_words(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.words {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `characters` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_characters(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.characters {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `characters_with_spaces` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_characters_with_spaces(
    ptr: *const kreuzberg::DocxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.characters_with_spaces {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `lines` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_lines(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.lines {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `paragraphs` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_paragraphs(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.paragraphs {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `company` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_company(
    ptr: *const kreuzberg::DocxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.company {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `doc_security` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_doc_security(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.doc_security {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `scale_crop` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_scale_crop(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.scale_crop {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `links_up_to_date` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_links_up_to_date(
    ptr: *const kreuzberg::DocxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.links_up_to_date {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `shared_doc` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_shared_doc(ptr: *const kreuzberg::DocxAppProperties) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.shared_doc {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `hyperlinks_changed` field from a `DocxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_app_properties_hyperlinks_changed(
    ptr: *const kreuzberg::DocxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hyperlinks_changed {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Create a `XlsxAppProperties` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_xlsx_app_properties_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_from_json(
    json: *const c_char,
) -> *mut kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `XlsxAppProperties` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_to_json(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `XlsxAppProperties` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_free(
    ptr: *mut kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `application` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_application(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.application {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `app_version` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_app_version(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.app_version {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `doc_security` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_doc_security(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.doc_security {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `scale_crop` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_scale_crop(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.scale_crop {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `links_up_to_date` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_links_up_to_date(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.links_up_to_date {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `shared_doc` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_shared_doc(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.shared_doc {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `hyperlinks_changed` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_hyperlinks_changed(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hyperlinks_changed {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `company` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_company(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.company {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `worksheet_names` field from a `XlsxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xlsx_app_properties_worksheet_names(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::XlsxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.worksheet_names) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `PptxAppProperties` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pptx_app_properties_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_from_json(
    json: *const c_char,
) -> *mut kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PptxAppProperties` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_to_json(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PptxAppProperties` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_free(
    ptr: *mut kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `application` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_application(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.application {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `app_version` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_app_version(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.app_version {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `total_time` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_total_time(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.total_time {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `company` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_company(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.company {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `doc_security` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_doc_security(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.doc_security {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `scale_crop` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_scale_crop(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.scale_crop {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `links_up_to_date` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_links_up_to_date(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.links_up_to_date {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `shared_doc` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_shared_doc(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.shared_doc {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `hyperlinks_changed` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_hyperlinks_changed(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hyperlinks_changed {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `slides` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_slides(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.slides {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `notes` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_notes(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.notes {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `hidden_slides` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_hidden_slides(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hidden_slides {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `multimedia_clips` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_multimedia_clips(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.multimedia_clips {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `presentation_format` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_presentation_format(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.presentation_format {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `slide_titles` field from a `PptxAppProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_app_properties_slide_titles(
    ptr: *const kreuzberg::extraction::office_metadata::app_properties::PptxAppProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.slide_titles) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `CoreProperties` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_core_properties_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_from_json(json: *const c_char) -> *mut kreuzberg::CoreProperties {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::CoreProperties>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `CoreProperties` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_to_json(ptr: *const kreuzberg::CoreProperties) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `CoreProperties` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_free(ptr: *mut kreuzberg::CoreProperties) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `title` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_title(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `subject` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_subject(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.subject {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `creator` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_creator(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.creator {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `keywords` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_keywords(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.keywords {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `description` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_description(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.description {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `last_modified_by` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_last_modified_by(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.last_modified_by {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `revision` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_revision(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.revision {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `created` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_created(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.created {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `modified` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_modified(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.modified {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `category` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_category(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.category {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `content_status` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_content_status(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.content_status {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `language` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_language(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `identifier` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_identifier(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.identifier {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `version` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_version(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.version {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `last_printed` field from a `CoreProperties`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_core_properties_last_printed(
    ptr: *const kreuzberg::CoreProperties,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.last_printed {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `SecurityLimits` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_security_limits_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_from_json(json: *const c_char) -> *mut kreuzberg::SecurityLimits {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::SecurityLimits>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `SecurityLimits` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_to_json(ptr: *const kreuzberg::SecurityLimits) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `SecurityLimits` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_free(ptr: *mut kreuzberg::SecurityLimits) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `max_archive_size` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_archive_size(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_archive_size
}

/// Get the `max_compression_ratio` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_compression_ratio(
    ptr: *const kreuzberg::SecurityLimits,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_compression_ratio
}

/// Get the `max_files_in_archive` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_files_in_archive(
    ptr: *const kreuzberg::SecurityLimits,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_files_in_archive
}

/// Get the `max_nesting_depth` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_nesting_depth(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_nesting_depth
}

/// Get the `max_entity_length` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_entity_length(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_entity_length
}

/// Get the `max_content_size` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_content_size(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_content_size
}

/// Get the `max_iterations` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_iterations(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_iterations
}

/// Get the `max_xml_depth` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_xml_depth(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_xml_depth
}

/// Get the `max_table_cells` field from a `SecurityLimits`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_max_table_cells(ptr: *const kreuzberg::SecurityLimits) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_table_cells
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_security_limits_default() -> *mut kreuzberg::SecurityLimits {
    clear_last_error();
    let result = kreuzberg::SecurityLimits::default();
    Box::into_raw(Box::new(result))
}

/// Create a `TokenReductionConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_token_reduction_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::TokenReductionConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TokenReductionConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TokenReductionConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_to_json(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TokenReductionConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_free(ptr: *mut kreuzberg::TokenReductionConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `level` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_level(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> *mut kreuzberg::ReductionLevel {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.level))
}

/// Get the `language_hint` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_language_hint(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language_hint {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `preserve_markdown` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_preserve_markdown(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.preserve_markdown as i32
}

/// Get the `preserve_code` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_preserve_code(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.preserve_code as i32
}

/// Get the `semantic_threshold` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_semantic_threshold(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.semantic_threshold
}

/// Get the `enable_parallel` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_enable_parallel(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enable_parallel as i32
}

/// Get the `use_simd` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_use_simd(ptr: *const kreuzberg::TokenReductionConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.use_simd as i32
}

/// Get the `custom_stopwords` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_custom_stopwords(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.custom_stopwords {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `preserve_patterns` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_preserve_patterns(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.preserve_patterns) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `target_reduction` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_target_reduction(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.target_reduction {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `enable_semantic_clustering` field from a `TokenReductionConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_enable_semantic_clustering(
    ptr: *const kreuzberg::TokenReductionConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enable_semantic_clustering as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_token_reduction_config_default() -> *mut kreuzberg::TokenReductionConfig {
    clear_last_error();
    let result = kreuzberg::TokenReductionConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `PdfAnnotation` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pdf_annotation_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_from_json(json: *const c_char) -> *mut kreuzberg::PdfAnnotation {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PdfAnnotation>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PdfAnnotation` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_to_json(ptr: *const kreuzberg::PdfAnnotation) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PdfAnnotation` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_free(ptr: *mut kreuzberg::PdfAnnotation) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `annotation_type` field from a `PdfAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_annotation_type(
    ptr: *const kreuzberg::PdfAnnotation,
) -> *mut kreuzberg::PdfAnnotationType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.annotation_type))
}

/// Get the `content` field from a `PdfAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_content(
    ptr: *const kreuzberg::PdfAnnotation,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.content {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `page_number` field from a `PdfAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_page_number(ptr: *const kreuzberg::PdfAnnotation) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_number
}

/// Get the `bounding_box` field from a `PdfAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_bounding_box(
    ptr: *const kreuzberg::PdfAnnotation,
) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bounding_box {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Create a `DjotContent` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_djot_content_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_from_json(json: *const c_char) -> *mut kreuzberg::DjotContent {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DjotContent>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DjotContent` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_to_json(ptr: *const kreuzberg::DjotContent) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DjotContent` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_free(ptr: *mut kreuzberg::DjotContent) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `plain_text` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_plain_text(
    ptr: *const kreuzberg::DjotContent,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.plain_text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `blocks` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_blocks(ptr: *const kreuzberg::DjotContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.blocks) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_metadata(
    ptr: *const kreuzberg::DjotContent,
) -> *mut kreuzberg::Metadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.metadata.clone()))
}

/// Get the `tables` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_tables(ptr: *const kreuzberg::DjotContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `images` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_images(ptr: *const kreuzberg::DjotContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.images) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `links` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_links(ptr: *const kreuzberg::DjotContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.links) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `footnotes` field from a `DjotContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_content_footnotes(ptr: *const kreuzberg::DjotContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.footnotes) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `FormattedBlock` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_formatted_block_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_from_json(json: *const c_char) -> *mut kreuzberg::FormattedBlock {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::FormattedBlock>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `FormattedBlock` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_to_json(ptr: *const kreuzberg::FormattedBlock) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `FormattedBlock` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_free(ptr: *mut kreuzberg::FormattedBlock) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `block_type` field from a `FormattedBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_block_type(
    ptr: *const kreuzberg::FormattedBlock,
) -> *mut kreuzberg::BlockType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.block_type.clone()))
}

/// Get the `level` field from a `FormattedBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_level(ptr: *const kreuzberg::FormattedBlock) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.level {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `inline_content` field from a `FormattedBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_inline_content(
    ptr: *const kreuzberg::FormattedBlock,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.inline_content) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `language` field from a `FormattedBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_language(
    ptr: *const kreuzberg::FormattedBlock,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `code` field from a `FormattedBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_code(
    ptr: *const kreuzberg::FormattedBlock,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.code {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `children` field from a `FormattedBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_formatted_block_children(
    ptr: *const kreuzberg::FormattedBlock,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.children) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `InlineElement` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_inline_element_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_element_from_json(json: *const c_char) -> *mut kreuzberg::InlineElement {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::InlineElement>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `InlineElement` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_element_to_json(ptr: *const kreuzberg::InlineElement) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `InlineElement` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_element_free(ptr: *mut kreuzberg::InlineElement) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `element_type` field from a `InlineElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_element_element_type(
    ptr: *const kreuzberg::InlineElement,
) -> *mut kreuzberg::InlineType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.element_type.clone()))
}

/// Get the `content` field from a `InlineElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_element_content(
    ptr: *const kreuzberg::InlineElement,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `InlineElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_element_metadata(
    ptr: *const kreuzberg::InlineElement,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.metadata {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `DjotImage` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_djot_image_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_image_from_json(json: *const c_char) -> *mut kreuzberg::DjotImage {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DjotImage>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DjotImage` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_image_to_json(ptr: *const kreuzberg::DjotImage) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DjotImage` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_image_free(ptr: *mut kreuzberg::DjotImage) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `src` field from a `DjotImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_image_src(ptr: *const kreuzberg::DjotImage) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.src.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `alt` field from a `DjotImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_image_alt(ptr: *const kreuzberg::DjotImage) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.alt.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `title` field from a `DjotImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_image_title(ptr: *const kreuzberg::DjotImage) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `DjotLink` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_djot_link_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_link_from_json(json: *const c_char) -> *mut kreuzberg::DjotLink {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DjotLink>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DjotLink` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_link_to_json(ptr: *const kreuzberg::DjotLink) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DjotLink` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_link_free(ptr: *mut kreuzberg::DjotLink) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `url` field from a `DjotLink`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_link_url(ptr: *const kreuzberg::DjotLink) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.url.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `text` field from a `DjotLink`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_link_text(ptr: *const kreuzberg::DjotLink) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `title` field from a `DjotLink`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_djot_link_title(ptr: *const kreuzberg::DjotLink) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `Footnote` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_footnote_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_footnote_from_json(json: *const c_char) -> *mut kreuzberg::Footnote {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::Footnote>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `Footnote` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_footnote_to_json(ptr: *const kreuzberg::Footnote) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `Footnote` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_footnote_free(ptr: *mut kreuzberg::Footnote) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `label` field from a `Footnote`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_footnote_label(ptr: *const kreuzberg::Footnote) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.label.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `content` field from a `Footnote`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_footnote_content(ptr: *const kreuzberg::Footnote) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.content) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `DocumentStructure` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_document_structure_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_from_json(
    json: *const c_char,
) -> *mut kreuzberg::DocumentStructure {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DocumentStructure>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DocumentStructure` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_to_json(ptr: *const kreuzberg::DocumentStructure) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DocumentStructure` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_free(ptr: *mut kreuzberg::DocumentStructure) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `nodes` field from a `DocumentStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_nodes(
    ptr: *const kreuzberg::DocumentStructure,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.nodes) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `source_format` field from a `DocumentStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_source_format(
    ptr: *const kreuzberg::DocumentStructure,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.source_format {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `relationships` field from a `DocumentStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_relationships(
    ptr: *const kreuzberg::DocumentStructure,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.relationships) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `node_types` field from a `DocumentStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_node_types(
    ptr: *const kreuzberg::DocumentStructure,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.node_types) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Compute and populate the `node_types` field from the current `nodes`.
///
/// Call this after all nodes have been added to the structure. Internal
/// construction paths (builder, derivation) call this automatically.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::types::document_structure::{DocumentStructure, DocumentNode, NodeContent, NodeId};
///
/// let mut structure = DocumentStructure {
///     nodes: vec![DocumentNode {
///         id: NodeId::from("n1"),
///         content: NodeContent::Paragraph { text: "Hello".into() },
///         parent: None,
///         children: vec![],
///         content_layer: Default::default(),
///         page: None,
///         page_end: None,
///         bbox: None,
///         annotations: vec![],
///         attributes: None,
///     }],
///     source_format: None,
///     relationships: vec![],
///     node_types: vec![],
/// };
/// structure.finalize_node_types();
/// assert!(structure.node_types.contains(&"paragraph".to_string()));
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_finalize_node_types(this: *mut kreuzberg::DocumentStructure) {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return;
    }
    // SAFETY: null check above guarantees this is a valid pointer; caller ensures exclusive access.
    let obj = unsafe { &mut *this };

    let result = obj.finalize_node_types();
}

/// Check if the document structure is empty.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_is_empty(this: *const kreuzberg::DocumentStructure) -> i32 {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    let result = obj.is_empty();
    if result {
        1
    } else {
        0
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_structure_default() -> *mut kreuzberg::DocumentStructure {
    clear_last_error();
    let result = kreuzberg::DocumentStructure::default();
    Box::into_raw(Box::new(result))
}

/// Create a `DocumentRelationship` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_document_relationship_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_relationship_from_json(
    json: *const c_char,
) -> *mut kreuzberg::DocumentRelationship {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DocumentRelationship>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DocumentRelationship` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_relationship_to_json(
    ptr: *const kreuzberg::DocumentRelationship,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DocumentRelationship` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_relationship_free(ptr: *mut kreuzberg::DocumentRelationship) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `source` field from a `DocumentRelationship`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_relationship_source(ptr: *const kreuzberg::DocumentRelationship) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.source.0
}

/// Get the `target` field from a `DocumentRelationship`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_relationship_target(ptr: *const kreuzberg::DocumentRelationship) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.target.0
}

/// Get the `kind` field from a `DocumentRelationship`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_relationship_kind(
    ptr: *const kreuzberg::DocumentRelationship,
) -> *mut kreuzberg::RelationshipKind {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.kind))
}

/// Create a `DocumentNode` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_document_node_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_from_json(json: *const c_char) -> *mut kreuzberg::DocumentNode {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DocumentNode>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DocumentNode` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_to_json(ptr: *const kreuzberg::DocumentNode) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DocumentNode` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_free(ptr: *mut kreuzberg::DocumentNode) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_content(
    ptr: *const kreuzberg::DocumentNode,
) -> *mut kreuzberg::NodeContent {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.content.clone()))
}

/// Get the `parent` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_parent(ptr: *const kreuzberg::DocumentNode) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.parent {
        Some(val) => val.0,
        None => 0,
    }
}

/// Get the `children` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_children(
    ptr: *const kreuzberg::DocumentNode,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.children) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `content_layer` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_content_layer(
    ptr: *const kreuzberg::DocumentNode,
) -> *mut kreuzberg::ContentLayer {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.content_layer))
}

/// Get the `page` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_page(ptr: *const kreuzberg::DocumentNode) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `page_end` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_page_end(ptr: *const kreuzberg::DocumentNode) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page_end {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `bbox` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_bbox(
    ptr: *const kreuzberg::DocumentNode,
) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bbox {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `annotations` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_annotations(
    ptr: *const kreuzberg::DocumentNode,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.annotations) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `attributes` field from a `DocumentNode`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_node_attributes(
    ptr: *const kreuzberg::DocumentNode,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.attributes {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `TableGrid` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_table_grid_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_grid_from_json(json: *const c_char) -> *mut kreuzberg::TableGrid {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TableGrid>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TableGrid` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_grid_to_json(ptr: *const kreuzberg::TableGrid) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TableGrid` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_grid_free(ptr: *mut kreuzberg::TableGrid) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `rows` field from a `TableGrid`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_grid_rows(ptr: *const kreuzberg::TableGrid) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.rows
}

/// Get the `cols` field from a `TableGrid`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_grid_cols(ptr: *const kreuzberg::TableGrid) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.cols
}

/// Get the `cells` field from a `TableGrid`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_grid_cells(ptr: *const kreuzberg::TableGrid) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cells) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `GridCell` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_grid_cell_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_from_json(json: *const c_char) -> *mut kreuzberg::GridCell {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::GridCell>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `GridCell` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_to_json(ptr: *const kreuzberg::GridCell) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `GridCell` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_free(ptr: *mut kreuzberg::GridCell) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_content(ptr: *const kreuzberg::GridCell) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `row` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_row(ptr: *const kreuzberg::GridCell) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.row
}

/// Get the `col` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_col(ptr: *const kreuzberg::GridCell) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.col
}

/// Get the `row_span` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_row_span(ptr: *const kreuzberg::GridCell) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.row_span
}

/// Get the `col_span` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_col_span(ptr: *const kreuzberg::GridCell) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.col_span
}

/// Get the `is_header` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_is_header(ptr: *const kreuzberg::GridCell) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.is_header as i32
}

/// Get the `bbox` field from a `GridCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_grid_cell_bbox(ptr: *const kreuzberg::GridCell) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bbox {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Create a `TextAnnotation` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_text_annotation_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_annotation_from_json(json: *const c_char) -> *mut kreuzberg::TextAnnotation {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TextAnnotation>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TextAnnotation` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_annotation_to_json(ptr: *const kreuzberg::TextAnnotation) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TextAnnotation` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_annotation_free(ptr: *mut kreuzberg::TextAnnotation) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `start` field from a `TextAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_annotation_start(ptr: *const kreuzberg::TextAnnotation) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.start
}

/// Get the `end` field from a `TextAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_annotation_end(ptr: *const kreuzberg::TextAnnotation) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.end
}

/// Get the `kind` field from a `TextAnnotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_annotation_kind(
    ptr: *const kreuzberg::TextAnnotation,
) -> *mut kreuzberg::AnnotationKind {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.kind.clone()))
}

/// Create a `ExtractionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_extraction_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ExtractionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExtractionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExtractionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_to_json(ptr: *const kreuzberg::ExtractionResult) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExtractionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_free(ptr: *mut kreuzberg::ExtractionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_content(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `mime_type` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_mime_type(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mime_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_metadata(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut kreuzberg::Metadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.metadata.clone()))
}

/// Get the `extraction_method` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_extraction_method(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut kreuzberg::ExtractionMethod {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.extraction_method {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `tables` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_tables(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `detected_languages` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_detected_languages(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.detected_languages {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `chunks` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_chunks(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.chunks {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `images` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_images(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.images {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `pages` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_pages(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pages {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `elements` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_elements(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.elements {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `djot_content` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_djot_content(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut kreuzberg::DjotContent {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.djot_content {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `ocr_elements` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_ocr_elements(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.ocr_elements {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `document` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_document(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut kreuzberg::DocumentStructure {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.document {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `extracted_keywords` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_extracted_keywords(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.extracted_keywords {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `quality_score` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_quality_score(ptr: *const kreuzberg::ExtractionResult) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.quality_score {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `processing_warnings` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_processing_warnings(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.processing_warnings) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `annotations` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_annotations(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.annotations {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `children` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_children(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.children {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `uris` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_uris(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.uris {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `revisions` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_revisions(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.revisions {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `structured_output` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_structured_output(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.structured_output {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `code_intelligence` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_code_intelligence(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.code_intelligence {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `llm_usage` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_llm_usage(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.llm_usage {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `formatted_content` field from a `ExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_formatted_content(
    ptr: *const kreuzberg::ExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.formatted_content {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Convert from an OCR result.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_result_from_ocr(
    ocr: *const kreuzberg::OcrExtractionResult,
) -> *mut kreuzberg::ExtractionResult {
    clear_last_error();
    if ocr.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'ocr'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ocr is a valid pointer.
    let ocr_rs = unsafe { &*ocr }.clone();
    let result = kreuzberg::ExtractionResult::from_ocr(ocr_rs);
    Box::into_raw(Box::new(result))
}

/// Create a `ArchiveEntry` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_archive_entry_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_entry_from_json(json: *const c_char) -> *mut kreuzberg::ArchiveEntry {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ArchiveEntry>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ArchiveEntry` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_entry_to_json(ptr: *const kreuzberg::ArchiveEntry) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ArchiveEntry` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_entry_free(ptr: *mut kreuzberg::ArchiveEntry) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `path` field from a `ArchiveEntry`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_entry_path(ptr: *const kreuzberg::ArchiveEntry) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.path.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `mime_type` field from a `ArchiveEntry`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_entry_mime_type(
    ptr: *const kreuzberg::ArchiveEntry,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mime_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `result` field from a `ArchiveEntry`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_entry_result(
    ptr: *const kreuzberg::ArchiveEntry,
) -> *mut kreuzberg::ExtractionResult {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new((*obj.result).clone()))
}

/// Create a `ProcessingWarning` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_processing_warning_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_warning_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ProcessingWarning {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ProcessingWarning>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ProcessingWarning` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_warning_to_json(ptr: *const kreuzberg::ProcessingWarning) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ProcessingWarning` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_warning_free(ptr: *mut kreuzberg::ProcessingWarning) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `source` field from a `ProcessingWarning`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_warning_source(
    ptr: *const kreuzberg::ProcessingWarning,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.source.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `message` field from a `ProcessingWarning`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_warning_message(
    ptr: *const kreuzberg::ProcessingWarning,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.message.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `LlmUsage` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_llm_usage_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_from_json(json: *const c_char) -> *mut kreuzberg::LlmUsage {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LlmUsage>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LlmUsage` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_to_json(ptr: *const kreuzberg::LlmUsage) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LlmUsage` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_free(ptr: *mut kreuzberg::LlmUsage) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `model` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_model(ptr: *const kreuzberg::LlmUsage) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.model.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `source` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_source(ptr: *const kreuzberg::LlmUsage) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.source.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `input_tokens` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_input_tokens(ptr: *const kreuzberg::LlmUsage) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.input_tokens {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `output_tokens` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_output_tokens(ptr: *const kreuzberg::LlmUsage) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.output_tokens {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `total_tokens` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_total_tokens(ptr: *const kreuzberg::LlmUsage) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.total_tokens {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `estimated_cost` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_estimated_cost(ptr: *const kreuzberg::LlmUsage) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.estimated_cost {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `finish_reason` field from a `LlmUsage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_llm_usage_finish_reason(ptr: *const kreuzberg::LlmUsage) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.finish_reason {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `Chunk` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_chunk_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_from_json(json: *const c_char) -> *mut kreuzberg::Chunk {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::Chunk>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `Chunk` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_to_json(ptr: *const kreuzberg::Chunk) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `Chunk` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_free(ptr: *mut kreuzberg::Chunk) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `Chunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_content(ptr: *const kreuzberg::Chunk) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `chunk_type` field from a `Chunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_chunk_type(ptr: *const kreuzberg::Chunk) -> *mut kreuzberg::ChunkType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.chunk_type))
}

/// Get the `embedding` field from a `Chunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_embedding(ptr: *const kreuzberg::Chunk) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.embedding {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `Chunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata(ptr: *const kreuzberg::Chunk) -> *mut kreuzberg::ChunkMetadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.metadata.clone()))
}

/// Create a `HeadingContext` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_heading_context_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_context_from_json(json: *const c_char) -> *mut kreuzberg::HeadingContext {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HeadingContext>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HeadingContext` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_context_to_json(ptr: *const kreuzberg::HeadingContext) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HeadingContext` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_context_free(ptr: *mut kreuzberg::HeadingContext) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `headings` field from a `HeadingContext`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_context_headings(
    ptr: *const kreuzberg::HeadingContext,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.headings) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `HeadingLevel` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_heading_level_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_level_from_json(json: *const c_char) -> *mut kreuzberg::HeadingLevel {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HeadingLevel>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HeadingLevel` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_level_to_json(ptr: *const kreuzberg::HeadingLevel) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HeadingLevel` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_level_free(ptr: *mut kreuzberg::HeadingLevel) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `level` field from a `HeadingLevel`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_level_level(ptr: *const kreuzberg::HeadingLevel) -> u8 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.level
}

/// Get the `text` field from a `HeadingLevel`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_heading_level_text(ptr: *const kreuzberg::HeadingLevel) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `ChunkMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_chunk_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_from_json(json: *const c_char) -> *mut kreuzberg::ChunkMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ChunkMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ChunkMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_to_json(ptr: *const kreuzberg::ChunkMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ChunkMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_free(ptr: *mut kreuzberg::ChunkMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `byte_start` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_byte_start(ptr: *const kreuzberg::ChunkMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.byte_start
}

/// Get the `byte_end` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_byte_end(ptr: *const kreuzberg::ChunkMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.byte_end
}

/// Get the `token_count` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_token_count(ptr: *const kreuzberg::ChunkMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.token_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `chunk_index` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_chunk_index(ptr: *const kreuzberg::ChunkMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.chunk_index
}

/// Get the `total_chunks` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_total_chunks(ptr: *const kreuzberg::ChunkMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.total_chunks
}

/// Get the `first_page` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_first_page(ptr: *const kreuzberg::ChunkMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.first_page {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `last_page` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_last_page(ptr: *const kreuzberg::ChunkMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.last_page {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `heading_context` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_heading_context(
    ptr: *const kreuzberg::ChunkMetadata,
) -> *mut kreuzberg::HeadingContext {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.heading_context {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `image_indices` field from a `ChunkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_metadata_image_indices(
    ptr: *const kreuzberg::ChunkMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.image_indices) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `ExtractedImage` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_extracted_image_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_from_json(json: *const c_char) -> *mut kreuzberg::ExtractedImage {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExtractedImage>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExtractedImage` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_to_json(ptr: *const kreuzberg::ExtractedImage) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExtractedImage` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_free(ptr: *mut kreuzberg::ExtractedImage) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `data` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_data(
    ptr: *const kreuzberg::ExtractedImage,
    out_len: *mut usize,
) -> *mut u8 {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    let data = &obj.data;
    if !out_len.is_null() {
        // SAFETY: null check above guarantees out_len is a valid pointer.
        unsafe {
            *out_len = data.len();
        }
    }
    data.as_ptr() as *mut u8
}

/// Get the `format` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_format(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `image_index` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_image_index(ptr: *const kreuzberg::ExtractedImage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.image_index
}

/// Get the `page_number` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_page_number(ptr: *const kreuzberg::ExtractedImage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page_number {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `width` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_width(ptr: *const kreuzberg::ExtractedImage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.width {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `height` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_height(ptr: *const kreuzberg::ExtractedImage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.height {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `colorspace` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_colorspace(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.colorspace {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `bits_per_component` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_bits_per_component(ptr: *const kreuzberg::ExtractedImage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bits_per_component {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `is_mask` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_is_mask(ptr: *const kreuzberg::ExtractedImage) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.is_mask as i32
}

/// Get the `description` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_description(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.description {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `ocr_result` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_ocr_result(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut kreuzberg::ExtractionResult {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.ocr_result {
        Some(val) => Box::into_raw(Box::new((**val).clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `bounding_box` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_bounding_box(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bounding_box {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `source_path` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_source_path(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.source_path {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `image_kind` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_image_kind(
    ptr: *const kreuzberg::ExtractedImage,
) -> *mut kreuzberg::ImageKind {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.image_kind {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `kind_confidence` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_kind_confidence(ptr: *const kreuzberg::ExtractedImage) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.kind_confidence {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `cluster_id` field from a `ExtractedImage`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_image_cluster_id(ptr: *const kreuzberg::ExtractedImage) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cluster_id {
        Some(val) => *val,
        None => 0,
    }
}

/// Create a `BoundingBox` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_bounding_box_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_from_json(json: *const c_char) -> *mut kreuzberg::BoundingBox {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::BoundingBox>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `BoundingBox` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_to_json(ptr: *const kreuzberg::BoundingBox) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `BoundingBox` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_free(ptr: *mut kreuzberg::BoundingBox) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `x0` field from a `BoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_x0(ptr: *const kreuzberg::BoundingBox) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.x0
}

/// Get the `y0` field from a `BoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_y0(ptr: *const kreuzberg::BoundingBox) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.y0
}

/// Get the `x1` field from a `BoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_x1(ptr: *const kreuzberg::BoundingBox) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.x1
}

/// Get the `y1` field from a `BoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bounding_box_y1(ptr: *const kreuzberg::BoundingBox) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.y1
}

/// Create a `ElementMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_element_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_from_json(json: *const c_char) -> *mut kreuzberg::ElementMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ElementMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ElementMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_to_json(ptr: *const kreuzberg::ElementMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ElementMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_free(ptr: *mut kreuzberg::ElementMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `page_number` field from a `ElementMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_page_number(ptr: *const kreuzberg::ElementMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page_number {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `filename` field from a `ElementMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_filename(
    ptr: *const kreuzberg::ElementMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.filename {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `coordinates` field from a `ElementMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_coordinates(
    ptr: *const kreuzberg::ElementMetadata,
) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.coordinates {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `element_index` field from a `ElementMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_element_index(ptr: *const kreuzberg::ElementMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.element_index {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `additional` field from a `ElementMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata_additional(
    ptr: *const kreuzberg::ElementMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.additional) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `Element` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_element_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_from_json(json: *const c_char) -> *mut kreuzberg::Element {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::Element>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `Element` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_to_json(ptr: *const kreuzberg::Element) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `Element` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_free(ptr: *mut kreuzberg::Element) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `element_type` field from a `Element`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_element_type(ptr: *const kreuzberg::Element) -> *mut kreuzberg::ElementType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.element_type))
}

/// Get the `text` field from a `Element`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_text(ptr: *const kreuzberg::Element) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `Element`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_metadata(ptr: *const kreuzberg::Element) -> *mut kreuzberg::ElementMetadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.metadata.clone()))
}

/// Create a `ExcelWorkbook` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_excel_workbook_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_workbook_from_json(json: *const c_char) -> *mut kreuzberg::ExcelWorkbook {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExcelWorkbook>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExcelWorkbook` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_workbook_to_json(ptr: *const kreuzberg::ExcelWorkbook) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExcelWorkbook` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_workbook_free(ptr: *mut kreuzberg::ExcelWorkbook) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `sheets` field from a `ExcelWorkbook`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_workbook_sheets(
    ptr: *const kreuzberg::ExcelWorkbook,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.sheets) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `ExcelWorkbook`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_workbook_metadata(
    ptr: *const kreuzberg::ExcelWorkbook,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.metadata) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `revisions` field from a `ExcelWorkbook`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_workbook_revisions(
    ptr: *const kreuzberg::ExcelWorkbook,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.revisions {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `ExcelSheet` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_excel_sheet_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_from_json(json: *const c_char) -> *mut kreuzberg::ExcelSheet {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExcelSheet>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExcelSheet` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_to_json(ptr: *const kreuzberg::ExcelSheet) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExcelSheet` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_free(ptr: *mut kreuzberg::ExcelSheet) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `name` field from a `ExcelSheet`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_name(ptr: *const kreuzberg::ExcelSheet) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `markdown` field from a `ExcelSheet`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_markdown(ptr: *const kreuzberg::ExcelSheet) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.markdown.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `row_count` field from a `ExcelSheet`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_row_count(ptr: *const kreuzberg::ExcelSheet) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.row_count
}

/// Get the `col_count` field from a `ExcelSheet`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_col_count(ptr: *const kreuzberg::ExcelSheet) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.col_count
}

/// Get the `cell_count` field from a `ExcelSheet`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_cell_count(ptr: *const kreuzberg::ExcelSheet) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.cell_count
}

/// Get the `table_cells` field from a `ExcelSheet`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_sheet_table_cells(ptr: *const kreuzberg::ExcelSheet) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.table_cells {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `XmlExtractionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_xml_extraction_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_extraction_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::XmlExtractionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::XmlExtractionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `XmlExtractionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_extraction_result_to_json(
    ptr: *const kreuzberg::XmlExtractionResult,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `XmlExtractionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_extraction_result_free(ptr: *mut kreuzberg::XmlExtractionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `XmlExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_extraction_result_content(
    ptr: *const kreuzberg::XmlExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `element_count` field from a `XmlExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_extraction_result_element_count(
    ptr: *const kreuzberg::XmlExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.element_count
}

/// Get the `unique_elements` field from a `XmlExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_extraction_result_unique_elements(
    ptr: *const kreuzberg::XmlExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.unique_elements) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `TextExtractionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_text_extraction_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::TextExtractionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TextExtractionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TextExtractionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_to_json(
    ptr: *const kreuzberg::TextExtractionResult,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TextExtractionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_free(ptr: *mut kreuzberg::TextExtractionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `TextExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_content(
    ptr: *const kreuzberg::TextExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `line_count` field from a `TextExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_line_count(
    ptr: *const kreuzberg::TextExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.line_count
}

/// Get the `word_count` field from a `TextExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_word_count(
    ptr: *const kreuzberg::TextExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.word_count
}

/// Get the `character_count` field from a `TextExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_character_count(
    ptr: *const kreuzberg::TextExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.character_count
}

/// Get the `headers` field from a `TextExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_extraction_result_headers(
    ptr: *const kreuzberg::TextExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.headers {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `PptxExtractionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pptx_extraction_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::PptxExtractionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PptxExtractionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PptxExtractionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_to_json(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PptxExtractionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_free(ptr: *mut kreuzberg::PptxExtractionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_content(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_metadata(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut kreuzberg::PptxMetadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.metadata.clone()))
}

/// Get the `slide_count` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_slide_count(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.slide_count
}

/// Get the `image_count` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_image_count(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.image_count
}

/// Get the `table_count` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_table_count(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.table_count
}

/// Get the `images` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_images(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.images) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `page_structure` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_page_structure(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut kreuzberg::PageStructure {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page_structure {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `page_contents` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_page_contents(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page_contents {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `document` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_document(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut kreuzberg::DocumentStructure {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.document {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `office_metadata` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_office_metadata(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.office_metadata) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `revisions` field from a `PptxExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_extraction_result_revisions(
    ptr: *const kreuzberg::PptxExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.revisions {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `EmailExtractionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_email_extraction_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::EmailExtractionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmailExtractionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmailExtractionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_to_json(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmailExtractionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_free(ptr: *mut kreuzberg::EmailExtractionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `subject` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_subject(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.subject {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `from_email` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_from_email(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.from_email {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `to_emails` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_to_emails(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.to_emails) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `cc_emails` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_cc_emails(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cc_emails) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `bcc_emails` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_bcc_emails(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.bcc_emails) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `date` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_date(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.date {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `message_id` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_message_id(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.message_id {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `plain_text` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_plain_text(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.plain_text {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `html_content` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_html_content(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.html_content {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `content` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_content(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `attachments` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_attachments(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.attachments) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `EmailExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_extraction_result_metadata(
    ptr: *const kreuzberg::EmailExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.metadata) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `EmailAttachment` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_email_attachment_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_from_json(json: *const c_char) -> *mut kreuzberg::EmailAttachment {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmailAttachment>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmailAttachment` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_to_json(ptr: *const kreuzberg::EmailAttachment) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmailAttachment` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_free(ptr: *mut kreuzberg::EmailAttachment) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `name` field from a `EmailAttachment`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_name(
    ptr: *const kreuzberg::EmailAttachment,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.name {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `filename` field from a `EmailAttachment`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_filename(
    ptr: *const kreuzberg::EmailAttachment,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.filename {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `mime_type` field from a `EmailAttachment`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_mime_type(
    ptr: *const kreuzberg::EmailAttachment,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.mime_type {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `size` field from a `EmailAttachment`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_size(ptr: *const kreuzberg::EmailAttachment) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.size {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `is_image` field from a `EmailAttachment`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_is_image(ptr: *const kreuzberg::EmailAttachment) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.is_image as i32
}

/// Get the `data` field from a `EmailAttachment`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_attachment_data(
    ptr: *const kreuzberg::EmailAttachment,
    out_len: *mut usize,
) -> *mut u8 {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.data {
        Some(val) => {
            if !out_len.is_null() {
                // SAFETY: null check above guarantees out_len is a valid pointer.
                unsafe {
                    *out_len = val.len();
                }
            }
            val.as_ptr() as *mut u8
        }
        None => {
            if !out_len.is_null() {
                // SAFETY: null check above guarantees out_len is a valid pointer.
                unsafe {
                    *out_len = 0;
                }
            }
            std::ptr::null_mut()
        }
    }
}

/// Create a `OcrExtractionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_extraction_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OcrExtractionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrExtractionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrExtractionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_to_json(
    ptr: *const kreuzberg::OcrExtractionResult,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrExtractionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_free(ptr: *mut kreuzberg::OcrExtractionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `OcrExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_content(
    ptr: *const kreuzberg::OcrExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `mime_type` field from a `OcrExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_mime_type(
    ptr: *const kreuzberg::OcrExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mime_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata` field from a `OcrExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_metadata(
    ptr: *const kreuzberg::OcrExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.metadata) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tables` field from a `OcrExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_tables(
    ptr: *const kreuzberg::OcrExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `ocr_elements` field from a `OcrExtractionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_extraction_result_ocr_elements(
    ptr: *const kreuzberg::OcrExtractionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.ocr_elements {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `OcrTable` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_table_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_from_json(json: *const c_char) -> *mut kreuzberg::OcrTable {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrTable>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrTable` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_to_json(ptr: *const kreuzberg::OcrTable) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrTable` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_free(ptr: *mut kreuzberg::OcrTable) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `cells` field from a `OcrTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_cells(ptr: *const kreuzberg::OcrTable) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cells) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `markdown` field from a `OcrTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_markdown(ptr: *const kreuzberg::OcrTable) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.markdown.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `page_number` field from a `OcrTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_page_number(ptr: *const kreuzberg::OcrTable) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_number
}

/// Get the `bounding_box` field from a `OcrTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box(
    ptr: *const kreuzberg::OcrTable,
) -> *mut kreuzberg::OcrTableBoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bounding_box {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Create a `OcrTableBoundingBox` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_table_bounding_box_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OcrTableBoundingBox {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrTableBoundingBox>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrTableBoundingBox` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_to_json(
    ptr: *const kreuzberg::OcrTableBoundingBox,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrTableBoundingBox` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_free(ptr: *mut kreuzberg::OcrTableBoundingBox) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `left` field from a `OcrTableBoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_left(ptr: *const kreuzberg::OcrTableBoundingBox) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.left
}

/// Get the `top` field from a `OcrTableBoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_top(ptr: *const kreuzberg::OcrTableBoundingBox) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.top
}

/// Get the `right` field from a `OcrTableBoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_right(ptr: *const kreuzberg::OcrTableBoundingBox) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.right
}

/// Get the `bottom` field from a `OcrTableBoundingBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_table_bounding_box_bottom(ptr: *const kreuzberg::OcrTableBoundingBox) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.bottom
}

/// Create a `ImagePreprocessingConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_image_preprocessing_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ImagePreprocessingConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ImagePreprocessingConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ImagePreprocessingConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_to_json(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ImagePreprocessingConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_free(ptr: *mut kreuzberg::ImagePreprocessingConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `target_dpi` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_target_dpi(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.target_dpi
}

/// Get the `auto_rotate` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_auto_rotate(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.auto_rotate as i32
}

/// Get the `deskew` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_deskew(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.deskew as i32
}

/// Get the `denoise` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_denoise(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.denoise as i32
}

/// Get the `contrast_enhance` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_contrast_enhance(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.contrast_enhance as i32
}

/// Get the `binarization_method` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_binarization_method(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.binarization_method.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `invert_colors` field from a `ImagePreprocessingConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_invert_colors(
    ptr: *const kreuzberg::ImagePreprocessingConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.invert_colors as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_config_default() -> *mut kreuzberg::ImagePreprocessingConfig {
    clear_last_error();
    let result = kreuzberg::ImagePreprocessingConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `TesseractConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_tesseract_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_from_json(json: *const c_char) -> *mut kreuzberg::TesseractConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TesseractConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TesseractConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_to_json(ptr: *const kreuzberg::TesseractConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TesseractConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_free(ptr: *mut kreuzberg::TesseractConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `language` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_language(
    ptr: *const kreuzberg::TesseractConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.language.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `psm` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_psm(ptr: *const kreuzberg::TesseractConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.psm
}

/// Get the `output_format` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_output_format(
    ptr: *const kreuzberg::TesseractConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.output_format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `oem` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_oem(ptr: *const kreuzberg::TesseractConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.oem
}

/// Get the `min_confidence` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_min_confidence(ptr: *const kreuzberg::TesseractConfig) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_confidence
}

/// Get the `preprocessing` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_preprocessing(
    ptr: *const kreuzberg::TesseractConfig,
) -> *mut kreuzberg::ImagePreprocessingConfig {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.preprocessing {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `enable_table_detection` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_enable_table_detection(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enable_table_detection as i32
}

/// Get the `table_min_confidence` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_table_min_confidence(
    ptr: *const kreuzberg::TesseractConfig,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.table_min_confidence
}

/// Get the `table_column_threshold` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_table_column_threshold(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.table_column_threshold
}

/// Get the `table_row_threshold_ratio` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_table_row_threshold_ratio(
    ptr: *const kreuzberg::TesseractConfig,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.table_row_threshold_ratio
}

/// Get the `use_cache` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_use_cache(ptr: *const kreuzberg::TesseractConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.use_cache as i32
}

/// Get the `classify_use_pre_adapted_templates` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_classify_use_pre_adapted_templates(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.classify_use_pre_adapted_templates as i32
}

/// Get the `language_model_ngram_on` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_language_model_ngram_on(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.language_model_ngram_on as i32
}

/// Get the `tessedit_dont_blkrej_good_wds` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_tessedit_dont_blkrej_good_wds(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.tessedit_dont_blkrej_good_wds as i32
}

/// Get the `tessedit_dont_rowrej_good_wds` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_tessedit_dont_rowrej_good_wds(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.tessedit_dont_rowrej_good_wds as i32
}

/// Get the `tessedit_enable_dict_correction` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_tessedit_enable_dict_correction(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.tessedit_enable_dict_correction as i32
}

/// Get the `tessedit_char_whitelist` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_tessedit_char_whitelist(
    ptr: *const kreuzberg::TesseractConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.tessedit_char_whitelist.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tessedit_char_blacklist` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_tessedit_char_blacklist(
    ptr: *const kreuzberg::TesseractConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.tessedit_char_blacklist.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tessedit_use_primary_params_model` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_tessedit_use_primary_params_model(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.tessedit_use_primary_params_model as i32
}

/// Get the `textord_space_size_is_variable` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_textord_space_size_is_variable(
    ptr: *const kreuzberg::TesseractConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.textord_space_size_is_variable as i32
}

/// Get the `thresholding_method` field from a `TesseractConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_thresholding_method(ptr: *const kreuzberg::TesseractConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.thresholding_method as i32
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_tesseract_config_default() -> *mut kreuzberg::TesseractConfig {
    clear_last_error();
    let result = kreuzberg::TesseractConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `ImagePreprocessingMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_image_preprocessing_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ImagePreprocessingMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ImagePreprocessingMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ImagePreprocessingMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_to_json(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ImagePreprocessingMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_free(ptr: *mut kreuzberg::ImagePreprocessingMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `target_dpi` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_target_dpi(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.target_dpi
}

/// Get the `scale_factor` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_scale_factor(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.scale_factor
}

/// Get the `auto_adjusted` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_auto_adjusted(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.auto_adjusted as i32
}

/// Get the `final_dpi` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_final_dpi(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.final_dpi
}

/// Get the `resample_method` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_resample_method(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.resample_method.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `dimension_clamped` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_dimension_clamped(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.dimension_clamped as i32
}

/// Get the `calculated_dpi` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_calculated_dpi(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.calculated_dpi {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `skipped_resize` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_skipped_resize(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.skipped_resize as i32
}

/// Get the `resize_error` field from a `ImagePreprocessingMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_preprocessing_metadata_resize_error(
    ptr: *const kreuzberg::ImagePreprocessingMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.resize_error {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `Metadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_from_json(json: *const c_char) -> *mut kreuzberg::Metadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::Metadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `Metadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_to_json(ptr: *const kreuzberg::Metadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `Metadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_free(ptr: *mut kreuzberg::Metadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `title` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_title(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `subject` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_subject(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.subject {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `authors` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_authors(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.authors {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `keywords` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_keywords(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.keywords {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `language` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_language(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `created_at` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_created_at(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.created_at {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `modified_at` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_modified_at(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.modified_at {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `created_by` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_created_by(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.created_by {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `modified_by` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_modified_by(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.modified_by {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `pages` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_pages(ptr: *const kreuzberg::Metadata) -> *mut kreuzberg::PageStructure {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pages {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `format` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_format(ptr: *const kreuzberg::Metadata) -> *mut kreuzberg::FormatMetadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.format {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `image_preprocessing` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_image_preprocessing(
    ptr: *const kreuzberg::Metadata,
) -> *mut kreuzberg::ImagePreprocessingMetadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.image_preprocessing {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `json_schema` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_json_schema(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.json_schema {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `error` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_error(ptr: *const kreuzberg::Metadata) -> *mut kreuzberg::ErrorMetadata {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.error {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `extraction_duration_ms` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_extraction_duration_ms(ptr: *const kreuzberg::Metadata) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.extraction_duration_ms {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `category` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_category(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.category {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `tags` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_tags(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.tags {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `document_version` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_document_version(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.document_version {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `abstract_text` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_abstract_text(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.abstract_text {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `output_format` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_output_format(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.output_format {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `ocr_used` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_ocr_used(ptr: *const kreuzberg::Metadata) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.ocr_used as i32
}

/// Get the `additional` field from a `Metadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_additional(ptr: *const kreuzberg::Metadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.additional) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Returns `true` when no metadata fields, format-specific metadata, or
/// additional postprocessor fields are populated.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_metadata_is_empty(this: *const kreuzberg::Metadata) -> i32 {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return 0;
    }
    // SAFETY: null check above guarantees this is a valid pointer.
    let obj = unsafe { &*this };

    let result = obj.is_empty();
    if result {
        1
    } else {
        0
    }
}

/// Create a `ExcelMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_excel_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_metadata_from_json(json: *const c_char) -> *mut kreuzberg::ExcelMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExcelMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExcelMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_metadata_to_json(ptr: *const kreuzberg::ExcelMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExcelMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_metadata_free(ptr: *mut kreuzberg::ExcelMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `sheet_count` field from a `ExcelMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_metadata_sheet_count(ptr: *const kreuzberg::ExcelMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.sheet_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `sheet_names` field from a `ExcelMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_excel_metadata_sheet_names(
    ptr: *const kreuzberg::ExcelMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.sheet_names {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `EmailMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_email_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_from_json(json: *const c_char) -> *mut kreuzberg::EmailMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmailMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmailMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_to_json(ptr: *const kreuzberg::EmailMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmailMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_free(ptr: *mut kreuzberg::EmailMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `from_email` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_from_email(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.from_email {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `from_name` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_from_name(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.from_name {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `to_emails` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_to_emails(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.to_emails) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `cc_emails` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_cc_emails(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cc_emails) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `bcc_emails` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_bcc_emails(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.bcc_emails) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `message_id` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_message_id(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.message_id {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `attachments` field from a `EmailMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_email_metadata_attachments(
    ptr: *const kreuzberg::EmailMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.attachments) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `ArchiveMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_archive_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_from_json(json: *const c_char) -> *mut kreuzberg::ArchiveMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ArchiveMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ArchiveMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_to_json(ptr: *const kreuzberg::ArchiveMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ArchiveMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_free(ptr: *mut kreuzberg::ArchiveMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `format` field from a `ArchiveMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_format(
    ptr: *const kreuzberg::ArchiveMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `file_count` field from a `ArchiveMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_file_count(ptr: *const kreuzberg::ArchiveMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.file_count
}

/// Get the `file_list` field from a `ArchiveMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_file_list(
    ptr: *const kreuzberg::ArchiveMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.file_list) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `total_size` field from a `ArchiveMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_total_size(ptr: *const kreuzberg::ArchiveMetadata) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.total_size
}

/// Get the `compressed_size` field from a `ArchiveMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_archive_metadata_compressed_size(ptr: *const kreuzberg::ArchiveMetadata) -> u64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.compressed_size {
        Some(val) => *val,
        None => 0,
    }
}

/// Create a `ImageMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_image_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_from_json(json: *const c_char) -> *mut kreuzberg::ImageMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ImageMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ImageMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_to_json(ptr: *const kreuzberg::ImageMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ImageMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_free(ptr: *mut kreuzberg::ImageMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `width` field from a `ImageMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_width(ptr: *const kreuzberg::ImageMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.width
}

/// Get the `height` field from a `ImageMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_height(ptr: *const kreuzberg::ImageMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.height
}

/// Get the `format` field from a `ImageMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_format(
    ptr: *const kreuzberg::ImageMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `exif` field from a `ImageMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_exif(ptr: *const kreuzberg::ImageMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.exif) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `XmlMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_xml_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_metadata_from_json(json: *const c_char) -> *mut kreuzberg::XmlMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::XmlMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `XmlMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_metadata_to_json(ptr: *const kreuzberg::XmlMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `XmlMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_metadata_free(ptr: *mut kreuzberg::XmlMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `element_count` field from a `XmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_metadata_element_count(ptr: *const kreuzberg::XmlMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.element_count
}

/// Get the `unique_elements` field from a `XmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_xml_metadata_unique_elements(
    ptr: *const kreuzberg::XmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.unique_elements) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `TextMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_text_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_from_json(json: *const c_char) -> *mut kreuzberg::TextMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TextMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TextMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_to_json(ptr: *const kreuzberg::TextMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TextMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_free(ptr: *mut kreuzberg::TextMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `line_count` field from a `TextMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_line_count(ptr: *const kreuzberg::TextMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.line_count
}

/// Get the `word_count` field from a `TextMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_word_count(ptr: *const kreuzberg::TextMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.word_count
}

/// Get the `character_count` field from a `TextMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_character_count(ptr: *const kreuzberg::TextMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.character_count
}

/// Get the `headers` field from a `TextMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_metadata_headers(ptr: *const kreuzberg::TextMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.headers {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `HeaderMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_header_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_from_json(json: *const c_char) -> *mut kreuzberg::HeaderMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HeaderMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HeaderMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_to_json(ptr: *const kreuzberg::HeaderMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HeaderMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_free(ptr: *mut kreuzberg::HeaderMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `level` field from a `HeaderMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_level(ptr: *const kreuzberg::HeaderMetadata) -> u8 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.level
}

/// Get the `text` field from a `HeaderMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_text(
    ptr: *const kreuzberg::HeaderMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `id` field from a `HeaderMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_id(ptr: *const kreuzberg::HeaderMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.id {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `depth` field from a `HeaderMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_depth(ptr: *const kreuzberg::HeaderMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.depth
}

/// Get the `html_offset` field from a `HeaderMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_header_metadata_html_offset(ptr: *const kreuzberg::HeaderMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.html_offset
}

/// Create a `LinkMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_link_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_from_json(json: *const c_char) -> *mut kreuzberg::LinkMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LinkMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LinkMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_to_json(ptr: *const kreuzberg::LinkMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LinkMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_free(ptr: *mut kreuzberg::LinkMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `href` field from a `LinkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_href(ptr: *const kreuzberg::LinkMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.href.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `text` field from a `LinkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_text(ptr: *const kreuzberg::LinkMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `title` field from a `LinkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_title(ptr: *const kreuzberg::LinkMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `link_type` field from a `LinkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_link_type(
    ptr: *const kreuzberg::LinkMetadata,
) -> *mut kreuzberg::LinkType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.link_type))
}

/// Get the `rel` field from a `LinkMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_metadata_rel(ptr: *const kreuzberg::LinkMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.rel) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `ImageMetadataType` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_image_metadata_type_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_from_json(
    json: *const c_char,
) -> *mut kreuzberg::ImageMetadataType {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ImageMetadataType>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ImageMetadataType` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_to_json(
    ptr: *const kreuzberg::ImageMetadataType,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ImageMetadataType` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_free(ptr: *mut kreuzberg::ImageMetadataType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `src` field from a `ImageMetadataType`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_src(
    ptr: *const kreuzberg::ImageMetadataType,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.src.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `alt` field from a `ImageMetadataType`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_alt(
    ptr: *const kreuzberg::ImageMetadataType,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.alt {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `title` field from a `ImageMetadataType`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_title(
    ptr: *const kreuzberg::ImageMetadataType,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `image_type` field from a `ImageMetadataType`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_metadata_type_image_type(
    ptr: *const kreuzberg::ImageMetadataType,
) -> *mut kreuzberg::ImageType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.image_type))
}

/// Create a `StructuredData` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_structured_data_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_from_json(json: *const c_char) -> *mut kreuzberg::StructuredData {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::StructuredData>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `StructuredData` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_to_json(ptr: *const kreuzberg::StructuredData) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `StructuredData` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_free(ptr: *mut kreuzberg::StructuredData) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `data_type` field from a `StructuredData`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_data_type(
    ptr: *const kreuzberg::StructuredData,
) -> *mut kreuzberg::StructuredDataType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.data_type))
}

/// Get the `raw_json` field from a `StructuredData`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_raw_json(
    ptr: *const kreuzberg::StructuredData,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.raw_json.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `schema_type` field from a `StructuredData`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_schema_type(
    ptr: *const kreuzberg::StructuredData,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.schema_type {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `HtmlMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_html_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_from_json(json: *const c_char) -> *mut kreuzberg::HtmlMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HtmlMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HtmlMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_to_json(ptr: *const kreuzberg::HtmlMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HtmlMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_free(ptr: *mut kreuzberg::HtmlMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `title` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_title(ptr: *const kreuzberg::HtmlMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `description` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_description(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.description {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `keywords` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_keywords(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.keywords) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `author` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_author(ptr: *const kreuzberg::HtmlMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.author {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `canonical_url` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_canonical_url(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.canonical_url {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `base_href` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_base_href(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.base_href {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `language` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_language(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `text_direction` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_text_direction(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut kreuzberg::TextDirection {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.text_direction {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Get the `open_graph` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_open_graph(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.open_graph) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `twitter_card` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_twitter_card(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.twitter_card) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `meta_tags` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_meta_tags(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.meta_tags) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `headers` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_headers(ptr: *const kreuzberg::HtmlMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.headers) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `links` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_links(ptr: *const kreuzberg::HtmlMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.links) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `images` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_images(ptr: *const kreuzberg::HtmlMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.images) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `structured_data` field from a `HtmlMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_metadata_structured_data(
    ptr: *const kreuzberg::HtmlMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.structured_data) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `OcrMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_from_json(json: *const c_char) -> *mut kreuzberg::OcrMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_to_json(ptr: *const kreuzberg::OcrMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_free(ptr: *mut kreuzberg::OcrMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `language` field from a `OcrMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_language(ptr: *const kreuzberg::OcrMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.language.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `psm` field from a `OcrMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_psm(ptr: *const kreuzberg::OcrMetadata) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.psm
}

/// Get the `output_format` field from a `OcrMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_output_format(
    ptr: *const kreuzberg::OcrMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.output_format.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `table_count` field from a `OcrMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_table_count(ptr: *const kreuzberg::OcrMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.table_count
}

/// Get the `table_rows` field from a `OcrMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_table_rows(ptr: *const kreuzberg::OcrMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.table_rows {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `table_cols` field from a `OcrMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_metadata_table_cols(ptr: *const kreuzberg::OcrMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.table_cols {
        Some(val) => *val,
        None => 0,
    }
}

/// Create a `ErrorMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_error_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_error_metadata_from_json(json: *const c_char) -> *mut kreuzberg::ErrorMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ErrorMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ErrorMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_error_metadata_to_json(ptr: *const kreuzberg::ErrorMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ErrorMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_error_metadata_free(ptr: *mut kreuzberg::ErrorMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `error_type` field from a `ErrorMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_error_metadata_error_type(
    ptr: *const kreuzberg::ErrorMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.error_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `message` field from a `ErrorMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_error_metadata_message(
    ptr: *const kreuzberg::ErrorMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.message.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `PptxMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pptx_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_from_json(json: *const c_char) -> *mut kreuzberg::PptxMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PptxMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PptxMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_to_json(ptr: *const kreuzberg::PptxMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PptxMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_free(ptr: *mut kreuzberg::PptxMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `slide_count` field from a `PptxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_slide_count(ptr: *const kreuzberg::PptxMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.slide_count
}

/// Get the `slide_names` field from a `PptxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_slide_names(
    ptr: *const kreuzberg::PptxMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.slide_names) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `image_count` field from a `PptxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_image_count(ptr: *const kreuzberg::PptxMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.image_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `table_count` field from a `PptxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pptx_metadata_table_count(ptr: *const kreuzberg::PptxMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.table_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Create a `DocxMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_docx_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_metadata_from_json(json: *const c_char) -> *mut kreuzberg::DocxMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DocxMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DocxMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_metadata_to_json(ptr: *const kreuzberg::DocxMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DocxMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_metadata_free(ptr: *mut kreuzberg::DocxMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `core_properties` field from a `DocxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_metadata_core_properties(
    ptr: *const kreuzberg::DocxMetadata,
) -> *mut kreuzberg::CoreProperties {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.core_properties {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `app_properties` field from a `DocxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_metadata_app_properties(
    ptr: *const kreuzberg::DocxMetadata,
) -> *mut kreuzberg::DocxAppProperties {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.app_properties {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `custom_properties` field from a `DocxMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_docx_metadata_custom_properties(
    ptr: *const kreuzberg::DocxMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.custom_properties {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `CsvMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_csv_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_from_json(json: *const c_char) -> *mut kreuzberg::CsvMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::CsvMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `CsvMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_to_json(ptr: *const kreuzberg::CsvMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `CsvMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_free(ptr: *mut kreuzberg::CsvMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `row_count` field from a `CsvMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_row_count(ptr: *const kreuzberg::CsvMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.row_count
}

/// Get the `column_count` field from a `CsvMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_column_count(ptr: *const kreuzberg::CsvMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.column_count
}

/// Get the `delimiter` field from a `CsvMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_delimiter(ptr: *const kreuzberg::CsvMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.delimiter {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `has_header` field from a `CsvMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_has_header(ptr: *const kreuzberg::CsvMetadata) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.has_header as i32
}

/// Get the `column_types` field from a `CsvMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_csv_metadata_column_types(
    ptr: *const kreuzberg::CsvMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.column_types {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `BibtexMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_bibtex_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_from_json(json: *const c_char) -> *mut kreuzberg::BibtexMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::BibtexMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `BibtexMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_to_json(ptr: *const kreuzberg::BibtexMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `BibtexMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_free(ptr: *mut kreuzberg::BibtexMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `entry_count` field from a `BibtexMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_entry_count(ptr: *const kreuzberg::BibtexMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.entry_count
}

/// Get the `citation_keys` field from a `BibtexMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_citation_keys(
    ptr: *const kreuzberg::BibtexMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.citation_keys) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `authors` field from a `BibtexMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_authors(
    ptr: *const kreuzberg::BibtexMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.authors) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `year_range` field from a `BibtexMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_year_range(
    ptr: *const kreuzberg::BibtexMetadata,
) -> *mut kreuzberg::YearRange {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.year_range {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `entry_types` field from a `BibtexMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_bibtex_metadata_entry_types(
    ptr: *const kreuzberg::BibtexMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.entry_types {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `CitationMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_citation_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_from_json(
    json: *const c_char,
) -> *mut kreuzberg::CitationMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::CitationMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `CitationMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_to_json(ptr: *const kreuzberg::CitationMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `CitationMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_free(ptr: *mut kreuzberg::CitationMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `citation_count` field from a `CitationMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_citation_count(ptr: *const kreuzberg::CitationMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.citation_count
}

/// Get the `format` field from a `CitationMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_format(
    ptr: *const kreuzberg::CitationMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.format {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `authors` field from a `CitationMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_authors(
    ptr: *const kreuzberg::CitationMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.authors) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `year_range` field from a `CitationMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_year_range(
    ptr: *const kreuzberg::CitationMetadata,
) -> *mut kreuzberg::YearRange {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.year_range {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `dois` field from a `CitationMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_dois(
    ptr: *const kreuzberg::CitationMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.dois) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `keywords` field from a `CitationMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_citation_metadata_keywords(
    ptr: *const kreuzberg::CitationMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.keywords) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `YearRange` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_year_range_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_year_range_from_json(json: *const c_char) -> *mut kreuzberg::YearRange {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::YearRange>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `YearRange` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_year_range_to_json(ptr: *const kreuzberg::YearRange) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `YearRange` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_year_range_free(ptr: *mut kreuzberg::YearRange) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `min` field from a `YearRange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_year_range_min(ptr: *const kreuzberg::YearRange) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.min {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `max` field from a `YearRange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_year_range_max(ptr: *const kreuzberg::YearRange) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `years` field from a `YearRange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_year_range_years(ptr: *const kreuzberg::YearRange) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.years) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `FictionBookMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_fiction_book_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_fiction_book_metadata_from_json(
    json: *const c_char,
) -> *mut kreuzberg::FictionBookMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::FictionBookMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `FictionBookMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_fiction_book_metadata_to_json(
    ptr: *const kreuzberg::FictionBookMetadata,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `FictionBookMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_fiction_book_metadata_free(ptr: *mut kreuzberg::FictionBookMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `genres` field from a `FictionBookMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_fiction_book_metadata_genres(
    ptr: *const kreuzberg::FictionBookMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.genres) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `sequences` field from a `FictionBookMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_fiction_book_metadata_sequences(
    ptr: *const kreuzberg::FictionBookMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.sequences) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `annotation` field from a `FictionBookMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_fiction_book_metadata_annotation(
    ptr: *const kreuzberg::FictionBookMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.annotation {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `DbfMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_dbf_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_metadata_from_json(json: *const c_char) -> *mut kreuzberg::DbfMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DbfMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DbfMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_metadata_to_json(ptr: *const kreuzberg::DbfMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DbfMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_metadata_free(ptr: *mut kreuzberg::DbfMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `record_count` field from a `DbfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_metadata_record_count(ptr: *const kreuzberg::DbfMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.record_count
}

/// Get the `field_count` field from a `DbfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_metadata_field_count(ptr: *const kreuzberg::DbfMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.field_count
}

/// Get the `fields` field from a `DbfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_metadata_fields(ptr: *const kreuzberg::DbfMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.fields) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `DbfFieldInfo` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_dbf_field_info_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_field_info_from_json(json: *const c_char) -> *mut kreuzberg::DbfFieldInfo {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DbfFieldInfo>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DbfFieldInfo` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_field_info_to_json(ptr: *const kreuzberg::DbfFieldInfo) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DbfFieldInfo` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_field_info_free(ptr: *mut kreuzberg::DbfFieldInfo) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `name` field from a `DbfFieldInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_field_info_name(ptr: *const kreuzberg::DbfFieldInfo) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `field_type` field from a `DbfFieldInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_dbf_field_info_field_type(
    ptr: *const kreuzberg::DbfFieldInfo,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.field_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `JatsMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_jats_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_from_json(json: *const c_char) -> *mut kreuzberg::JatsMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::JatsMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `JatsMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_to_json(ptr: *const kreuzberg::JatsMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `JatsMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_free(ptr: *mut kreuzberg::JatsMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `copyright` field from a `JatsMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_copyright(
    ptr: *const kreuzberg::JatsMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.copyright {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `license` field from a `JatsMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_license(ptr: *const kreuzberg::JatsMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.license {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `history_dates` field from a `JatsMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_history_dates(
    ptr: *const kreuzberg::JatsMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.history_dates) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `contributor_roles` field from a `JatsMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_jats_metadata_contributor_roles(
    ptr: *const kreuzberg::JatsMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.contributor_roles) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `ContributorRole` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_contributor_role_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_contributor_role_from_json(json: *const c_char) -> *mut kreuzberg::ContributorRole {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ContributorRole>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ContributorRole` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_contributor_role_to_json(ptr: *const kreuzberg::ContributorRole) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ContributorRole` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_contributor_role_free(ptr: *mut kreuzberg::ContributorRole) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `name` field from a `ContributorRole`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_contributor_role_name(
    ptr: *const kreuzberg::ContributorRole,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `role` field from a `ContributorRole`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_contributor_role_role(
    ptr: *const kreuzberg::ContributorRole,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.role {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `EpubMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_epub_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_from_json(json: *const c_char) -> *mut kreuzberg::EpubMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EpubMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EpubMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_to_json(ptr: *const kreuzberg::EpubMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EpubMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_free(ptr: *mut kreuzberg::EpubMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `coverage` field from a `EpubMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_coverage(
    ptr: *const kreuzberg::EpubMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.coverage {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `dc_format` field from a `EpubMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_dc_format(
    ptr: *const kreuzberg::EpubMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.dc_format {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `relation` field from a `EpubMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_relation(
    ptr: *const kreuzberg::EpubMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.relation {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `source` field from a `EpubMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_source(ptr: *const kreuzberg::EpubMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.source {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `dc_type` field from a `EpubMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_dc_type(ptr: *const kreuzberg::EpubMetadata) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.dc_type {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `cover_image` field from a `EpubMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_epub_metadata_cover_image(
    ptr: *const kreuzberg::EpubMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cover_image {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `PstMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pst_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pst_metadata_from_json(json: *const c_char) -> *mut kreuzberg::PstMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PstMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PstMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pst_metadata_to_json(ptr: *const kreuzberg::PstMetadata) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PstMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pst_metadata_free(ptr: *mut kreuzberg::PstMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `message_count` field from a `PstMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pst_metadata_message_count(ptr: *const kreuzberg::PstMetadata) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.message_count
}

/// Create a `OcrConfidence` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_confidence_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_confidence_from_json(json: *const c_char) -> *mut kreuzberg::OcrConfidence {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrConfidence>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrConfidence` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_confidence_to_json(ptr: *const kreuzberg::OcrConfidence) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrConfidence` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_confidence_free(ptr: *mut kreuzberg::OcrConfidence) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `detection` field from a `OcrConfidence`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_confidence_detection(ptr: *const kreuzberg::OcrConfidence) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.detection {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Get the `recognition` field from a `OcrConfidence`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_confidence_recognition(ptr: *const kreuzberg::OcrConfidence) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.recognition
}

/// Create a `OcrRotation` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_rotation_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_rotation_from_json(json: *const c_char) -> *mut kreuzberg::OcrRotation {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrRotation>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrRotation` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_rotation_to_json(ptr: *const kreuzberg::OcrRotation) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrRotation` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_rotation_free(ptr: *mut kreuzberg::OcrRotation) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `angle_degrees` field from a `OcrRotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_rotation_angle_degrees(ptr: *const kreuzberg::OcrRotation) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.angle_degrees
}

/// Get the `confidence` field from a `OcrRotation`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_rotation_confidence(ptr: *const kreuzberg::OcrRotation) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.confidence {
        Some(val) => *val,
        None => 0.0,
    }
}

/// Create a `OcrElement` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_element_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_from_json(json: *const c_char) -> *mut kreuzberg::OcrElement {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrElement>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrElement` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_to_json(ptr: *const kreuzberg::OcrElement) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrElement` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_free(ptr: *mut kreuzberg::OcrElement) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `text` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_text(ptr: *const kreuzberg::OcrElement) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `geometry` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_geometry(
    ptr: *const kreuzberg::OcrElement,
) -> *mut kreuzberg::OcrBoundingGeometry {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.geometry.clone()))
}

/// Get the `confidence` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_confidence(
    ptr: *const kreuzberg::OcrElement,
) -> *mut kreuzberg::OcrConfidence {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.confidence.clone()))
}

/// Get the `level` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_level(
    ptr: *const kreuzberg::OcrElement,
) -> *mut kreuzberg::OcrElementLevel {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.level))
}

/// Get the `rotation` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_rotation(
    ptr: *const kreuzberg::OcrElement,
) -> *mut kreuzberg::OcrRotation {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.rotation {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `page_number` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_page_number(ptr: *const kreuzberg::OcrElement) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_number
}

/// Get the `parent_id` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_parent_id(ptr: *const kreuzberg::OcrElement) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.parent_id {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `backend_metadata` field from a `OcrElement`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_backend_metadata(
    ptr: *const kreuzberg::OcrElement,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.backend_metadata) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `OcrElementConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_ocr_element_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OcrElementConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OcrElementConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OcrElementConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_to_json(ptr: *const kreuzberg::OcrElementConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OcrElementConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_free(ptr: *mut kreuzberg::OcrElementConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `include_elements` field from a `OcrElementConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_include_elements(ptr: *const kreuzberg::OcrElementConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_elements as i32
}

/// Get the `min_level` field from a `OcrElementConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_min_level(
    ptr: *const kreuzberg::OcrElementConfig,
) -> *mut kreuzberg::OcrElementLevel {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.min_level))
}

/// Get the `min_confidence` field from a `OcrElementConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_min_confidence(ptr: *const kreuzberg::OcrElementConfig) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_confidence
}

/// Get the `build_hierarchy` field from a `OcrElementConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_config_build_hierarchy(ptr: *const kreuzberg::OcrElementConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.build_hierarchy as i32
}

/// Create a `PageStructure` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_page_structure_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_from_json(json: *const c_char) -> *mut kreuzberg::PageStructure {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PageStructure>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PageStructure` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_to_json(ptr: *const kreuzberg::PageStructure) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PageStructure` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_free(ptr: *mut kreuzberg::PageStructure) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `total_count` field from a `PageStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_total_count(ptr: *const kreuzberg::PageStructure) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.total_count
}

/// Get the `unit_type` field from a `PageStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_unit_type(
    ptr: *const kreuzberg::PageStructure,
) -> *mut kreuzberg::PageUnitType {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.unit_type))
}

/// Get the `boundaries` field from a `PageStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_boundaries(
    ptr: *const kreuzberg::PageStructure,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.boundaries {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `pages` field from a `PageStructure`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_structure_pages(ptr: *const kreuzberg::PageStructure) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pages {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `PageBoundary` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_page_boundary_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_boundary_from_json(json: *const c_char) -> *mut kreuzberg::PageBoundary {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PageBoundary>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PageBoundary` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_boundary_to_json(ptr: *const kreuzberg::PageBoundary) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PageBoundary` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_boundary_free(ptr: *mut kreuzberg::PageBoundary) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `byte_start` field from a `PageBoundary`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_boundary_byte_start(ptr: *const kreuzberg::PageBoundary) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.byte_start
}

/// Get the `byte_end` field from a `PageBoundary`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_boundary_byte_end(ptr: *const kreuzberg::PageBoundary) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.byte_end
}

/// Get the `page_number` field from a `PageBoundary`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_boundary_page_number(ptr: *const kreuzberg::PageBoundary) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_number
}

/// Create a `PageInfo` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_page_info_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_from_json(json: *const c_char) -> *mut kreuzberg::PageInfo {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PageInfo>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PageInfo` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_to_json(ptr: *const kreuzberg::PageInfo) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PageInfo` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_free(ptr: *mut kreuzberg::PageInfo) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `number` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_number(ptr: *const kreuzberg::PageInfo) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.number
}

/// Get the `title` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_title(ptr: *const kreuzberg::PageInfo) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.title {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `image_count` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_image_count(ptr: *const kreuzberg::PageInfo) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.image_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `table_count` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_table_count(ptr: *const kreuzberg::PageInfo) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.table_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `hidden` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_hidden(ptr: *const kreuzberg::PageInfo) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hidden {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `is_blank` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_is_blank(ptr: *const kreuzberg::PageInfo) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.is_blank {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `has_vector_graphics` field from a `PageInfo`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_info_has_vector_graphics(ptr: *const kreuzberg::PageInfo) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.has_vector_graphics as i32
}

/// Create a `PageContent` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_page_content_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_from_json(json: *const c_char) -> *mut kreuzberg::PageContent {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PageContent>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PageContent` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_to_json(ptr: *const kreuzberg::PageContent) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PageContent` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_free(ptr: *mut kreuzberg::PageContent) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `page_number` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_page_number(ptr: *const kreuzberg::PageContent) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_number
}

/// Get the `content` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_content(ptr: *const kreuzberg::PageContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tables` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_tables(ptr: *const kreuzberg::PageContent) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `image_indices` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_image_indices(
    ptr: *const kreuzberg::PageContent,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.image_indices) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `hierarchy` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_hierarchy(
    ptr: *const kreuzberg::PageContent,
) -> *mut kreuzberg::PageHierarchy {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.hierarchy {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `is_blank` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_is_blank(ptr: *const kreuzberg::PageContent) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.is_blank {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `layout_regions` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_layout_regions(
    ptr: *const kreuzberg::PageContent,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.layout_regions {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `speaker_notes` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_speaker_notes(
    ptr: *const kreuzberg::PageContent,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.speaker_notes {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `section_name` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_section_name(
    ptr: *const kreuzberg::PageContent,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.section_name {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `sheet_name` field from a `PageContent`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_content_sheet_name(
    ptr: *const kreuzberg::PageContent,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.sheet_name {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `LayoutRegion` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_layout_region_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_from_json(json: *const c_char) -> *mut kreuzberg::LayoutRegion {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LayoutRegion>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LayoutRegion` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_to_json(ptr: *const kreuzberg::LayoutRegion) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LayoutRegion` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_free(ptr: *mut kreuzberg::LayoutRegion) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `class_name` field from a `LayoutRegion`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_class_name(
    ptr: *const kreuzberg::LayoutRegion,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.class_name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `confidence` field from a `LayoutRegion`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_confidence(ptr: *const kreuzberg::LayoutRegion) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.confidence
}

/// Get the `bounding_box` field from a `LayoutRegion`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_bounding_box(
    ptr: *const kreuzberg::LayoutRegion,
) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.bounding_box))
}

/// Get the `area_fraction` field from a `LayoutRegion`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_region_area_fraction(ptr: *const kreuzberg::LayoutRegion) -> f64 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.area_fraction
}

/// Create a `PageHierarchy` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_page_hierarchy_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_hierarchy_from_json(json: *const c_char) -> *mut kreuzberg::PageHierarchy {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PageHierarchy>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PageHierarchy` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_hierarchy_to_json(ptr: *const kreuzberg::PageHierarchy) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PageHierarchy` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_hierarchy_free(ptr: *mut kreuzberg::PageHierarchy) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `block_count` field from a `PageHierarchy`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_hierarchy_block_count(ptr: *const kreuzberg::PageHierarchy) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.block_count
}

/// Get the `blocks` field from a `PageHierarchy`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_hierarchy_blocks(
    ptr: *const kreuzberg::PageHierarchy,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.blocks) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `HierarchicalBlock` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_hierarchical_block_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchical_block_from_json(
    json: *const c_char,
) -> *mut kreuzberg::HierarchicalBlock {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::HierarchicalBlock>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `HierarchicalBlock` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchical_block_to_json(ptr: *const kreuzberg::HierarchicalBlock) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `HierarchicalBlock` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchical_block_free(ptr: *mut kreuzberg::HierarchicalBlock) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `text` field from a `HierarchicalBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchical_block_text(
    ptr: *const kreuzberg::HierarchicalBlock,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `font_size` field from a `HierarchicalBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchical_block_font_size(ptr: *const kreuzberg::HierarchicalBlock) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.font_size
}

/// Get the `level` field from a `HierarchicalBlock`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_hierarchical_block_level(
    ptr: *const kreuzberg::HierarchicalBlock,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.level.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `CellChange` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_cell_change_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_from_json(json: *const c_char) -> *mut kreuzberg::CellChange {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::CellChange>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `CellChange` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_to_json(ptr: *const kreuzberg::CellChange) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `CellChange` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_free(ptr: *mut kreuzberg::CellChange) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `row` field from a `CellChange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_row(ptr: *const kreuzberg::CellChange) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.row
}

/// Get the `col` field from a `CellChange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_col(ptr: *const kreuzberg::CellChange) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.col
}

/// Get the `from` field from a `CellChange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_from(ptr: *const kreuzberg::CellChange) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.from.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `to` field from a `CellChange`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_cell_change_to(ptr: *const kreuzberg::CellChange) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.to.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `DocumentRevision` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_document_revision_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_from_json(
    json: *const c_char,
) -> *mut kreuzberg::DocumentRevision {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DocumentRevision>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DocumentRevision` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_to_json(ptr: *const kreuzberg::DocumentRevision) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DocumentRevision` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_free(ptr: *mut kreuzberg::DocumentRevision) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `revision_id` field from a `DocumentRevision`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_revision_id(
    ptr: *const kreuzberg::DocumentRevision,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.revision_id.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `author` field from a `DocumentRevision`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_author(
    ptr: *const kreuzberg::DocumentRevision,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.author {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `timestamp` field from a `DocumentRevision`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_timestamp(
    ptr: *const kreuzberg::DocumentRevision,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.timestamp {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `kind` field from a `DocumentRevision`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_kind(
    ptr: *const kreuzberg::DocumentRevision,
) -> *mut kreuzberg::RevisionKind {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.kind))
}

/// Get the `anchor` field from a `DocumentRevision`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_anchor(
    ptr: *const kreuzberg::DocumentRevision,
) -> *mut kreuzberg::RevisionAnchor {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.anchor {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `delta` field from a `DocumentRevision`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_document_revision_delta(
    ptr: *const kreuzberg::DocumentRevision,
) -> *mut kreuzberg::RevisionDelta {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.delta.clone()))
}

/// Create a `RevisionDelta` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_revision_delta_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_delta_from_json(json: *const c_char) -> *mut kreuzberg::RevisionDelta {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::RevisionDelta>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `RevisionDelta` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_delta_to_json(ptr: *const kreuzberg::RevisionDelta) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `RevisionDelta` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_delta_free(ptr: *mut kreuzberg::RevisionDelta) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `RevisionDelta`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_delta_content(
    ptr: *const kreuzberg::RevisionDelta,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.content) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `table_changes` field from a `RevisionDelta`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_delta_table_changes(
    ptr: *const kreuzberg::RevisionDelta,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.table_changes) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `Table` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_table_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_from_json(json: *const c_char) -> *mut kreuzberg::Table {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::Table>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `Table` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_to_json(ptr: *const kreuzberg::Table) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `Table` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_free(ptr: *mut kreuzberg::Table) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `cells` field from a `Table`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cells(ptr: *const kreuzberg::Table) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cells) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `markdown` field from a `Table`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_markdown(ptr: *const kreuzberg::Table) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.markdown.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `page_number` field from a `Table`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_page_number(ptr: *const kreuzberg::Table) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_number
}

/// Get the `bounding_box` field from a `Table`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_bounding_box(ptr: *const kreuzberg::Table) -> *mut kreuzberg::BoundingBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.bounding_box {
        Some(val) => Box::into_raw(Box::new(*val)),
        None => std::ptr::null_mut(),
    }
}

/// Create a `TableCell` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_table_cell_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_from_json(json: *const c_char) -> *mut kreuzberg::TableCell {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TableCell>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TableCell` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_to_json(ptr: *const kreuzberg::TableCell) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TableCell` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_free(ptr: *mut kreuzberg::TableCell) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content` field from a `TableCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_content(ptr: *const kreuzberg::TableCell) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.content.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `row_span` field from a `TableCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_row_span(ptr: *const kreuzberg::TableCell) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.row_span
}

/// Get the `col_span` field from a `TableCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_col_span(ptr: *const kreuzberg::TableCell) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.col_span
}

/// Get the `is_header` field from a `TableCell`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_cell_is_header(ptr: *const kreuzberg::TableCell) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.is_header as i32
}

/// Create a `ExtractedUri` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_extracted_uri_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_from_json(json: *const c_char) -> *mut kreuzberg::ExtractedUri {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExtractedUri>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExtractedUri` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_to_json(ptr: *const kreuzberg::ExtractedUri) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExtractedUri` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_free(ptr: *mut kreuzberg::ExtractedUri) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `url` field from a `ExtractedUri`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_url(ptr: *const kreuzberg::ExtractedUri) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.url.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `label` field from a `ExtractedUri`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_label(ptr: *const kreuzberg::ExtractedUri) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.label {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `page` field from a `ExtractedUri`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_page(ptr: *const kreuzberg::ExtractedUri) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `kind` field from a `ExtractedUri`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extracted_uri_kind(ptr: *const kreuzberg::ExtractedUri) -> *mut kreuzberg::UriKind {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.kind))
}

/// Create a `DetectResponse` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_detect_response_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_response_from_json(
    json: *const c_char,
) -> *mut kreuzberg::api::DetectResponse {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::api::DetectResponse>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DetectResponse` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_response_to_json(ptr: *const kreuzberg::api::DetectResponse) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DetectResponse` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_response_free(ptr: *mut kreuzberg::api::DetectResponse) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `mime_type` field from a `DetectResponse`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_response_mime_type(
    ptr: *const kreuzberg::api::DetectResponse,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.mime_type.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `filename` field from a `DetectResponse`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_response_filename(
    ptr: *const kreuzberg::api::DetectResponse,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.filename {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `DiffOptions` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_diff_options_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_from_json(json: *const c_char) -> *mut kreuzberg::DiffOptions {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DiffOptions>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DiffOptions` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_to_json(ptr: *const kreuzberg::DiffOptions) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DiffOptions` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_free(ptr: *mut kreuzberg::DiffOptions) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `include_metadata` field from a `DiffOptions`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_include_metadata(ptr: *const kreuzberg::DiffOptions) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_metadata as i32
}

/// Get the `include_embedded` field from a `DiffOptions`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_include_embedded(ptr: *const kreuzberg::DiffOptions) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.include_embedded as i32
}

/// Get the `max_content_chars` field from a `DiffOptions`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_max_content_chars(ptr: *const kreuzberg::DiffOptions) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.max_content_chars {
        Some(val) => *val,
        None => 0,
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_options_default() -> *mut kreuzberg::DiffOptions {
    clear_last_error();
    let result = kreuzberg::DiffOptions::default();
    Box::into_raw(Box::new(result))
}

/// Create a `ExtractionDiff` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_extraction_diff_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_from_json(json: *const c_char) -> *mut kreuzberg::ExtractionDiff {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ExtractionDiff>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ExtractionDiff` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_to_json(ptr: *const kreuzberg::ExtractionDiff) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ExtractionDiff` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_free(ptr: *mut kreuzberg::ExtractionDiff) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `content_diff` field from a `ExtractionDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_content_diff(
    ptr: *const kreuzberg::ExtractionDiff,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.content_diff) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tables_added` field from a `ExtractionDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_tables_added(
    ptr: *const kreuzberg::ExtractionDiff,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables_added) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tables_removed` field from a `ExtractionDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_tables_removed(
    ptr: *const kreuzberg::ExtractionDiff,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables_removed) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `tables_changed` field from a `ExtractionDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_tables_changed(
    ptr: *const kreuzberg::ExtractionDiff,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.tables_changed) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `metadata_changed` field from a `ExtractionDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_metadata_changed(
    ptr: *const kreuzberg::ExtractionDiff,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.metadata_changed) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `embedded_changes` field from a `ExtractionDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_diff_embedded_changes(
    ptr: *const kreuzberg::ExtractionDiff,
) -> *mut kreuzberg::EmbeddedChanges {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.embedded_changes.clone()))
}

/// Create a `DiffHunk` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_diff_hunk_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_from_json(json: *const c_char) -> *mut kreuzberg::DiffHunk {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DiffHunk>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DiffHunk` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_to_json(ptr: *const kreuzberg::DiffHunk) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DiffHunk` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_free(ptr: *mut kreuzberg::DiffHunk) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `from_line` field from a `DiffHunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_from_line(ptr: *const kreuzberg::DiffHunk) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.from_line
}

/// Get the `from_count` field from a `DiffHunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_from_count(ptr: *const kreuzberg::DiffHunk) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.from_count
}

/// Get the `to_line` field from a `DiffHunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_to_line(ptr: *const kreuzberg::DiffHunk) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.to_line
}

/// Get the `to_count` field from a `DiffHunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_to_count(ptr: *const kreuzberg::DiffHunk) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.to_count
}

/// Get the `lines` field from a `DiffHunk`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_hunk_lines(ptr: *const kreuzberg::DiffHunk) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.lines) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `TableDiff` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_table_diff_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_diff_from_json(json: *const c_char) -> *mut kreuzberg::TableDiff {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::TableDiff>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `TableDiff` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_diff_to_json(ptr: *const kreuzberg::TableDiff) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `TableDiff` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_diff_free(ptr: *mut kreuzberg::TableDiff) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `from_index` field from a `TableDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_diff_from_index(ptr: *const kreuzberg::TableDiff) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.from_index
}

/// Get the `to_index` field from a `TableDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_diff_to_index(ptr: *const kreuzberg::TableDiff) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.to_index
}

/// Get the `cell_changes` field from a `TableDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_diff_cell_changes(ptr: *const kreuzberg::TableDiff) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cell_changes) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `EmbeddedChanges` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_embedded_changes_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_changes_from_json(json: *const c_char) -> *mut kreuzberg::EmbeddedChanges {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmbeddedChanges>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmbeddedChanges` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_changes_to_json(ptr: *const kreuzberg::EmbeddedChanges) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmbeddedChanges` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_changes_free(ptr: *mut kreuzberg::EmbeddedChanges) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `added` field from a `EmbeddedChanges`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_changes_added(
    ptr: *const kreuzberg::EmbeddedChanges,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.added) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `removed` field from a `EmbeddedChanges`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_changes_removed(
    ptr: *const kreuzberg::EmbeddedChanges,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.removed) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `changed` field from a `EmbeddedChanges`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_changes_changed(
    ptr: *const kreuzberg::EmbeddedChanges,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.changed) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `EmbeddedDiff` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_embedded_diff_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_diff_from_json(json: *const c_char) -> *mut kreuzberg::EmbeddedDiff {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmbeddedDiff>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmbeddedDiff` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_diff_to_json(ptr: *const kreuzberg::EmbeddedDiff) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmbeddedDiff` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_diff_free(ptr: *mut kreuzberg::EmbeddedDiff) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `path` field from a `EmbeddedDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_diff_path(ptr: *const kreuzberg::EmbeddedDiff) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.path.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `diff` field from a `EmbeddedDiff`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_diff_diff(
    ptr: *const kreuzberg::EmbeddedDiff,
) -> *mut kreuzberg::ExtractionDiff {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new((*obj.diff).clone()))
}

/// Create a `EmbeddingPreset` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_embedding_preset_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_from_json(json: *const c_char) -> *mut kreuzberg::EmbeddingPreset {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::EmbeddingPreset>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmbeddingPreset` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_to_json(ptr: *const kreuzberg::EmbeddingPreset) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmbeddingPreset` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_free(ptr: *mut kreuzberg::EmbeddingPreset) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `name` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_name(
    ptr: *const kreuzberg::EmbeddingPreset,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `chunk_size` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_chunk_size(ptr: *const kreuzberg::EmbeddingPreset) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.chunk_size
}

/// Get the `overlap` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_overlap(ptr: *const kreuzberg::EmbeddingPreset) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.overlap
}

/// Get the `model_repo` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_model_repo(
    ptr: *const kreuzberg::EmbeddingPreset,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.model_repo.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `pooling` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_pooling(
    ptr: *const kreuzberg::EmbeddingPreset,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.pooling.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `model_file` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_model_file(
    ptr: *const kreuzberg::EmbeddingPreset,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.model_file.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `dimensions` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_dimensions(ptr: *const kreuzberg::EmbeddingPreset) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.dimensions
}

/// Get the `description` field from a `EmbeddingPreset`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_preset_description(
    ptr: *const kreuzberg::EmbeddingPreset,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.description.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `YakeParams` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_yake_params_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_yake_params_from_json(json: *const c_char) -> *mut kreuzberg::YakeParams {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::YakeParams>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `YakeParams` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_yake_params_to_json(ptr: *const kreuzberg::YakeParams) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `YakeParams` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_yake_params_free(ptr: *mut kreuzberg::YakeParams) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `window_size` field from a `YakeParams`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_yake_params_window_size(ptr: *const kreuzberg::YakeParams) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.window_size
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_yake_params_default() -> *mut kreuzberg::YakeParams {
    clear_last_error();
    let result = kreuzberg::YakeParams::default();
    Box::into_raw(Box::new(result))
}

/// Create a `RakeParams` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_rake_params_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_rake_params_from_json(json: *const c_char) -> *mut kreuzberg::RakeParams {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::RakeParams>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `RakeParams` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_rake_params_to_json(ptr: *const kreuzberg::RakeParams) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `RakeParams` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_rake_params_free(ptr: *mut kreuzberg::RakeParams) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `min_word_length` field from a `RakeParams`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_rake_params_min_word_length(ptr: *const kreuzberg::RakeParams) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_word_length
}

/// Get the `max_words_per_phrase` field from a `RakeParams`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_rake_params_max_words_per_phrase(ptr: *const kreuzberg::RakeParams) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_words_per_phrase
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_rake_params_default() -> *mut kreuzberg::RakeParams {
    clear_last_error();
    let result = kreuzberg::RakeParams::default();
    Box::into_raw(Box::new(result))
}

/// Create a `KeywordConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_keyword_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_from_json(json: *const c_char) -> *mut kreuzberg::KeywordConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::KeywordConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `KeywordConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_to_json(ptr: *const kreuzberg::KeywordConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `KeywordConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_free(ptr: *mut kreuzberg::KeywordConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `algorithm` field from a `KeywordConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_algorithm(
    ptr: *const kreuzberg::KeywordConfig,
) -> *mut kreuzberg::KeywordAlgorithm {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.algorithm))
}

/// Get the `max_keywords` field from a `KeywordConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_max_keywords(ptr: *const kreuzberg::KeywordConfig) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.max_keywords
}

/// Get the `min_score` field from a `KeywordConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_min_score(ptr: *const kreuzberg::KeywordConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.min_score
}

/// Get the `language` field from a `KeywordConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_language(
    ptr: *const kreuzberg::KeywordConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.language {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `yake_params` field from a `KeywordConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_yake_params(
    ptr: *const kreuzberg::KeywordConfig,
) -> *mut kreuzberg::YakeParams {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.yake_params {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// Get the `rake_params` field from a `KeywordConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_rake_params(
    ptr: *const kreuzberg::KeywordConfig,
) -> *mut kreuzberg::RakeParams {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.rake_params {
        Some(val) => Box::into_raw(Box::new(val.clone())),
        None => std::ptr::null_mut(),
    }
}

/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_config_default() -> *mut kreuzberg::KeywordConfig {
    clear_last_error();
    let result = kreuzberg::KeywordConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `Keyword` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_keyword_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_from_json(json: *const c_char) -> *mut kreuzberg::Keyword {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::Keyword>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `Keyword` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_to_json(ptr: *const kreuzberg::Keyword) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `Keyword` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_free(ptr: *mut kreuzberg::Keyword) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `text` field from a `Keyword`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_text(ptr: *const kreuzberg::Keyword) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.text.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `score` field from a `Keyword`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_score(ptr: *const kreuzberg::Keyword) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.score
}

/// Get the `algorithm` field from a `Keyword`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_algorithm(
    ptr: *const kreuzberg::Keyword,
) -> *mut kreuzberg::KeywordAlgorithm {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.algorithm))
}

/// Get the `positions` field from a `Keyword`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_positions(ptr: *const kreuzberg::Keyword) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.positions {
        Some(val) => match serde_json::to_string(&val) {
            Ok(s) => match CString::new(s) {
                Ok(cs) => cs.into_raw(),
                Err(_) => std::ptr::null_mut(),
            },
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `PaddleOcrConfig` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_paddle_ocr_config_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_from_json(json: *const c_char) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::PaddleOcrConfig>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PaddleOcrConfig` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_to_json(ptr: *const kreuzberg::PaddleOcrConfig) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PaddleOcrConfig` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_free(ptr: *mut kreuzberg::PaddleOcrConfig) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `language` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_language(
    ptr: *const kreuzberg::PaddleOcrConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.language.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `cache_dir` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_cache_dir(
    ptr: *const kreuzberg::PaddleOcrConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.cache_dir {
        Some(val) => match CString::new(val.to_string_lossy().to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `use_angle_cls` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_use_angle_cls(ptr: *const kreuzberg::PaddleOcrConfig) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.use_angle_cls as i32
}

/// Get the `enable_table_detection` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_enable_table_detection(
    ptr: *const kreuzberg::PaddleOcrConfig,
) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.enable_table_detection as i32
}

/// Get the `det_db_thresh` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_det_db_thresh(ptr: *const kreuzberg::PaddleOcrConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.det_db_thresh
}

/// Get the `det_db_box_thresh` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_det_db_box_thresh(ptr: *const kreuzberg::PaddleOcrConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.det_db_box_thresh
}

/// Get the `det_db_unclip_ratio` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_det_db_unclip_ratio(
    ptr: *const kreuzberg::PaddleOcrConfig,
) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.det_db_unclip_ratio
}

/// Get the `det_limit_side_len` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_det_limit_side_len(ptr: *const kreuzberg::PaddleOcrConfig) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.det_limit_side_len
}

/// Get the `rec_batch_num` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_rec_batch_num(ptr: *const kreuzberg::PaddleOcrConfig) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.rec_batch_num
}

/// Get the `padding` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_padding(ptr: *const kreuzberg::PaddleOcrConfig) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.padding
}

/// Get the `drop_score` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_drop_score(ptr: *const kreuzberg::PaddleOcrConfig) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.drop_score
}

/// Get the `model_tier` field from a `PaddleOcrConfig`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_model_tier(
    ptr: *const kreuzberg::PaddleOcrConfig,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.model_tier.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Sets a custom cache directory for model files.
/// \param path Path to cache directory
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::PaddleOcrConfig;
/// use std::path::PathBuf;
///
/// let config = PaddleOcrConfig::new("en")
///     .with_cache_dir(PathBuf::from("/tmp/paddle-cache"));
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_cache_dir(
    this: *mut kreuzberg::PaddleOcrConfig,
    path: *const std::ffi::c_char,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    if path.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'path'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees path is a valid pointer; string is valid UTF-8 from caller.
    let path_rs = match unsafe { CStr::from_ptr(path) }.to_str() {
        Ok(s) => std::path::PathBuf::from(s),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'path'");
            return std::ptr::null_mut();
        }
    };
    let result = obj.with_cache_dir(path_rs);
    Box::into_raw(Box::new(result))
}

/// Enables or disables table structure detection.
/// \param enable Whether to enable table detection
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::PaddleOcrConfig;
///
/// let config = PaddleOcrConfig::new("en")
///     .with_table_detection(true);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_table_detection(
    this: *mut kreuzberg::PaddleOcrConfig,
    enable: i32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let enable_rs = enable != 0;
    let result = obj.with_table_detection(enable_rs);
    Box::into_raw(Box::new(result))
}

/// Enables or disables angle classification for rotated text.
/// \param enable Whether to enable angle classification
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_angle_cls(
    this: *mut kreuzberg::PaddleOcrConfig,
    enable: i32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let enable_rs = enable != 0;
    let result = obj.with_angle_cls(enable_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the database threshold for text detection.
/// \param threshold Detection threshold (0.0-1.0)
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_det_db_thresh(
    this: *mut kreuzberg::PaddleOcrConfig,
    threshold: f32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let threshold_rs = threshold;
    let result = obj.with_det_db_thresh(threshold_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the box threshold for text bounding box refinement.
/// \param threshold Box threshold (0.0-1.0)
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_det_db_box_thresh(
    this: *mut kreuzberg::PaddleOcrConfig,
    threshold: f32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let threshold_rs = threshold;
    let result = obj.with_det_db_box_thresh(threshold_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the unclip ratio for expanding text bounding boxes.
/// \param ratio Unclip ratio (typically 1.5-2.0)
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_det_db_unclip_ratio(
    this: *mut kreuzberg::PaddleOcrConfig,
    ratio: f32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let ratio_rs = ratio;
    let result = obj.with_det_db_unclip_ratio(ratio_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the maximum side length for detection images.
/// \param length Maximum side length in pixels
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_det_limit_side_len(
    this: *mut kreuzberg::PaddleOcrConfig,
    length: u32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let length_rs = length;
    let result = obj.with_det_limit_side_len(length_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the batch size for recognition inference.
/// \param batch_size Number of text regions to process simultaneously
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_rec_batch_num(
    this: *mut kreuzberg::PaddleOcrConfig,
    batch_size: u32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let batch_size_rs = batch_size;
    let result = obj.with_rec_batch_num(batch_size_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the minimum recognition confidence threshold.
/// \param score Minimum confidence (0.0-1.0), text below this is dropped
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_drop_score(
    this: *mut kreuzberg::PaddleOcrConfig,
    score: f32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let score_rs = score;
    let result = obj.with_drop_score(score_rs);
    Box::into_raw(Box::new(result))
}

/// Sets padding in pixels added around images before detection.
/// \param padding Padding in pixels (0-100)
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_padding(
    this: *mut kreuzberg::PaddleOcrConfig,
    padding: u32,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    let padding_rs = padding;
    let result = obj.with_padding(padding_rs);
    Box::into_raw(Box::new(result))
}

/// Sets the model tier controlling detection/recognition model size.
/// \param tier `"mobile"` (default, lightweight, faster) or `"server"` (high accuracy, GPU/complex
/// documents)
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_with_model_tier(
    this: *mut kreuzberg::PaddleOcrConfig,
    tier: *const std::ffi::c_char,
) -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    if this.is_null() {
        set_last_error(1, "Null pointer passed for self");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees this is a valid pointer originally from Box::into_raw.
    let obj = unsafe { *Box::from_raw(this) };

    if tier.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'tier'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees tier is a valid pointer; string is valid UTF-8 from caller.
    let tier_rs = match unsafe { CStr::from_ptr(tier) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'tier'");
            return std::ptr::null_mut();
        }
    };
    let result = obj.with_model_tier(tier_rs);
    Box::into_raw(Box::new(result))
}

/// Creates a default configuration with English language support.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_ocr_config_default() -> *mut kreuzberg::PaddleOcrConfig {
    clear_last_error();
    let result = kreuzberg::PaddleOcrConfig::default();
    Box::into_raw(Box::new(result))
}

/// Create a `ModelPaths` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_model_paths_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_from_json(json: *const c_char) -> *mut kreuzberg::ModelPaths {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::ModelPaths>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `ModelPaths` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_to_json(ptr: *const kreuzberg::ModelPaths) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `ModelPaths` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_free(ptr: *mut kreuzberg::ModelPaths) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `det_model` field from a `ModelPaths`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_det_model(ptr: *const kreuzberg::ModelPaths) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.det_model.to_string_lossy().to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `cls_model` field from a `ModelPaths`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_cls_model(ptr: *const kreuzberg::ModelPaths) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.cls_model.to_string_lossy().to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `rec_model` field from a `ModelPaths`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_rec_model(ptr: *const kreuzberg::ModelPaths) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.rec_model.to_string_lossy().to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `dict_file` field from a `ModelPaths`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_model_paths_dict_file(ptr: *const kreuzberg::ModelPaths) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.dict_file.to_string_lossy().to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `OrientationResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_orientation_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_orientation_result_from_json(
    json: *const c_char,
) -> *mut kreuzberg::OrientationResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::OrientationResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `OrientationResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_orientation_result_to_json(ptr: *const kreuzberg::OrientationResult) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `OrientationResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_orientation_result_free(ptr: *mut kreuzberg::OrientationResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `degrees` field from a `OrientationResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_orientation_result_degrees(ptr: *const kreuzberg::OrientationResult) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.degrees
}

/// Get the `confidence` field from a `OrientationResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_orientation_result_confidence(ptr: *const kreuzberg::OrientationResult) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.confidence
}

/// Create a `BBox` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_b_box_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_from_json(json: *const c_char) -> *mut kreuzberg::BBox {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::BBox>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `BBox` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_to_json(ptr: *const kreuzberg::BBox) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `BBox` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_free(ptr: *mut kreuzberg::BBox) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `x1` field from a `BBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_x1(ptr: *const kreuzberg::BBox) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.x1
}

/// Get the `y1` field from a `BBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_y1(ptr: *const kreuzberg::BBox) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.y1
}

/// Get the `x2` field from a `BBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_x2(ptr: *const kreuzberg::BBox) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.x2
}

/// Get the `y2` field from a `BBox`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_b_box_y2(ptr: *const kreuzberg::BBox) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.y2
}

/// Create a `LayoutDetection` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_layout_detection_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_from_json(json: *const c_char) -> *mut kreuzberg::LayoutDetection {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::LayoutDetection>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `LayoutDetection` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_to_json(ptr: *const kreuzberg::LayoutDetection) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `LayoutDetection` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_free(ptr: *mut kreuzberg::LayoutDetection) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `class_name` field from a `LayoutDetection`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_class_name(
    ptr: *const kreuzberg::LayoutDetection,
) -> *mut kreuzberg::LayoutClass {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.class_name))
}

/// Get the `confidence` field from a `LayoutDetection`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_confidence(ptr: *const kreuzberg::LayoutDetection) -> f32 {
    if ptr.is_null() {
        return 0.0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.confidence
}

/// Get the `bbox` field from a `LayoutDetection`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_detection_bbox(
    ptr: *const kreuzberg::LayoutDetection,
) -> *mut kreuzberg::BBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.bbox))
}

/// Create a `RecognizedTable` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_recognized_table_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_recognized_table_from_json(json: *const c_char) -> *mut kreuzberg::RecognizedTable {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::RecognizedTable>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `RecognizedTable` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_recognized_table_to_json(ptr: *const kreuzberg::RecognizedTable) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `RecognizedTable` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_recognized_table_free(ptr: *mut kreuzberg::RecognizedTable) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `detection_bbox` field from a `RecognizedTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_recognized_table_detection_bbox(
    ptr: *const kreuzberg::RecognizedTable,
) -> *mut kreuzberg::BBox {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    Box::into_raw(Box::new(obj.detection_bbox))
}

/// Get the `cells` field from a `RecognizedTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_recognized_table_cells(
    ptr: *const kreuzberg::RecognizedTable,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.cells) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `markdown` field from a `RecognizedTable`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_recognized_table_markdown(
    ptr: *const kreuzberg::RecognizedTable,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.markdown.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `DetectionResult` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_detection_result_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detection_result_from_json(json: *const c_char) -> *mut kreuzberg::DetectionResult {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::DetectionResult>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `DetectionResult` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detection_result_to_json(ptr: *const kreuzberg::DetectionResult) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `DetectionResult` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detection_result_free(ptr: *mut kreuzberg::DetectionResult) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `page_width` field from a `DetectionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detection_result_page_width(ptr: *const kreuzberg::DetectionResult) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_width
}

/// Get the `page_height` field from a `DetectionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detection_result_page_height(ptr: *const kreuzberg::DetectionResult) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.page_height
}

/// Get the `detections` field from a `DetectionResult`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detection_result_detections(
    ptr: *const kreuzberg::DetectionResult,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match serde_json::to_string(&obj.detections) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(_) => std::ptr::null_mut(),
    }
}

/// Create a `EmbeddedFile` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_embedded_file_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_from_json(
    json: *const c_char,
) -> *mut kreuzberg::pdf::embedded_files::EmbeddedFile {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::pdf::embedded_files::EmbeddedFile>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `EmbeddedFile` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_to_json(
    ptr: *const kreuzberg::pdf::embedded_files::EmbeddedFile,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `EmbeddedFile` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_free(ptr: *mut kreuzberg::pdf::embedded_files::EmbeddedFile) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `name` field from a `EmbeddedFile`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_name(
    ptr: *const kreuzberg::pdf::embedded_files::EmbeddedFile,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match CString::new(obj.name.to_string()) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Get the `data` field from a `EmbeddedFile`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_data(
    ptr: *const kreuzberg::pdf::embedded_files::EmbeddedFile,
    out_len: *mut usize,
) -> *mut u8 {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    let data = &obj.data;
    if !out_len.is_null() {
        // SAFETY: null check above guarantees out_len is a valid pointer.
        unsafe {
            *out_len = data.len();
        }
    }
    data.as_ptr() as *mut u8
}

/// Get the `compressed_size` field from a `EmbeddedFile`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_compressed_size(
    ptr: *const kreuzberg::pdf::embedded_files::EmbeddedFile,
) -> usize {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    obj.compressed_size
}

/// Get the `mime_type` field from a `EmbeddedFile`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedded_file_mime_type(
    ptr: *const kreuzberg::pdf::embedded_files::EmbeddedFile,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.mime_type {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Create a `PdfMetadata` from a JSON string. Returns null on failure.
/// # Safety
/// JSON string must be valid UTF-8 and null-terminated.
/// Returned handle must be freed with `kreuzberg_pdf_metadata_free`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_from_json(
    json: *const c_char,
) -> *mut kreuzberg::pdf::metadata::PdfMetadata {
    clear_last_error();
    if json.is_null() {
        set_last_error(1, "Null pointer passed for JSON string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees json is a valid pointer; string is valid UTF-8 from caller.
    let c_str = match unsafe { CStr::from_ptr(json) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in JSON string");
            return std::ptr::null_mut();
        }
    };
    match serde_json::from_str::<kreuzberg::pdf::metadata::PdfMetadata>(c_str) {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Serialize a `PdfMetadata` to a JSON string. Returns null on failure.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_to_json(
    ptr: *const kreuzberg::pdf::metadata::PdfMetadata,
) -> *mut c_char {
    clear_last_error();
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(e) => {
                set_last_error(2, &e.to_string());
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Free a `PdfMetadata` handle.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_free(ptr: *mut kreuzberg::pdf::metadata::PdfMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Get the `pdf_version` field from a `PdfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_pdf_version(
    ptr: *const kreuzberg::pdf::metadata::PdfMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.pdf_version {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `producer` field from a `PdfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_producer(
    ptr: *const kreuzberg::pdf::metadata::PdfMetadata,
) -> *mut std::ffi::c_char {
    if ptr.is_null() {
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.producer {
        Some(val) => match CString::new(val.to_string()) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        None => std::ptr::null_mut(),
    }
}

/// Get the `is_encrypted` field from a `PdfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_is_encrypted(ptr: *const kreuzberg::pdf::metadata::PdfMetadata) -> i32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.is_encrypted {
        Some(val) => *val as i32,
        None => 0,
    }
}

/// Get the `width` field from a `PdfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_width(ptr: *const kreuzberg::pdf::metadata::PdfMetadata) -> i64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.width {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `height` field from a `PdfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_height(ptr: *const kreuzberg::pdf::metadata::PdfMetadata) -> i64 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.height {
        Some(val) => *val,
        None => 0,
    }
}

/// Get the `page_count` field from a `PdfMetadata`.
/// # Safety
/// Pointer must be a valid handle returned by this library.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_metadata_page_count(ptr: *const kreuzberg::pdf::metadata::PdfMetadata) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: null check above guarantees ptr is a valid pointer.
    let obj = unsafe { &*ptr };
    match &obj.page_count {
        Some(val) => *val,
        None => 0,
    }
}

/// Convert an integer to a `ExecutionProviderType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_execution_provider_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Auto
        1 => 1, // Cpu
        2 => 2, // CoreMl
        3 => 3, // Cuda
        4 => 4, // TensorRt
        _ => {
            set_last_error(1, "Invalid ExecutionProviderType variant");
            -1
        }
    }
}

/// Convert a `ExecutionProviderType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_execution_provider_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Auto" => 0,
        "Cpu" => 1,
        "CoreMl" => 2,
        "Cuda" => 3,
        "TensorRt" => 4,
        _ => {
            set_last_error(1, "Unknown ExecutionProviderType variant");
            -1
        }
    }
}

/// Convert an integer to a `OutputFormat` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_output_format_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Plain
        1 => 1, // Markdown
        2 => 2, // Djot
        3 => 3, // Html
        4 => 4, // Json
        5 => 5, // Structured
        6 => 6, // Custom
        _ => {
            set_last_error(1, "Invalid OutputFormat variant");
            -1
        }
    }
}

/// Convert a `OutputFormat` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_output_format_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Plain" => 0,
        "Markdown" => 1,
        "Djot" => 2,
        "Html" => 3,
        "Json" => 4,
        "Structured" => 5,
        "Custom" => 6,
        _ => {
            set_last_error(1, "Unknown OutputFormat variant");
            -1
        }
    }
}

/// Convert an integer to a `HtmlTheme` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_theme_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Default
        1 => 1, // GitHub
        2 => 2, // Dark
        3 => 3, // Light
        4 => 4, // Unstyled
        _ => {
            set_last_error(1, "Invalid HtmlTheme variant");
            -1
        }
    }
}

/// Convert a `HtmlTheme` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_theme_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Default" => 0,
        "GitHub" => 1,
        "Dark" => 2,
        "Light" => 3,
        "Unstyled" => 4,
        _ => {
            set_last_error(1, "Unknown HtmlTheme variant");
            -1
        }
    }
}

/// Convert an integer to a `TableModel` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_model_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Tatr
        1 => 1, // SlanetWired
        2 => 2, // SlanetWireless
        3 => 3, // SlanetPlus
        4 => 4, // SlanetAuto
        5 => 5, // Disabled
        _ => {
            set_last_error(1, "Invalid TableModel variant");
            -1
        }
    }
}

/// Convert a `TableModel` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_model_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Tatr" => 0,
        "SlanetWired" => 1,
        "SlanetWireless" => 2,
        "SlanetPlus" => 3,
        "SlanetAuto" => 4,
        "Disabled" => 5,
        _ => {
            set_last_error(1, "Unknown TableModel variant");
            -1
        }
    }
}

/// Convert an integer to a `ChunkerType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunker_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Text
        1 => 1, // Markdown
        2 => 2, // Yaml
        3 => 3, // Semantic
        _ => {
            set_last_error(1, "Invalid ChunkerType variant");
            -1
        }
    }
}

/// Convert a `ChunkerType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunker_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Text" => 0,
        "Markdown" => 1,
        "Yaml" => 2,
        "Semantic" => 3,
        _ => {
            set_last_error(1, "Unknown ChunkerType variant");
            -1
        }
    }
}

/// Convert an integer to a `ChunkSizing` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_sizing_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Characters
        1 => 1, // Tokenizer
        _ => {
            set_last_error(1, "Invalid ChunkSizing variant");
            -1
        }
    }
}

/// Convert a `ChunkSizing` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_sizing_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Characters" => 0,
        "Tokenizer" => 1,
        _ => {
            set_last_error(1, "Unknown ChunkSizing variant");
            -1
        }
    }
}

/// Convert an integer to a `EmbeddingModelType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_model_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Preset
        1 => 1, // Custom
        2 => 2, // Llm
        3 => 3, // Plugin
        _ => {
            set_last_error(1, "Invalid EmbeddingModelType variant");
            -1
        }
    }
}

/// Convert a `EmbeddingModelType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_model_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Preset" => 0,
        "Custom" => 1,
        "Llm" => 2,
        "Plugin" => 3,
        _ => {
            set_last_error(1, "Unknown EmbeddingModelType variant");
            -1
        }
    }
}

/// Convert an integer to a `CodeContentMode` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_code_content_mode_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Chunks
        1 => 1, // Raw
        2 => 2, // Structure
        _ => {
            set_last_error(1, "Invalid CodeContentMode variant");
            -1
        }
    }
}

/// Convert a `CodeContentMode` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_code_content_mode_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Chunks" => 0,
        "Raw" => 1,
        "Structure" => 2,
        _ => {
            set_last_error(1, "Unknown CodeContentMode variant");
            -1
        }
    }
}

/// Convert an integer to a `ListType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Bullet
        1 => 1, // Numbered
        2 => 2, // Lettered
        3 => 3, // Indented
        _ => {
            set_last_error(1, "Invalid ListType variant");
            -1
        }
    }
}

/// Convert a `ListType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Bullet" => 0,
        "Numbered" => 1,
        "Lettered" => 2,
        "Indented" => 3,
        _ => {
            set_last_error(1, "Unknown ListType variant");
            -1
        }
    }
}

/// Convert an integer to a `OcrBackendType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_backend_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Tesseract
        1 => 1, // EasyOCR
        2 => 2, // PaddleOCR
        3 => 3, // Custom
        _ => {
            set_last_error(1, "Invalid OcrBackendType variant");
            -1
        }
    }
}

/// Convert a `OcrBackendType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_backend_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Tesseract" => 0,
        "EasyOCR" => 1,
        "PaddleOCR" => 2,
        "Custom" => 3,
        _ => {
            set_last_error(1, "Unknown OcrBackendType variant");
            -1
        }
    }
}

/// Convert an integer to a `ProcessingStage` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_stage_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Early
        1 => 1, // Middle
        2 => 2, // Late
        _ => {
            set_last_error(1, "Invalid ProcessingStage variant");
            -1
        }
    }
}

/// Convert a `ProcessingStage` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_processing_stage_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Early" => 0,
        "Middle" => 1,
        "Late" => 2,
        _ => {
            set_last_error(1, "Unknown ProcessingStage variant");
            -1
        }
    }
}

/// Convert an integer to a `ReductionLevel` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_reduction_level_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Off
        1 => 1, // Light
        2 => 2, // Moderate
        3 => 3, // Aggressive
        4 => 4, // Maximum
        _ => {
            set_last_error(1, "Invalid ReductionLevel variant");
            -1
        }
    }
}

/// Convert a `ReductionLevel` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_reduction_level_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Off" => 0,
        "Light" => 1,
        "Moderate" => 2,
        "Aggressive" => 3,
        "Maximum" => 4,
        _ => {
            set_last_error(1, "Unknown ReductionLevel variant");
            -1
        }
    }
}

/// Convert an integer to a `PdfAnnotationType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Text
        1 => 1, // Highlight
        2 => 2, // Link
        3 => 3, // Stamp
        4 => 4, // Underline
        5 => 5, // StrikeOut
        6 => 6, // Other
        _ => {
            set_last_error(1, "Invalid PdfAnnotationType variant");
            -1
        }
    }
}

/// Convert a `PdfAnnotationType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Text" => 0,
        "Highlight" => 1,
        "Link" => 2,
        "Stamp" => 3,
        "Underline" => 4,
        "StrikeOut" => 5,
        "Other" => 6,
        _ => {
            set_last_error(1, "Unknown PdfAnnotationType variant");
            -1
        }
    }
}

/// Convert an integer to a `BlockType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_block_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Paragraph
        1 => 1,   // Heading
        2 => 2,   // Blockquote
        3 => 3,   // CodeBlock
        4 => 4,   // ListItem
        5 => 5,   // OrderedList
        6 => 6,   // BulletList
        7 => 7,   // TaskList
        8 => 8,   // DefinitionList
        9 => 9,   // DefinitionTerm
        10 => 10, // DefinitionDescription
        11 => 11, // Div
        12 => 12, // Section
        13 => 13, // ThematicBreak
        14 => 14, // RawBlock
        15 => 15, // MathDisplay
        _ => {
            set_last_error(1, "Invalid BlockType variant");
            -1
        }
    }
}

/// Convert a `BlockType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_block_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Paragraph" => 0,
        "Heading" => 1,
        "Blockquote" => 2,
        "CodeBlock" => 3,
        "ListItem" => 4,
        "OrderedList" => 5,
        "BulletList" => 6,
        "TaskList" => 7,
        "DefinitionList" => 8,
        "DefinitionTerm" => 9,
        "DefinitionDescription" => 10,
        "Div" => 11,
        "Section" => 12,
        "ThematicBreak" => 13,
        "RawBlock" => 14,
        "MathDisplay" => 15,
        _ => {
            set_last_error(1, "Unknown BlockType variant");
            -1
        }
    }
}

/// Convert an integer to a `InlineType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Text
        1 => 1,   // Strong
        2 => 2,   // Emphasis
        3 => 3,   // Highlight
        4 => 4,   // Subscript
        5 => 5,   // Superscript
        6 => 6,   // Insert
        7 => 7,   // Delete
        8 => 8,   // Code
        9 => 9,   // Link
        10 => 10, // Image
        11 => 11, // Span
        12 => 12, // Math
        13 => 13, // RawInline
        14 => 14, // FootnoteRef
        15 => 15, // Symbol
        _ => {
            set_last_error(1, "Invalid InlineType variant");
            -1
        }
    }
}

/// Convert a `InlineType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Text" => 0,
        "Strong" => 1,
        "Emphasis" => 2,
        "Highlight" => 3,
        "Subscript" => 4,
        "Superscript" => 5,
        "Insert" => 6,
        "Delete" => 7,
        "Code" => 8,
        "Link" => 9,
        "Image" => 10,
        "Span" => 11,
        "Math" => 12,
        "RawInline" => 13,
        "FootnoteRef" => 14,
        "Symbol" => 15,
        _ => {
            set_last_error(1, "Unknown InlineType variant");
            -1
        }
    }
}

/// Convert an integer to a `RelationshipKind` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_relationship_kind_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // FootnoteReference
        1 => 1, // CitationReference
        2 => 2, // InternalLink
        3 => 3, // Caption
        4 => 4, // Label
        5 => 5, // TocEntry
        6 => 6, // CrossReference
        _ => {
            set_last_error(1, "Invalid RelationshipKind variant");
            -1
        }
    }
}

/// Convert a `RelationshipKind` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_relationship_kind_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "FootnoteReference" => 0,
        "CitationReference" => 1,
        "InternalLink" => 2,
        "Caption" => 3,
        "Label" => 4,
        "TocEntry" => 5,
        "CrossReference" => 6,
        _ => {
            set_last_error(1, "Unknown RelationshipKind variant");
            -1
        }
    }
}

/// Convert an integer to a `ContentLayer` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_layer_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Body
        1 => 1, // Header
        2 => 2, // Footer
        3 => 3, // Footnote
        _ => {
            set_last_error(1, "Invalid ContentLayer variant");
            -1
        }
    }
}

/// Convert a `ContentLayer` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_layer_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Body" => 0,
        "Header" => 1,
        "Footer" => 2,
        "Footnote" => 3,
        _ => {
            set_last_error(1, "Unknown ContentLayer variant");
            -1
        }
    }
}

/// Convert an integer to a `NodeContent` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_node_content_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Title
        1 => 1,   // Heading
        2 => 2,   // Paragraph
        3 => 3,   // List
        4 => 4,   // ListItem
        5 => 5,   // Table
        6 => 6,   // Image
        7 => 7,   // Code
        8 => 8,   // Quote
        9 => 9,   // Formula
        10 => 10, // Footnote
        11 => 11, // Group
        12 => 12, // PageBreak
        13 => 13, // Slide
        14 => 14, // DefinitionList
        15 => 15, // DefinitionItem
        16 => 16, // Citation
        17 => 17, // Admonition
        18 => 18, // RawBlock
        19 => 19, // MetadataBlock
        _ => {
            set_last_error(1, "Invalid NodeContent variant");
            -1
        }
    }
}

/// Convert a `NodeContent` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_node_content_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Title" => 0,
        "Heading" => 1,
        "Paragraph" => 2,
        "List" => 3,
        "ListItem" => 4,
        "Table" => 5,
        "Image" => 6,
        "Code" => 7,
        "Quote" => 8,
        "Formula" => 9,
        "Footnote" => 10,
        "Group" => 11,
        "PageBreak" => 12,
        "Slide" => 13,
        "DefinitionList" => 14,
        "DefinitionItem" => 15,
        "Citation" => 16,
        "Admonition" => 17,
        "RawBlock" => 18,
        "MetadataBlock" => 19,
        _ => {
            set_last_error(1, "Unknown NodeContent variant");
            -1
        }
    }
}

/// Convert an integer to a `AnnotationKind` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_annotation_kind_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Bold
        1 => 1,   // Italic
        2 => 2,   // Underline
        3 => 3,   // Strikethrough
        4 => 4,   // Code
        5 => 5,   // Subscript
        6 => 6,   // Superscript
        7 => 7,   // Link
        8 => 8,   // Highlight
        9 => 9,   // Color
        10 => 10, // FontSize
        11 => 11, // Custom
        _ => {
            set_last_error(1, "Invalid AnnotationKind variant");
            -1
        }
    }
}

/// Convert a `AnnotationKind` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_annotation_kind_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Bold" => 0,
        "Italic" => 1,
        "Underline" => 2,
        "Strikethrough" => 3,
        "Code" => 4,
        "Subscript" => 5,
        "Superscript" => 6,
        "Link" => 7,
        "Highlight" => 8,
        "Color" => 9,
        "FontSize" => 10,
        "Custom" => 11,
        _ => {
            set_last_error(1, "Unknown AnnotationKind variant");
            -1
        }
    }
}

/// Convert an integer to a `ExtractionMethod` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_method_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Native
        1 => 1, // Ocr
        2 => 2, // Mixed
        _ => {
            set_last_error(1, "Invalid ExtractionMethod variant");
            -1
        }
    }
}

/// Convert a `ExtractionMethod` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_method_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Native" => 0,
        "Ocr" => 1,
        "Mixed" => 2,
        _ => {
            set_last_error(1, "Unknown ExtractionMethod variant");
            -1
        }
    }
}

/// Convert an integer to a `ChunkType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Heading
        1 => 1,   // PartyList
        2 => 2,   // Definitions
        3 => 3,   // OperativeClause
        4 => 4,   // SignatureBlock
        5 => 5,   // Schedule
        6 => 6,   // TableLike
        7 => 7,   // Formula
        8 => 8,   // CodeBlock
        9 => 9,   // Image
        10 => 10, // OrgChart
        11 => 11, // Diagram
        12 => 12, // Unknown
        _ => {
            set_last_error(1, "Invalid ChunkType variant");
            -1
        }
    }
}

/// Convert a `ChunkType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Heading" => 0,
        "PartyList" => 1,
        "Definitions" => 2,
        "OperativeClause" => 3,
        "SignatureBlock" => 4,
        "Schedule" => 5,
        "TableLike" => 6,
        "Formula" => 7,
        "CodeBlock" => 8,
        "Image" => 9,
        "OrgChart" => 10,
        "Diagram" => 11,
        "Unknown" => 12,
        _ => {
            set_last_error(1, "Unknown ChunkType variant");
            -1
        }
    }
}

/// Convert an integer to a `ImageKind` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_kind_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Photograph
        1 => 1,   // Diagram
        2 => 2,   // Chart
        3 => 3,   // Drawing
        4 => 4,   // TextBlock
        5 => 5,   // Decoration
        6 => 6,   // Logo
        7 => 7,   // Icon
        8 => 8,   // TileFragment
        9 => 9,   // Mask
        10 => 10, // PageRaster
        11 => 11, // Unknown
        _ => {
            set_last_error(1, "Invalid ImageKind variant");
            -1
        }
    }
}

/// Convert a `ImageKind` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_kind_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Photograph" => 0,
        "Diagram" => 1,
        "Chart" => 2,
        "Drawing" => 3,
        "TextBlock" => 4,
        "Decoration" => 5,
        "Logo" => 6,
        "Icon" => 7,
        "TileFragment" => 8,
        "Mask" => 9,
        "PageRaster" => 10,
        "Unknown" => 11,
        _ => {
            set_last_error(1, "Unknown ImageKind variant");
            -1
        }
    }
}

/// Convert an integer to a `ResultFormat` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_result_format_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Unified
        1 => 1, // ElementBased
        _ => {
            set_last_error(1, "Invalid ResultFormat variant");
            -1
        }
    }
}

/// Convert a `ResultFormat` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_result_format_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Unified" => 0,
        "ElementBased" => 1,
        _ => {
            set_last_error(1, "Unknown ResultFormat variant");
            -1
        }
    }
}

/// Convert an integer to a `ElementType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Title
        1 => 1,   // NarrativeText
        2 => 2,   // Heading
        3 => 3,   // ListItem
        4 => 4,   // Table
        5 => 5,   // Image
        6 => 6,   // PageBreak
        7 => 7,   // CodeBlock
        8 => 8,   // BlockQuote
        9 => 9,   // Footer
        10 => 10, // Header
        _ => {
            set_last_error(1, "Invalid ElementType variant");
            -1
        }
    }
}

/// Convert a `ElementType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Title" => 0,
        "NarrativeText" => 1,
        "Heading" => 2,
        "ListItem" => 3,
        "Table" => 4,
        "Image" => 5,
        "PageBreak" => 6,
        "CodeBlock" => 7,
        "BlockQuote" => 8,
        "Footer" => 9,
        "Header" => 10,
        _ => {
            set_last_error(1, "Unknown ElementType variant");
            -1
        }
    }
}

/// Convert an integer to a `FormatMetadata` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_format_metadata_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Pdf
        1 => 1,   // Docx
        2 => 2,   // Excel
        3 => 3,   // Email
        4 => 4,   // Pptx
        5 => 5,   // Archive
        6 => 6,   // Image
        7 => 7,   // Xml
        8 => 8,   // Text
        9 => 9,   // Html
        10 => 10, // Ocr
        11 => 11, // Csv
        12 => 12, // Bibtex
        13 => 13, // Citation
        14 => 14, // FictionBook
        15 => 15, // Dbf
        16 => 16, // Jats
        17 => 17, // Epub
        18 => 18, // Pst
        19 => 19, // Code
        _ => {
            set_last_error(1, "Invalid FormatMetadata variant");
            -1
        }
    }
}

/// Convert a `FormatMetadata` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_format_metadata_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Pdf" => 0,
        "Docx" => 1,
        "Excel" => 2,
        "Email" => 3,
        "Pptx" => 4,
        "Archive" => 5,
        "Image" => 6,
        "Xml" => 7,
        "Text" => 8,
        "Html" => 9,
        "Ocr" => 10,
        "Csv" => 11,
        "Bibtex" => 12,
        "Citation" => 13,
        "FictionBook" => 14,
        "Dbf" => 15,
        "Jats" => 16,
        "Epub" => 17,
        "Pst" => 18,
        "Code" => 19,
        _ => {
            set_last_error(1, "Unknown FormatMetadata variant");
            -1
        }
    }
}

/// Convert an integer to a `TextDirection` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_direction_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // LeftToRight
        1 => 1, // RightToLeft
        2 => 2, // Auto
        _ => {
            set_last_error(1, "Invalid TextDirection variant");
            -1
        }
    }
}

/// Convert a `TextDirection` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_direction_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "LeftToRight" => 0,
        "RightToLeft" => 1,
        "Auto" => 2,
        _ => {
            set_last_error(1, "Unknown TextDirection variant");
            -1
        }
    }
}

/// Convert an integer to a `LinkType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Anchor
        1 => 1, // Internal
        2 => 2, // External
        3 => 3, // Email
        4 => 4, // Phone
        5 => 5, // Other
        _ => {
            set_last_error(1, "Invalid LinkType variant");
            -1
        }
    }
}

/// Convert a `LinkType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Anchor" => 0,
        "Internal" => 1,
        "External" => 2,
        "Email" => 3,
        "Phone" => 4,
        "Other" => 5,
        _ => {
            set_last_error(1, "Unknown LinkType variant");
            -1
        }
    }
}

/// Convert an integer to a `ImageType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // DataUri
        1 => 1, // InlineSvg
        2 => 2, // External
        3 => 3, // Relative
        _ => {
            set_last_error(1, "Invalid ImageType variant");
            -1
        }
    }
}

/// Convert a `ImageType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "DataUri" => 0,
        "InlineSvg" => 1,
        "External" => 2,
        "Relative" => 3,
        _ => {
            set_last_error(1, "Unknown ImageType variant");
            -1
        }
    }
}

/// Convert an integer to a `StructuredDataType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // JsonLd
        1 => 1, // Microdata
        2 => 2, // RDFa
        _ => {
            set_last_error(1, "Invalid StructuredDataType variant");
            -1
        }
    }
}

/// Convert a `StructuredDataType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "JsonLd" => 0,
        "Microdata" => 1,
        "RDFa" => 2,
        _ => {
            set_last_error(1, "Unknown StructuredDataType variant");
            -1
        }
    }
}

/// Convert an integer to a `OcrBoundingGeometry` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_bounding_geometry_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Rectangle
        1 => 1, // Quadrilateral
        _ => {
            set_last_error(1, "Invalid OcrBoundingGeometry variant");
            -1
        }
    }
}

/// Convert a `OcrBoundingGeometry` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_bounding_geometry_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Rectangle" => 0,
        "Quadrilateral" => 1,
        _ => {
            set_last_error(1, "Unknown OcrBoundingGeometry variant");
            -1
        }
    }
}

/// Convert an integer to a `OcrElementLevel` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_level_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Word
        1 => 1, // Line
        2 => 2, // Block
        3 => 3, // Page
        _ => {
            set_last_error(1, "Invalid OcrElementLevel variant");
            -1
        }
    }
}

/// Convert a `OcrElementLevel` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_level_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Word" => 0,
        "Line" => 1,
        "Block" => 2,
        "Page" => 3,
        _ => {
            set_last_error(1, "Unknown OcrElementLevel variant");
            -1
        }
    }
}

/// Convert an integer to a `PageUnitType` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_unit_type_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Page
        1 => 1, // Slide
        2 => 2, // Sheet
        _ => {
            set_last_error(1, "Invalid PageUnitType variant");
            -1
        }
    }
}

/// Convert a `PageUnitType` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_unit_type_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Page" => 0,
        "Slide" => 1,
        "Sheet" => 2,
        _ => {
            set_last_error(1, "Unknown PageUnitType variant");
            -1
        }
    }
}

/// Convert an integer to a `DiffLine` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_line_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Context
        1 => 1, // Added
        2 => 2, // Removed
        _ => {
            set_last_error(1, "Invalid DiffLine variant");
            -1
        }
    }
}

/// Convert a `DiffLine` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_diff_line_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Context" => 0,
        "Added" => 1,
        "Removed" => 2,
        _ => {
            set_last_error(1, "Unknown DiffLine variant");
            -1
        }
    }
}

/// Convert an integer to a `RevisionKind` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_kind_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Insertion
        1 => 1, // Deletion
        2 => 2, // FormatChange
        3 => 3, // Comment
        _ => {
            set_last_error(1, "Invalid RevisionKind variant");
            -1
        }
    }
}

/// Convert a `RevisionKind` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_kind_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Insertion" => 0,
        "Deletion" => 1,
        "FormatChange" => 2,
        "Comment" => 3,
        _ => {
            set_last_error(1, "Unknown RevisionKind variant");
            -1
        }
    }
}

/// Convert an integer to a `RevisionAnchor` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_anchor_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Paragraph
        1 => 1, // TableCell
        2 => 2, // Page
        3 => 3, // Slide
        4 => 4, // Sheet
        _ => {
            set_last_error(1, "Invalid RevisionAnchor variant");
            -1
        }
    }
}

/// Convert a `RevisionAnchor` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_anchor_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Paragraph" => 0,
        "TableCell" => 1,
        "Page" => 2,
        "Slide" => 3,
        "Sheet" => 4,
        _ => {
            set_last_error(1, "Unknown RevisionAnchor variant");
            -1
        }
    }
}

/// Convert an integer to a `UriKind` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_uri_kind_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Hyperlink
        1 => 1, // Image
        2 => 2, // Anchor
        3 => 3, // Citation
        4 => 4, // Reference
        5 => 5, // Email
        _ => {
            set_last_error(1, "Invalid UriKind variant");
            -1
        }
    }
}

/// Convert a `UriKind` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_uri_kind_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Hyperlink" => 0,
        "Image" => 1,
        "Anchor" => 2,
        "Citation" => 3,
        "Reference" => 4,
        "Email" => 5,
        _ => {
            set_last_error(1, "Unknown UriKind variant");
            -1
        }
    }
}

/// Convert an integer to a `KeywordAlgorithm` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_algorithm_from_i32(value: i32) -> i32 {
    match value {
        0 => 0, // Yake
        1 => 1, // Rake
        _ => {
            set_last_error(1, "Invalid KeywordAlgorithm variant");
            -1
        }
    }
}

/// Convert a `KeywordAlgorithm` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_algorithm_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Yake" => 0,
        "Rake" => 1,
        _ => {
            set_last_error(1, "Unknown KeywordAlgorithm variant");
            -1
        }
    }
}

/// Convert an integer to a `PSMMode` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_psm_mode_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // OsdOnly
        1 => 1,   // AutoOsd
        2 => 2,   // AutoOnly
        3 => 3,   // Auto
        4 => 4,   // SingleColumn
        5 => 5,   // SingleBlockVertical
        6 => 6,   // SingleBlock
        7 => 7,   // SingleLine
        8 => 8,   // SingleWord
        9 => 9,   // CircleWord
        10 => 10, // SingleChar
        _ => {
            set_last_error(1, "Invalid PSMMode variant");
            -1
        }
    }
}

/// Convert a `PSMMode` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_psm_mode_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "OsdOnly" => 0,
        "AutoOsd" => 1,
        "AutoOnly" => 2,
        "Auto" => 3,
        "SingleColumn" => 4,
        "SingleBlockVertical" => 5,
        "SingleBlock" => 6,
        "SingleLine" => 7,
        "SingleWord" => 8,
        "CircleWord" => 9,
        "SingleChar" => 10,
        _ => {
            set_last_error(1, "Unknown PSMMode variant");
            -1
        }
    }
}

/// Convert an integer to a `PaddleLanguage` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_language_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // English
        1 => 1,   // Chinese
        2 => 2,   // Japanese
        3 => 3,   // Korean
        4 => 4,   // German
        5 => 5,   // French
        6 => 6,   // Latin
        7 => 7,   // Cyrillic
        8 => 8,   // TraditionalChinese
        9 => 9,   // Thai
        10 => 10, // Greek
        11 => 11, // EastSlavic
        12 => 12, // Arabic
        13 => 13, // Devanagari
        14 => 14, // Tamil
        15 => 15, // Telugu
        _ => {
            set_last_error(1, "Invalid PaddleLanguage variant");
            -1
        }
    }
}

/// Convert a `PaddleLanguage` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_paddle_language_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "English" => 0,
        "Chinese" => 1,
        "Japanese" => 2,
        "Korean" => 3,
        "German" => 4,
        "French" => 5,
        "Latin" => 6,
        "Cyrillic" => 7,
        "TraditionalChinese" => 8,
        "Thai" => 9,
        "Greek" => 10,
        "EastSlavic" => 11,
        "Arabic" => 12,
        "Devanagari" => 13,
        "Tamil" => 14,
        "Telugu" => 15,
        _ => {
            set_last_error(1, "Unknown PaddleLanguage variant");
            -1
        }
    }
}

/// Convert an integer to a `LayoutClass` variant. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure all pointer arguments are valid or null.
/// Returned pointers must be freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_class_from_i32(value: i32) -> i32 {
    match value {
        0 => 0,   // Caption
        1 => 1,   // Footnote
        2 => 2,   // Formula
        3 => 3,   // ListItem
        4 => 4,   // PageFooter
        5 => 5,   // PageHeader
        6 => 6,   // Picture
        7 => 7,   // SectionHeader
        8 => 8,   // Table
        9 => 9,   // Text
        10 => 10, // Title
        11 => 11, // DocumentIndex
        12 => 12, // Code
        13 => 13, // CheckboxSelected
        14 => 14, // CheckboxUnselected
        15 => 15, // Form
        16 => 16, // KeyValueRegion
        _ => {
            set_last_error(1, "Invalid LayoutClass variant");
            -1
        }
    }
}

/// Convert a `LayoutClass` variant name (C string) to its integer value. Returns -1 on invalid input.
/// # Safety
/// Caller must ensure `ptr` is a valid pointer to a `c_char` or null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_class_from_str(name: *const c_char) -> i32 {
    if name.is_null() {
        set_last_error(1, "Null pointer passed for enum name");
        return -1;
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let s = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in enum name");
            return -1;
        }
    };
    match s {
        "Caption" => 0,
        "Footnote" => 1,
        "Formula" => 2,
        "ListItem" => 3,
        "PageFooter" => 4,
        "PageHeader" => 5,
        "Picture" => 6,
        "SectionHeader" => 7,
        "Table" => 8,
        "Text" => 9,
        "Title" => 10,
        "DocumentIndex" => 11,
        "Code" => 12,
        "CheckboxSelected" => 13,
        "CheckboxUnselected" => 14,
        "Form" => 15,
        "KeyValueRegion" => 16,
        _ => {
            set_last_error(1, "Unknown LayoutClass variant");
            -1
        }
    }
}

/// Free a heap-allocated `ExecutionProviderType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_execution_provider_type_free(ptr: *mut kreuzberg::ExecutionProviderType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ExecutionProviderType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_execution_provider_type_to_json(
    ptr: *const kreuzberg::ExecutionProviderType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_execution_provider_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ExecutionProviderType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_execution_provider_type_to_string(
    ptr: *const kreuzberg::ExecutionProviderType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_execution_provider_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ExecutionProviderType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `OutputFormat` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_output_format_free(ptr: *mut kreuzberg::OutputFormat) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `OutputFormat` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_output_format_to_json(ptr: *const kreuzberg::OutputFormat) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_output_format_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `OutputFormat` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_output_format_to_string(ptr: *const kreuzberg::OutputFormat) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_output_format_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "OutputFormat variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `HtmlTheme` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_theme_free(ptr: *mut kreuzberg::HtmlTheme) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `HtmlTheme` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_theme_to_json(ptr: *const kreuzberg::HtmlTheme) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_html_theme_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `HtmlTheme` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_html_theme_to_string(ptr: *const kreuzberg::HtmlTheme) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_html_theme_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "HtmlTheme variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `TableModel` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_model_free(ptr: *mut kreuzberg::TableModel) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `TableModel` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_model_to_json(ptr: *const kreuzberg::TableModel) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_table_model_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `TableModel` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_table_model_to_string(ptr: *const kreuzberg::TableModel) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_table_model_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "TableModel variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ChunkerType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunker_type_free(ptr: *mut kreuzberg::ChunkerType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ChunkerType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunker_type_to_json(ptr: *const kreuzberg::ChunkerType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_chunker_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ChunkerType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunker_type_to_string(ptr: *const kreuzberg::ChunkerType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_chunker_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ChunkerType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ChunkSizing` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_sizing_free(ptr: *mut kreuzberg::ChunkSizing) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ChunkSizing` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_sizing_to_json(ptr: *const kreuzberg::ChunkSizing) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_chunk_sizing_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ChunkSizing` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_sizing_to_string(ptr: *const kreuzberg::ChunkSizing) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_chunk_sizing_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ChunkSizing variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `EmbeddingModelType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_model_type_free(ptr: *mut kreuzberg::EmbeddingModelType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `EmbeddingModelType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_model_type_to_json(
    ptr: *const kreuzberg::EmbeddingModelType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_embedding_model_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `EmbeddingModelType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embedding_model_type_to_string(
    ptr: *const kreuzberg::EmbeddingModelType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_embedding_model_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "EmbeddingModelType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `CodeContentMode` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_code_content_mode_free(ptr: *mut kreuzberg::CodeContentMode) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `CodeContentMode` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_code_content_mode_to_json(ptr: *const kreuzberg::CodeContentMode) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_code_content_mode_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `CodeContentMode` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_code_content_mode_to_string(ptr: *const kreuzberg::CodeContentMode) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_code_content_mode_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "CodeContentMode variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ReductionLevel` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_reduction_level_free(ptr: *mut kreuzberg::ReductionLevel) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ReductionLevel` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_reduction_level_to_json(ptr: *const kreuzberg::ReductionLevel) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_reduction_level_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ReductionLevel` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_reduction_level_to_string(ptr: *const kreuzberg::ReductionLevel) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_reduction_level_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ReductionLevel variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `PdfAnnotationType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_type_free(ptr: *mut kreuzberg::PdfAnnotationType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `PdfAnnotationType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_type_to_json(
    ptr: *const kreuzberg::PdfAnnotationType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_pdf_annotation_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `PdfAnnotationType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_pdf_annotation_type_to_string(
    ptr: *const kreuzberg::PdfAnnotationType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_pdf_annotation_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "PdfAnnotationType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `BlockType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_block_type_free(ptr: *mut kreuzberg::BlockType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `BlockType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_block_type_to_json(ptr: *const kreuzberg::BlockType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_block_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `BlockType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_block_type_to_string(ptr: *const kreuzberg::BlockType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_block_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "BlockType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `InlineType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_type_free(ptr: *mut kreuzberg::InlineType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `InlineType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_type_to_json(ptr: *const kreuzberg::InlineType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_inline_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `InlineType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_inline_type_to_string(ptr: *const kreuzberg::InlineType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_inline_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "InlineType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `RelationshipKind` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_relationship_kind_free(ptr: *mut kreuzberg::RelationshipKind) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `RelationshipKind` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_relationship_kind_to_json(ptr: *const kreuzberg::RelationshipKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_relationship_kind_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `RelationshipKind` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_relationship_kind_to_string(ptr: *const kreuzberg::RelationshipKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_relationship_kind_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "RelationshipKind variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ContentLayer` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_layer_free(ptr: *mut kreuzberg::ContentLayer) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ContentLayer` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_layer_to_json(ptr: *const kreuzberg::ContentLayer) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_content_layer_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ContentLayer` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_content_layer_to_string(ptr: *const kreuzberg::ContentLayer) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_content_layer_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ContentLayer variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `NodeContent` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_node_content_free(ptr: *mut kreuzberg::NodeContent) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `NodeContent` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_node_content_to_json(ptr: *const kreuzberg::NodeContent) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_node_content_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `NodeContent` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_node_content_to_string(ptr: *const kreuzberg::NodeContent) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_node_content_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "NodeContent variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `AnnotationKind` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_annotation_kind_free(ptr: *mut kreuzberg::AnnotationKind) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `AnnotationKind` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_annotation_kind_to_json(ptr: *const kreuzberg::AnnotationKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_annotation_kind_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `AnnotationKind` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_annotation_kind_to_string(ptr: *const kreuzberg::AnnotationKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_annotation_kind_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "AnnotationKind variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ExtractionMethod` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_method_free(ptr: *mut kreuzberg::ExtractionMethod) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ExtractionMethod` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_method_to_json(ptr: *const kreuzberg::ExtractionMethod) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_extraction_method_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ExtractionMethod` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extraction_method_to_string(ptr: *const kreuzberg::ExtractionMethod) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_extraction_method_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ExtractionMethod variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ChunkType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_type_free(ptr: *mut kreuzberg::ChunkType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ChunkType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_type_to_json(ptr: *const kreuzberg::ChunkType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_chunk_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ChunkType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_chunk_type_to_string(ptr: *const kreuzberg::ChunkType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_chunk_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ChunkType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ImageKind` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_kind_free(ptr: *mut kreuzberg::ImageKind) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ImageKind` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_kind_to_json(ptr: *const kreuzberg::ImageKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_image_kind_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ImageKind` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_kind_to_string(ptr: *const kreuzberg::ImageKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_image_kind_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ImageKind variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ResultFormat` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_result_format_free(ptr: *mut kreuzberg::ResultFormat) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ResultFormat` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_result_format_to_json(ptr: *const kreuzberg::ResultFormat) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_result_format_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ResultFormat` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_result_format_to_string(ptr: *const kreuzberg::ResultFormat) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_result_format_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ResultFormat variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ElementType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_type_free(ptr: *mut kreuzberg::ElementType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ElementType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_type_to_json(ptr: *const kreuzberg::ElementType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_element_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ElementType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_element_type_to_string(ptr: *const kreuzberg::ElementType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_element_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ElementType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `FormatMetadata` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_format_metadata_free(ptr: *mut kreuzberg::FormatMetadata) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `FormatMetadata` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_format_metadata_to_json(ptr: *const kreuzberg::FormatMetadata) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_format_metadata_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `FormatMetadata` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_format_metadata_to_string(ptr: *const kreuzberg::FormatMetadata) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_format_metadata_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "FormatMetadata variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `TextDirection` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_direction_free(ptr: *mut kreuzberg::TextDirection) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `TextDirection` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_direction_to_json(ptr: *const kreuzberg::TextDirection) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_text_direction_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `TextDirection` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_text_direction_to_string(ptr: *const kreuzberg::TextDirection) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_text_direction_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "TextDirection variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `LinkType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_type_free(ptr: *mut kreuzberg::LinkType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `LinkType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_type_to_json(ptr: *const kreuzberg::LinkType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_link_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `LinkType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_link_type_to_string(ptr: *const kreuzberg::LinkType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_link_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "LinkType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `ImageType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_type_free(ptr: *mut kreuzberg::ImageType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `ImageType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_type_to_json(ptr: *const kreuzberg::ImageType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_image_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `ImageType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_image_type_to_string(ptr: *const kreuzberg::ImageType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_image_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "ImageType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `StructuredDataType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_type_free(ptr: *mut kreuzberg::StructuredDataType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `StructuredDataType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_type_to_json(
    ptr: *const kreuzberg::StructuredDataType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_structured_data_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `StructuredDataType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_structured_data_type_to_string(
    ptr: *const kreuzberg::StructuredDataType,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_structured_data_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "StructuredDataType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `OcrBoundingGeometry` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_bounding_geometry_free(ptr: *mut kreuzberg::OcrBoundingGeometry) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `OcrBoundingGeometry` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_bounding_geometry_to_json(
    ptr: *const kreuzberg::OcrBoundingGeometry,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_ocr_bounding_geometry_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `OcrBoundingGeometry` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_bounding_geometry_to_string(
    ptr: *const kreuzberg::OcrBoundingGeometry,
) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_ocr_bounding_geometry_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "OcrBoundingGeometry variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `OcrElementLevel` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_level_free(ptr: *mut kreuzberg::OcrElementLevel) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `OcrElementLevel` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_level_to_json(ptr: *const kreuzberg::OcrElementLevel) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_ocr_element_level_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `OcrElementLevel` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_ocr_element_level_to_string(ptr: *const kreuzberg::OcrElementLevel) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_ocr_element_level_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "OcrElementLevel variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `PageUnitType` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_unit_type_free(ptr: *mut kreuzberg::PageUnitType) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `PageUnitType` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_unit_type_to_json(ptr: *const kreuzberg::PageUnitType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_page_unit_type_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `PageUnitType` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_page_unit_type_to_string(ptr: *const kreuzberg::PageUnitType) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_page_unit_type_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "PageUnitType variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `RevisionKind` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_kind_free(ptr: *mut kreuzberg::RevisionKind) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `RevisionKind` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_kind_to_json(ptr: *const kreuzberg::RevisionKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_revision_kind_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `RevisionKind` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_kind_to_string(ptr: *const kreuzberg::RevisionKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_revision_kind_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "RevisionKind variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `RevisionAnchor` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_anchor_free(ptr: *mut kreuzberg::RevisionAnchor) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `RevisionAnchor` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_anchor_to_json(ptr: *const kreuzberg::RevisionAnchor) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_revision_anchor_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `RevisionAnchor` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_revision_anchor_to_string(ptr: *const kreuzberg::RevisionAnchor) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_revision_anchor_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "RevisionAnchor variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `UriKind` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_uri_kind_free(ptr: *mut kreuzberg::UriKind) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `UriKind` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_uri_kind_to_json(ptr: *const kreuzberg::UriKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_uri_kind_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `UriKind` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_uri_kind_to_string(ptr: *const kreuzberg::UriKind) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_uri_kind_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "UriKind variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `KeywordAlgorithm` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_algorithm_free(ptr: *mut kreuzberg::KeywordAlgorithm) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `KeywordAlgorithm` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_algorithm_to_json(ptr: *const kreuzberg::KeywordAlgorithm) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_keyword_algorithm_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `KeywordAlgorithm` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_keyword_algorithm_to_string(ptr: *const kreuzberg::KeywordAlgorithm) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_keyword_algorithm_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "KeywordAlgorithm variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

/// Free a heap-allocated `LayoutClass` returned by a pointer-returning FFI function.
/// # Safety
/// Pointer must have been returned by this library, or be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_class_free(ptr: *mut kreuzberg::LayoutClass) {
    if !ptr.is_null() {
        // SAFETY: ptr was allocated by Box::into_raw; caller ensures no aliases.
        unsafe {
            drop(Box::from_raw(ptr));
        }
    }
}

/// Serialize a heap-allocated `LayoutClass` to a JSON string.
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_class_to_json(ptr: *const kreuzberg::LayoutClass) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_layout_class_to_json");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    match serde_json::to_string(val) {
        Ok(s) => match CString::new(s) {
            Ok(cs) => cs.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Render a heap-allocated `LayoutClass` as its string representation
/// (the unit-variant name as serialized by serde — e.g. `"completed"`,
/// without surrounding JSON quotes).
/// # Safety
/// `ptr` must be a valid, non-null pointer returned by a `kreuzberg` function.
/// The returned string must be freed with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_layout_class_to_string(ptr: *const kreuzberg::LayoutClass) -> *mut c_char {
    if ptr.is_null() {
        set_last_error(1, "Null pointer passed to kreuzberg_layout_class_to_string");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees ptr is valid; no mutable aliases held.
    let val = unsafe { &*ptr };
    let s: String = serde_json::to_value(val)
        .ok()
        .and_then(|v| v.as_str().map(str::to_owned))
        .unwrap_or_default();
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        Err(_) => {
            set_last_error(1, "LayoutClass variant contained interior NUL byte");
            std::ptr::null_mut()
        }
    }
}

fn get_ffi_runtime() -> &'static tokio::runtime::Runtime {
    use std::sync::OnceLock;
    static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
    RUNTIME.get_or_init(|| tokio::runtime::Runtime::new().expect("Failed to create tokio runtime"))
}

/// Extract content from a byte array.
///
/// This is the main entry point for in-memory extraction. It performs the following steps:
/// 1. Validate MIME type
/// 2. Handle legacy format conversion if needed
/// 3. Select appropriate extractor from registry
/// 4. Extract content
/// 5. Run post-processing pipeline
/// \param content The byte array to extract
/// \param mime_type MIME type of the content
/// \param config Extraction configuration
/// \return An `ExtractionResult` containing the extracted content and metadata.
/// \note Returns `KreuzbergError::Validation` if MIME type is invalid.
/// Returns `KreuzbergError::UnsupportedFormat` if MIME type is not supported.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::extractor::extract_bytes;
/// use kreuzberg::core::config::ExtractionConfig;
///
/// let config = ExtractionConfig::default();
/// let bytes = b"Hello, world!";
/// let result = extract_bytes(bytes, "text/plain", &config).await?;
/// println!("Content: {}", result.content);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extract_bytes(
    content: *const u8,
    content_len: usize,
    mime_type: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ExtractionResult {
    clear_last_error();
    if content.is_null() && content_len > 0 {
        set_last_error(1, "Null pointer passed for parameter 'content'");
        return std::ptr::null_mut();
    }
    // SAFETY: when content is null, content_len is 0 (checked above), so we use an empty slice; otherwise data is valid for len elements.
    let content_rs = if content.is_null() {
        Vec::new()
    } else {
        unsafe { std::slice::from_raw_parts(content, content_len) }.to_vec()
    };
    if mime_type.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'mime_type'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees mime_type is a valid pointer; string is valid UTF-8 from caller.
    let mime_type_rs = match unsafe { CStr::from_ptr(mime_type) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'mime_type'");
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result =
        get_ffi_runtime().block_on(async { kreuzberg::extract_bytes(&content_rs, &mime_type_rs, &config_rs).await });
    match result {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Extract content from a file.
///
/// This is the main entry point for file-based extraction. It performs the following steps:
/// 1. Check cache for existing result (if caching enabled)
/// 2. Detect or validate MIME type
/// 3. Select appropriate extractor from registry
/// 4. Extract content
/// 5. Run post-processing pipeline
/// 6. Store result in cache (if caching enabled)
/// \param path Path to the file to extract
/// \param mime_type Optional MIME type override. If None, will be auto-detected
/// \param config Extraction configuration
/// \return An `ExtractionResult` containing the extracted content and metadata.
/// \note Returns `KreuzbergError::Io` if the file doesn't exist (NotFound) or for other file I/O
/// errors.
/// Returns `KreuzbergError::UnsupportedFormat` if MIME type is not supported.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::extractor::extract_file;
/// use kreuzberg::core::config::ExtractionConfig;
///
/// let config = ExtractionConfig::default();
/// let result = extract_file("document.pdf", None, &config).await?;
/// println!("Content: {}", result.content);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extract_file(
    path: *const std::ffi::c_char,
    mime_type: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ExtractionResult {
    clear_last_error();
    if path.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'path'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees path is a valid pointer; string is valid UTF-8 from caller.
    let path_rs = match unsafe { CStr::from_ptr(path) }.to_str() {
        Ok(s) => std::path::PathBuf::from(s),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'path'");
            return std::ptr::null_mut();
        }
    };
    let mime_type_rs = if mime_type.is_null() {
        None
    } else {
        // SAFETY: null check above guarantees mime_type is a valid pointer; string is valid UTF-8 from caller.
        match unsafe { CStr::from_ptr(mime_type) }.to_str() {
            Ok(s) => Some(s.to_string()),
            Err(_) => {
                set_last_error(1, "Invalid UTF-8 in parameter 'mime_type'");
                return std::ptr::null_mut();
            }
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = get_ffi_runtime()
        .block_on(async { kreuzberg::extract_file(path_rs, mime_type_rs.as_deref(), &config_rs).await });
    match result {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Synchronous wrapper for `extract_file`.
///
/// This is a convenience function that blocks the current thread until extraction completes.
/// For async code, use `extract_file` directly.
///
/// Uses the global Tokio runtime for 100x+ performance improvement over creating
/// a new runtime per call. Always uses the global runtime to avoid nested runtime issues.
///
/// This function is only available with the `tokio-runtime` feature. For WASM targets,
/// use a truly synchronous extraction approach instead.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::extractor::extract_file_sync;
/// use kreuzberg::core::config::ExtractionConfig;
///
/// let config = ExtractionConfig::default();
/// let result = extract_file_sync("document.pdf", None, &config)?;
/// println!("Content: {}", result.content);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extract_file_sync(
    path: *const std::ffi::c_char,
    mime_type: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ExtractionResult {
    clear_last_error();
    if path.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'path'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees path is a valid pointer; string is valid UTF-8 from caller.
    let path_rs = match unsafe { CStr::from_ptr(path) }.to_str() {
        Ok(s) => std::path::PathBuf::from(s),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'path'");
            return std::ptr::null_mut();
        }
    };
    let mime_type_rs = if mime_type.is_null() {
        None
    } else {
        // SAFETY: null check above guarantees mime_type is a valid pointer; string is valid UTF-8 from caller.
        match unsafe { CStr::from_ptr(mime_type) }.to_str() {
            Ok(s) => Some(s.to_string()),
            Err(_) => {
                set_last_error(1, "Invalid UTF-8 in parameter 'mime_type'");
                return std::ptr::null_mut();
            }
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = kreuzberg::extract_file_sync(path_rs, mime_type_rs.as_deref(), &config_rs);
    match result {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Synchronous wrapper for `extract_bytes`.
///
/// Uses the global Tokio runtime for 100x+ performance improvement over creating
/// a new runtime per call.
///
/// With the `tokio-runtime` feature, this blocks the current thread using the global
/// Tokio runtime. Without it (WASM), this calls a truly synchronous implementation.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::extractor::extract_bytes_sync;
/// use kreuzberg::core::config::ExtractionConfig;
///
/// let config = ExtractionConfig::default();
/// let bytes = b"Hello, world!";
/// let result = extract_bytes_sync(bytes, "text/plain", &config)?;
/// println!("Content: {}", result.content);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_extract_bytes_sync(
    content: *const u8,
    content_len: usize,
    mime_type: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut kreuzberg::ExtractionResult {
    clear_last_error();
    if content.is_null() && content_len > 0 {
        set_last_error(1, "Null pointer passed for parameter 'content'");
        return std::ptr::null_mut();
    }
    // SAFETY: when content is null, content_len is 0 (checked above), so we use an empty slice; otherwise data is valid for len elements.
    let content_rs = if content.is_null() {
        Vec::new()
    } else {
        unsafe { std::slice::from_raw_parts(content, content_len) }.to_vec()
    };
    if mime_type.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'mime_type'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees mime_type is a valid pointer; string is valid UTF-8 from caller.
    let mime_type_rs = match unsafe { CStr::from_ptr(mime_type) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'mime_type'");
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = kreuzberg::extract_bytes_sync(&content_rs, &mime_type_rs, &config_rs);
    match result {
        Ok(val) => Box::into_raw(Box::new(val)),
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Synchronous wrapper for `batch_extract_files`.
///
/// Uses the global Tokio runtime for optimal performance.
/// Only available with `tokio-runtime` (WASM has no filesystem).
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::extractor::batch_extract_files_sync;
/// use kreuzberg::core::config::{ExtractionConfig, BatchFileItem, FileExtractionConfig};
///
/// let config = ExtractionConfig::default();
/// let items = vec![
///     BatchFileItem {
///         path: "doc1.pdf".into(),
///         config: Some(FileExtractionConfig { force_ocr: Some(true), ..Default::default() }),
///     },
///     BatchFileItem { path: "doc2.pdf".into(), config: None },
/// ];
/// let results = batch_extract_files_sync(items, &config)?;
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_files_sync(
    items: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if items.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'items'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees items is a valid pointer; string is valid UTF-8 from caller.
    let items_rs_str = match unsafe { CStr::from_ptr(items) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'items'");
            return std::ptr::null_mut();
        }
    };
    let items_rs = match serde_json::from_str::<Vec<kreuzberg::BatchFileItem>>(items_rs_str) {
        Ok(v) => v,
        Err(e) => {
            set_last_error(2, &e.to_string());
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = kreuzberg::batch_extract_files_sync(items_rs, &config_rs);
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by
/// `kreuzberg_batch_extract_files_sync` on this thread. Returns 0 when the primary call returned null
/// or failed before producing a string. Enables safe slice construction in Zig and Java FFM Panama
/// without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_batch_extract_files_sync`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_files_sync_len(
    _items: *const std::ffi::c_char,
    _config: *const kreuzberg::ExtractionConfig,
) -> usize {
    last_return_len()
}

/// Synchronous wrapper for `batch_extract_bytes`.
///
/// Uses the global Tokio runtime for optimal performance.
/// With the `tokio-runtime` feature, this blocks the current thread using the global
/// Tokio runtime. Without it (WASM), this calls a truly synchronous implementation
/// that iterates through items and calls `extract_bytes_sync()`.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::extractor::batch_extract_bytes_sync;
/// use kreuzberg::core::config::{ExtractionConfig, BatchBytesItem, FileExtractionConfig};
///
/// let config = ExtractionConfig::default();
/// let items = vec![
///     BatchBytesItem { content: b"content".to_vec(), mime_type: "text/plain".to_string(), config: None },
///     BatchBytesItem {
///         content: b"other".to_vec(),
///         mime_type: "text/plain".to_string(),
///         config: Some(FileExtractionConfig { force_ocr: Some(true), ..Default::default() }),
///     },
/// ];
/// let results = batch_extract_bytes_sync(items, &config)?;
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_bytes_sync(
    items: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if items.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'items'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees items is a valid pointer; string is valid UTF-8 from caller.
    let items_rs_str = match unsafe { CStr::from_ptr(items) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'items'");
            return std::ptr::null_mut();
        }
    };
    let items_rs = match serde_json::from_str::<Vec<kreuzberg::BatchBytesItem>>(items_rs_str) {
        Ok(v) => v,
        Err(e) => {
            set_last_error(2, &e.to_string());
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = kreuzberg::batch_extract_bytes_sync(items_rs, &config_rs);
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by
/// `kreuzberg_batch_extract_bytes_sync` on this thread. Returns 0 when the primary call returned null
/// or failed before producing a string. Enables safe slice construction in Zig and Java FFM Panama
/// without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_batch_extract_bytes_sync`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_bytes_sync_len(
    _items: *const std::ffi::c_char,
    _config: *const kreuzberg::ExtractionConfig,
) -> usize {
    last_return_len()
}

/// Extract content from multiple files concurrently.
///
/// This function processes multiple files in parallel, automatically managing
/// concurrency to prevent resource exhaustion. The concurrency limit can be
/// configured via `ExtractionConfig::max_concurrent_extractions` or defaults
/// to `(num_cpus * 1.5).ceil()`.
///
/// Each file can optionally specify a [`FileExtractionConfig`] that overrides specific
/// fields from the batch-level `config`. Pass `None` for a file to use the batch defaults.
/// Batch-level settings like `max_concurrent_extractions` and `use_cache` are always
/// taken from the batch-level `config`.
/// \param items Vector of `BatchFileItem` structs, each containing a path and optional per-file
/// configuration overrides.
/// \param config Batch-level extraction configuration (provides defaults and batch settings)
/// \return A vector of `ExtractionResult` in the same order as the input items.
/// \note Individual file errors are captured in the result metadata. System errors
/// (IO, RuntimeError equivalents) will bubble up and fail the entire batch.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// Simple usage with no per-file overrides:
///
/// use kreuzberg::core::extractor::batch_extract_files;
/// use kreuzberg::core::config::{ExtractionConfig, BatchFileItem};
/// use std::path::PathBuf;
///
/// let config = ExtractionConfig::default();
/// let items = vec![
///     BatchFileItem { path: "doc1.pdf".into(), config: None },
///     BatchFileItem { path: "doc2.pdf".into(), config: None },
/// ];
/// let results = batch_extract_files(items, &config).await?;
/// println!("Processed {} files", results.len());
///
/// Per-file configuration overrides:
///
/// use kreuzberg::core::extractor::batch_extract_files;
/// use kreuzberg::core::config::{ExtractionConfig, BatchFileItem, FileExtractionConfig};
/// use std::path::PathBuf;
///
/// let config = ExtractionConfig::default();
/// let items = vec![
///     BatchFileItem {
///         path: "scan.pdf".into(),
///         config: Some(FileExtractionConfig { force_ocr: Some(true), ..Default::default() }),
///     },
///     BatchFileItem { path: "notes.txt".into(), config: None },
/// ];
/// let results = batch_extract_files(items, &config).await?;
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_files(
    items: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if items.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'items'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees items is a valid pointer; string is valid UTF-8 from caller.
    let items_rs_str = match unsafe { CStr::from_ptr(items) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'items'");
            return std::ptr::null_mut();
        }
    };
    let items_rs = match serde_json::from_str::<Vec<kreuzberg::BatchFileItem>>(items_rs_str) {
        Ok(v) => v,
        Err(e) => {
            set_last_error(2, &e.to_string());
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = get_ffi_runtime().block_on(async { kreuzberg::batch_extract_files(items_rs, &config_rs).await });
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_batch_extract_files` on
/// this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_batch_extract_files`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_files_len(
    _items: *const std::ffi::c_char,
    _config: *const kreuzberg::ExtractionConfig,
) -> usize {
    last_return_len()
}

/// Extract content from multiple byte arrays concurrently.
///
/// This function processes multiple byte arrays in parallel, automatically managing
/// concurrency to prevent resource exhaustion. The concurrency limit can be
/// configured via `ExtractionConfig::max_concurrent_extractions` or defaults
/// to `(num_cpus * 1.5).ceil()`.
///
/// Each item can optionally specify a [`FileExtractionConfig`] that overrides specific
/// fields from the batch-level `config`. Pass `None` as the config to use
/// the batch-level defaults for that item.
/// \param items Vector of `BatchBytesItem` structs, each containing content bytes, MIME type, and
/// optional per-item configuration overrides.
/// \param config Batch-level extraction configuration
/// \return A vector of `ExtractionResult` in the same order as the input items.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// Simple usage with no per-item overrides:
///
/// use kreuzberg::core::extractor::batch_extract_bytes;
/// use kreuzberg::core::config::{ExtractionConfig, BatchBytesItem};
///
/// let config = ExtractionConfig::default();
/// let items = vec![
///     BatchBytesItem { content: b"content 1".to_vec(), mime_type: "text/plain".to_string(), config: None },
///     BatchBytesItem { content: b"content 2".to_vec(), mime_type: "text/plain".to_string(), config: None },
/// ];
/// let results = batch_extract_bytes(items, &config).await?;
/// println!("Processed {} items", results.len());
///
/// Per-item configuration overrides:
///
/// use kreuzberg::core::extractor::batch_extract_bytes;
/// use kreuzberg::core::config::{ExtractionConfig, BatchBytesItem, FileExtractionConfig};
///
/// let config = ExtractionConfig::default();
/// let items = vec![
///     BatchBytesItem { content: b"content".to_vec(), mime_type: "text/plain".to_string(), config: None },
///     BatchBytesItem {
///         content: b"<html>test</html>".to_vec(),
///         mime_type: "text/html".to_string(),
///         config: Some(FileExtractionConfig { force_ocr: Some(true), ..Default::default() }),
///     },
/// ];
/// let results = batch_extract_bytes(items, &config).await?;
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_bytes(
    items: *const std::ffi::c_char,
    config: *const kreuzberg::ExtractionConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if items.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'items'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees items is a valid pointer; string is valid UTF-8 from caller.
    let items_rs_str = match unsafe { CStr::from_ptr(items) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'items'");
            return std::ptr::null_mut();
        }
    };
    let items_rs = match serde_json::from_str::<Vec<kreuzberg::BatchBytesItem>>(items_rs_str) {
        Ok(v) => v,
        Err(e) => {
            set_last_error(2, &e.to_string());
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = get_ffi_runtime().block_on(async { kreuzberg::batch_extract_bytes(items_rs, &config_rs).await });
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_batch_extract_bytes` on
/// this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_batch_extract_bytes`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_batch_extract_bytes_len(
    _items: *const std::ffi::c_char,
    _config: *const kreuzberg::ExtractionConfig,
) -> usize {
    last_return_len()
}

/// Detect MIME type from raw file bytes.
///
/// Uses magic byte signatures to detect file type from content.
/// Falls back to `infer` crate for comprehensive detection.
///
/// For ZIP-based files, inspects contents to distinguish Office Open XML
/// formats (DOCX, XLSX, PPTX) from plain ZIP archives.
/// \param content Raw file bytes
/// \return The detected MIME type string.
/// \note Returns `KreuzbergError::UnsupportedFormat` if MIME type cannot be determined.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_mime_type_from_bytes(
    content: *const u8,
    content_len: usize,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if content.is_null() && content_len > 0 {
        set_last_error(1, "Null pointer passed for parameter 'content'");
        return std::ptr::null_mut();
    }
    // SAFETY: when content is null, content_len is 0 (checked above), so we use an empty slice; otherwise data is valid for len elements.
    let content_rs = if content.is_null() {
        Vec::new()
    } else {
        unsafe { std::slice::from_raw_parts(content, content_len) }.to_vec()
    };
    let result = kreuzberg::detect_mime_type_from_bytes(&content_rs);
    match result {
        Ok(val) => {
            let __alef_return = val.to_string();
            match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            }
        }
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by
/// `kreuzberg_detect_mime_type_from_bytes` on this thread. Returns 0 when the primary call returned
/// null or failed before producing a string. Enables safe slice construction in Zig and Java FFM Panama
/// without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_detect_mime_type_from_bytes`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_mime_type_from_bytes_len(_content: *const u8, _content_len: usize) -> usize {
    last_return_len()
}

/// Get file extensions for a given MIME type.
///
/// Returns all known file extensions that map to the specified MIME type.
/// \param mime_type The MIME type to look up
/// \return A vector of file extensions (without leading dot) for the MIME type.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::core::mime::get_extensions_for_mime;
///
/// let extensions = get_extensions_for_mime("application/pdf").unwrap();
/// assert_eq!(extensions, vec!["pdf"]);
///
/// let doc_extensions = get_extensions_for_mime("application/vnd.openxmlformats-officedocument.wordprocessingml.document").unwrap();
/// assert!(doc_extensions.contains(&"docx".to_string()));
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_get_extensions_for_mime(
    mime_type: *const std::ffi::c_char,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if mime_type.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'mime_type'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees mime_type is a valid pointer; string is valid UTF-8 from caller.
    let mime_type_rs = match unsafe { CStr::from_ptr(mime_type) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'mime_type'");
            return std::ptr::null_mut();
        }
    };
    let result = kreuzberg::get_extensions_for_mime(&mime_type_rs);
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_get_extensions_for_mime`
/// on this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_get_extensions_for_mime`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_get_extensions_for_mime_len(_mime_type: *const std::ffi::c_char) -> usize {
    last_return_len()
}

/// List the names of all registered embedding backends.
///
/// Used by `kreuzberg-cli`, the api/mcp endpoints, and generated language
/// bindings.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_embedding_backends() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_embedding_backends();
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_list_embedding_backends`
/// on this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_embedding_backends`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_embedding_backends_len() -> usize {
    last_return_len()
}

/// List names of all registered document extractors.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_document_extractors() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_document_extractors();
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by
/// `kreuzberg_list_document_extractors` on this thread. Returns 0 when the primary call returned null
/// or failed before producing a string. Enables safe slice construction in Zig and Java FFM Panama
/// without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_document_extractors`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_document_extractors_len() -> usize {
    last_return_len()
}

/// List all registered OCR backends.
///
/// Returns the names of all OCR backends currently registered in the global registry.
/// \return A vector of OCR backend names.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::plugins::list_ocr_backends;
///
/// let backends = list_ocr_backends()?;
/// for name in backends {
///     println!("Registered OCR backend: {}", name);
/// }
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_ocr_backends() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_ocr_backends();
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_list_ocr_backends` on
/// this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_ocr_backends`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_ocr_backends_len() -> usize {
    last_return_len()
}

/// List all registered post-processor names.
///
/// Returns a vector of all post-processor names currently registered in the
/// global registry.
/// \return - `Ok(Vec<String>)` - Vector of post-processor names
/// - `Err(...)` if the registry lock is poisoned
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::plugins::list_post_processors;
///
/// let processors = list_post_processors()?;
/// for name in processors {
///     println!("Registered post-processor: {}", name);
/// }
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_post_processors() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_post_processors();
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_list_post_processors` on
/// this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_post_processors`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_post_processors_len() -> usize {
    last_return_len()
}

/// List names of all registered renderers.
/// \note Returns an error if the registry lock is poisoned.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_renderers() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_renderers();
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_list_renderers` on this
/// thread. Returns 0 when the primary call returned null or failed before producing a string. Enables
/// safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_renderers`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_renderers_len() -> usize {
    last_return_len()
}

/// List names of all registered validators.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_validators() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_validators();
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_list_validators` on this
/// thread. Returns 0 when the primary call returned null or failed before producing a string. Enables
/// safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_validators`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_validators_len() -> usize {
    last_return_len()
}

/// Compare two extraction results and return a structured diff.
///
/// The comparison is purely structural â no I/O, no side effects. All fields
/// of [`ExtractionDiff`] are populated according to the provided [`DiffOptions`].
/// \param a â the "before" extraction result
/// \param b â the "after" extraction result
/// \param opts â controls which sections are compared and optional truncation
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::{ExtractionResult, diff::{compare, DiffOptions}};
///
/// let mut a = ExtractionResult::default();
/// let mut b = ExtractionResult::default();
/// a.content = "Hello world".to_string();
/// b.content = "Hello Rust".to_string();
///
/// let diff = compare(&a, &b, &DiffOptions::default());
/// assert_eq!(diff.content_diff.len(), 1);
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_compare(
    a: *const kreuzberg::ExtractionResult,
    b: *const kreuzberg::ExtractionResult,
    opts: *const kreuzberg::DiffOptions,
) -> *mut kreuzberg::ExtractionDiff {
    clear_last_error();
    if a.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'a'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees a is a valid pointer.
    let a_rs = unsafe { &*a };
    if b.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'b'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees b is a valid pointer.
    let b_rs = unsafe { &*b };
    if opts.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'opts'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees opts is a valid pointer.
    let opts_rs = unsafe { &*opts };
    let result = kreuzberg::compare(&a_rs, &b_rs, &opts_rs);
    Box::into_raw(Box::new(result))
}

/// Generate embeddings asynchronously for a list of text strings.
///
/// This is the async counterpart to [`embed_texts`]. It offloads the blocking
/// ONNX inference work to a dedicated blocking thread pool via Tokio's
/// `spawn_blocking`, keeping the async executor free.
///
/// Returns one embedding vector per input text in the same order.
/// \param texts Vec of strings to embed (owned, sent to blocking thread)
/// \param config Embedding configuration specifying model, batch size, and normalization
/// \note - `KreuzbergError::MissingDependency` if ONNX Runtime is not installed
/// - `KreuzbergError::Embedding` if the preset name is unknown, model download fails,
///   or the blocking inference task panics
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
/// \code
/// use kreuzberg::{embed_texts_async, EmbeddingConfig};
///
/// let embeddings = embed_texts_async(
///     vec!["Hello!".to_string()],
///     &EmbeddingConfig::default(),
/// ).await?;
/// \endcode
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embed_texts_async(
    texts: *const std::ffi::c_char,
    config: *const kreuzberg::EmbeddingConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if texts.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'texts'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees texts is a valid pointer; string is valid UTF-8 from caller.
    let texts_rs_str = match unsafe { CStr::from_ptr(texts) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'texts'");
            return std::ptr::null_mut();
        }
    };
    let texts_rs = match serde_json::from_str::<Vec<String>>(texts_rs_str) {
        Ok(v) => v,
        Err(e) => {
            set_last_error(2, &e.to_string());
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = get_ffi_runtime().block_on(async { kreuzberg::embed_texts_async(texts_rs, &config_rs).await });
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_embed_texts_async` on
/// this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_embed_texts_async`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embed_texts_async_len(
    _texts: *const std::ffi::c_char,
    _config: *const kreuzberg::EmbeddingConfig,
) -> usize {
    last_return_len()
}

/// Render a single PDF page to PNG bytes.
///
/// Returns raw PNG-encoded bytes for the specified page at the given DPI.
/// Uses pdf_oxide with tiny-skia for pure-Rust rendering.
/// \param pdf_bytes Raw PDF file bytes
/// \param page_index Zero-based page index
/// \param dpi Resolution in dots per inch (default: 150)
/// \param password Optional password for encrypted PDFs
/// \note Returns `KreuzbergError::Parsing` if the PDF cannot be opened, authenticated,
/// or rendered, or if `page_index` is out of range.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[allow(clippy::too_many_arguments)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_render_pdf_page_to_png(
    pdf_bytes: *const u8,
    pdf_bytes_len: usize,
    page_index: usize,
    dpi: i32,
    password: *const std::ffi::c_char,
    out_ptr: *mut *mut u8,
    out_len: *mut usize,
    out_cap: *mut usize,
) -> i32 {
    clear_last_error();
    if out_ptr.is_null() || out_len.is_null() || out_cap.is_null() {
        set_last_error(1, "Null out-param pointer for byte buffer return");
        return -1;
    }
    if pdf_bytes.is_null() && pdf_bytes_len > 0 {
        set_last_error(1, "Null pointer passed for parameter 'pdf_bytes'");
        return -1;
    }
    // SAFETY: when pdf_bytes is null, pdf_bytes_len is 0 (checked above), so we use an empty slice; otherwise data is valid for len elements.
    let pdf_bytes_rs = if pdf_bytes.is_null() {
        Vec::new()
    } else {
        unsafe { std::slice::from_raw_parts(pdf_bytes, pdf_bytes_len) }.to_vec()
    };
    let page_index_rs = page_index;
    // Optional numeric primitive: max value of type = None
    let dpi_rs = if dpi == i32::MAX { None } else { Some(dpi) };
    let password_rs = if password.is_null() {
        None
    } else {
        // SAFETY: null check above guarantees password is a valid pointer; string is valid UTF-8 from caller.
        match unsafe { CStr::from_ptr(password) }.to_str() {
            Ok(s) => Some(s.to_string()),
            Err(_) => {
                set_last_error(1, "Invalid UTF-8 in parameter 'password'");
                return -1;
            }
        }
    };
    let result = kreuzberg::render_pdf_page_to_png(&pdf_bytes_rs, page_index_rs, dpi_rs, password_rs.as_deref());
    match result {
        Ok(val) => {
            // Convert via Vec<u8> so the path works for both Vec<u8> and bytes::Bytes
            // (and any other type that implements Into<Vec<u8>>).
            let (ptr, len, cap) = Vec::<u8>::from(val).into_raw_parts();
            // SAFETY: out_ptr/out_len/out_cap were null-checked above.
            unsafe {
                *out_ptr = ptr;
                *out_len = len;
                *out_cap = cap;
            }
            0
        }
        Err(e) => {
            set_last_error(2, &e.to_string());
            // SAFETY: out_ptr/out_len/out_cap were null-checked above.
            unsafe {
                *out_ptr = std::ptr::null_mut();
                *out_len = 0;
                *out_cap = 0;
            }
            -1
        }
    }
}

/// Detect the MIME type of a file at the given path.
///
/// Uses the file extension and optionally the file content to determine the MIME type.
/// Set `check_exists` to `true` to verify the file exists before detection.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_mime_type(
    path: *const std::ffi::c_char,
    check_exists: i32,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if path.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'path'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees path is a valid pointer; string is valid UTF-8 from caller.
    let path_rs = match unsafe { CStr::from_ptr(path) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'path'");
            return std::ptr::null_mut();
        }
    };
    let check_exists_rs = check_exists != 0;
    let result = kreuzberg::detect_mime_type(path_rs, check_exists_rs);
    match result {
        Ok(val) => {
            let __alef_return = val.to_string();
            match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            }
        }
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_detect_mime_type` on
/// this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_detect_mime_type`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_detect_mime_type_len(_path: *const std::ffi::c_char, _check_exists: i32) -> usize {
    last_return_len()
}

/// Embed a list of texts using the configured embedding model.
///
/// Returns a 2D vector where each inner vector is the embedding for the corresponding text.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embed_texts(
    texts: *const std::ffi::c_char,
    config: *const kreuzberg::EmbeddingConfig,
) -> *mut std::ffi::c_char {
    clear_last_error();
    if texts.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'texts'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees texts is a valid pointer; string is valid UTF-8 from caller.
    let texts_rs_str = match unsafe { CStr::from_ptr(texts) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'texts'");
            return std::ptr::null_mut();
        }
    };
    let texts_rs = match serde_json::from_str::<Vec<String>>(texts_rs_str) {
        Ok(v) => v,
        Err(e) => {
            set_last_error(2, &e.to_string());
            return std::ptr::null_mut();
        }
    };
    if config.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'config'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees config is a valid pointer.
    let config_rs = unsafe { &*config };
    let result = kreuzberg::embed_texts(texts_rs, &config_rs);
    match result {
        Ok(val) => match serde_json::to_string(&val) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        },
        Err(e) => {
            set_last_error(2, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_embed_texts` on this
/// thread. Returns 0 when the primary call returned null or failed before producing a string. Enables
/// safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_embed_texts`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_embed_texts_len(
    _texts: *const std::ffi::c_char,
    _config: *const kreuzberg::EmbeddingConfig,
) -> usize {
    last_return_len()
}

/// Get an embedding preset by name.
///
/// Returns `None` if no preset with the given name exists. Returns an owned
/// clone so the value is safe to pass across FFI boundaries.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_get_embedding_preset(
    name: *const std::ffi::c_char,
) -> *mut kreuzberg::EmbeddingPreset {
    clear_last_error();
    if name.is_null() {
        set_last_error(1, "Null pointer passed for parameter 'name'");
        return std::ptr::null_mut();
    }
    // SAFETY: null check above guarantees name is a valid pointer; string is valid UTF-8 from caller.
    let name_rs = match unsafe { CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => {
            set_last_error(1, "Invalid UTF-8 in parameter 'name'");
            return std::ptr::null_mut();
        }
    };
    let result = kreuzberg::get_embedding_preset(&name_rs);
    match result {
        Some(val) => Box::into_raw(Box::new(val)),
        None => std::ptr::null_mut(),
    }
}

/// List the names of all available embedding presets.
///
/// Returns owned `String`s so the values are safe to pass across FFI boundaries.
/// \note SAFETY: Caller must ensure all pointer arguments are valid or null. Returned pointers must be
/// freed with the appropriate free function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_embedding_presets() -> *mut std::ffi::c_char {
    clear_last_error();
    let result = kreuzberg::list_embedding_presets();
    {
        match serde_json::to_string(&result) {
            Ok(__alef_return) => match CString::new(__alef_return) {
                Ok(cs) => {
                    set_last_return_len(cs.as_bytes().len());
                    cs.into_raw()
                }
                Err(_) => {
                    set_last_return_len(0);
                    std::ptr::null_mut()
                }
            },
            Err(_) => {
                set_last_return_len(0);
                std::ptr::null_mut()
            }
        }
    }
}

/// Return the byte length of the C string most recently returned by `kreuzberg_list_embedding_presets`
/// on this thread. Returns 0 when the primary call returned null or failed before producing a string.
/// Enables safe slice construction in Zig and Java FFM Panama without a NUL-scan.
/// \note SAFETY: Pointer arguments are ignored and are present only to keep the companion ABI aligned
/// with `kreuzberg_list_embedding_presets`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_list_embedding_presets_len() -> usize {
    last_return_len()
}

/// Write an error message string into an FFI out-error pointer.
///
/// # Safety
///
/// `out_error` must be null or a valid writable `*mut *mut c_char` pointer.
unsafe fn ffi_set_out_error(out_error: *mut *mut std::ffi::c_char, msg: &str) {
    if !out_error.is_null() {
        if let Ok(cs) = std::ffi::CString::new(msg) {
            // SAFETY: out_error is non-null; caller must free this string.
            unsafe {
                *out_error = cs.into_raw();
            }
        }
    }
}

/// VTable for C plugin bridges implementing the `OcrBackend` trait.
///
/// # Safety
///
/// All function pointers must be valid for the lifetime of any bridge created from
/// this vtable.  `free_user_data`, when non-null, is called once with `user_data`
/// when the bridge is dropped.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct KreuzbergOcrBackendVTable {
    /// Return a null-terminated UTF-8 name string into `out_name`; return 0 on success.
    pub name_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_name: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Return a null-terminated UTF-8 version string into `out_version`; return 0 on success.
    pub version_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_version: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Initialise the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub initialize_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Shut down the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub shutdown_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Process an image and extract text via OCR.
    ///
    /// # Arguments
    ///
    /// * `image_bytes` - Raw image data (JPEG, PNG, TIFF, etc.)
    /// * `config` - OCR configuration (language, PSM mode, etc.)
    ///
    /// # Returns
    ///
    /// An `ExtractionResult` containing the extracted text and metadata.
    ///
    /// # Errors
    ///
    /// - `KreuzbergError::Ocr` - OCR processing failed
    /// - `KreuzbergError::Validation` - Invalid image format or configuration
    /// - `KreuzbergError::Io` - I/O errors (these always bubble up)
    ///
    /// # Reading `backend_options`
    ///
    /// Backends that support runtime tuning can read `config.backend_options` and
    /// deserialize only the keys they care about. Unknown keys are silently ignored,
    /// so multiple backends can coexist in a pipeline without key conflicts.
    ///
    /// ```rust
    /// async fn process_image(&self, image_bytes: &[u8], config: &OcrConfig) -> Result<ExtractionResult> {
    /// // Read backend-specific options; unknown keys are silently ignored.
    /// let fast_mode = config.backend_options
    /// .as_ref()
    /// .and_then(|v| v.get("mode"))
    /// .and_then(|v| v.as_str())
    /// .map(|s| s == "fast")
    /// .unwrap_or(false);
    ///
    /// if image_bytes.is_empty() {
    /// return Err(kreuzberg::KreuzbergError::Validation {
    /// message: "Empty image data".to_string(),
    /// source: None,
    /// });
    /// }
    ///
    /// let text = if fast_mode {
    /// "Fast OCR result".to_string()
    /// } else {
    /// format!("Extracted text in language: {}", config.language)
    /// };
    ///
    /// Ok(ExtractionResult {
    /// content: text,
    /// mime_type: Cow::Borrowed("text/plain"),
    /// ..Default::default()
    /// })
    /// }
    /// ```
    pub process_image: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            image_bytes: *const u8,
            image_bytes_len: usize,
            config: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Process a file and extract text via OCR.
    ///
    /// Default implementation reads the file and calls `process_image`.
    /// Override for custom file handling or optimizations.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the image file
    /// * `config` - OCR configuration
    ///
    /// # Errors
    ///
    /// Same as `process_image`, plus file I/O errors.
    pub process_image_file: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            path: *const std::ffi::c_char,
            config: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Check if this backend supports a given language code.
    ///
    /// # Arguments
    ///
    /// * `lang` - ISO 639-2/3 language code (e.g., "eng", "deu", "fra")
    ///
    /// # Returns
    ///
    /// `true` if the language is supported, `false` otherwise.
    ///
    /// # Example
    ///
    /// ```rust
    /// fn supports_language(&self, lang: &str) -> bool {
    /// self.languages.contains(&lang.to_string())
    /// }
    /// ```
    pub supports_language:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, lang: *const std::ffi::c_char) -> i32>,
    /// Get the backend type identifier.
    ///
    /// # Returns
    ///
    /// The backend type enum value.
    ///
    /// # Example
    ///
    /// ```rust
    /// fn backend_type(&self) -> OcrBackendType {
    /// OcrBackendType::Tesseract
    /// }
    /// ```
    pub backend_type: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional: Get a list of all supported languages.
    ///
    /// Defaults to empty list. Override to provide comprehensive language support info.
    pub supported_languages: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional: Check if the backend supports table detection.
    ///
    /// Defaults to `false`. Override if your backend can detect and extract tables.
    pub supports_table_detection: Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void) -> i32>,
    /// Check if the backend supports direct document-level processing (e.g. for PDFs).
    ///
    /// Defaults to `false`. Override if the backend has optimized document processing.
    pub supports_document_processing: Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void) -> i32>,
    /// Process a document file directly via OCR.
    ///
    /// Only called if `supports_document_processing` returns `true`.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the document file (e.g. .pdf)
    /// * `config` - OCR configuration
    pub process_document: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            _path: *const std::ffi::c_char,
            _config: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional string destructor: called for strings returned by vtable callbacks.
    pub free_string: Option<unsafe extern "C" fn(*mut std::ffi::c_char)>,
    /// Optional destructor: called once with `user_data` when the bridge is dropped.
    pub free_user_data: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
}

// SAFETY: all fields are function pointers and free_user_data, which are Send + Sync.
unsafe impl Send for KreuzbergOcrBackendVTable {}
unsafe impl Sync for KreuzbergOcrBackendVTable {}

/// Rust-side bridge that holds a C vtable pointer and opaque `user_data`.
///
/// Implements `OcrBackend` by forwarding calls through the vtable.
pub struct KreuzbergOcrBackendBridge {
    vtable: KreuzbergOcrBackendVTable,
    user_data: *const std::ffi::c_void,
    cached_name: String,
    cached_version: String,
}

impl std::fmt::Debug for KreuzbergOcrBackendBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KreuzbergOcrBackendBridge")
            .field("cached_name", &self.cached_name)
            .field("cached_version", &self.cached_version)
            .finish_non_exhaustive()
    }
}

// SAFETY: The caller is responsible for ensuring `user_data` is safe to send across
// thread boundaries. This is documented in `KreuzbergOcrBackendVTable` and the registration function.
unsafe impl Send for KreuzbergOcrBackendBridge {}
unsafe impl Sync for KreuzbergOcrBackendBridge {}

impl Drop for KreuzbergOcrBackendBridge {
    fn drop(&mut self) {
        if let Some(free_fn) = self.vtable.free_user_data {
            // SAFETY: free_fn is a valid function pointer; user_data is the pointer
            // originally provided at registration. Called exactly once here.
            unsafe { free_fn(self.user_data as *mut std::ffi::c_void) }
        }
    }
}

impl KreuzbergOcrBackendBridge {
    /// Create a new bridge from a vtable and opaque user_data pointer.
    ///
    /// # Safety
    ///
    /// `vtable` must remain valid for the lifetime of the returned bridge.
    /// `user_data` must be valid for any thread that calls methods on this bridge.
    /// All required fn pointers in `vtable` must be non-null.
    pub unsafe fn new(name: String, vtable: KreuzbergOcrBackendVTable, user_data: *const std::ffi::c_void) -> Self {
        Self {
            vtable,
            user_data,
            cached_name: name,
            cached_version: String::new(),
        }
    }
}

impl kreuzberg::plugins::Plugin for KreuzbergOcrBackendBridge {
    fn name(&self) -> &str {
        &self.cached_name
    }
    fn version(&self) -> String {
        let Some(fp) = self.vtable.version_fn else {
            return String::new();
        };
        let mut _out: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out, &mut _out_error) };
        if rc != 0 {
            if !_out_error.is_null() {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let _ = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
            }
            return self.cached_version.clone();
        }
        if _out.is_null() {
            return self.cached_version.clone();
        }
        // SAFETY: _out is a callee-allocated NUL-terminated string.
        let version = unsafe { std::ffi::CStr::from_ptr(_out) }.to_string_lossy().into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out) };
        }
        version
    }
    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.initialize_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("initialize returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.shutdown_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("shutdown returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::OcrBackend for KreuzbergOcrBackendBridge {
    async fn process_image(
        &self,
        image_bytes: &[u8],
        config: &kreuzberg::OcrConfig,
    ) -> std::result::Result<kreuzberg::ExtractionResult, kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let image_bytes = image_bytes.to_vec();
        let config = config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.process_image else {
                return Err(Box::from("vtable.process_image is null — bridge not initialised"));
            };
            let _config_json = serde_json::to_string(&config).unwrap_or_default();
            let _config_cs = match std::ffi::CString::new(_config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param config"));
                }
            };
            let config_ptr = _config_cs.as_ptr();
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe {
                fp(
                    user_data,
                    image_bytes.as_ptr(),
                    image_bytes.len(),
                    config_ptr,
                    &mut _out_result,
                    &mut _out_error,
                )
            };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.process_image returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            if _out_result.is_null() {
                return Err(Box::from("vtable.process_image returned null out_result"));
            }
            // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
            let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                .to_string_lossy()
                .into_owned();
            if let Some(free_fn) = vtable.free_string {
                // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                unsafe { free_fn(_out_result) };
            }
            serde_json::from_str::<kreuzberg::ExtractionResult>(&json)
                .map_err(|e| Box::from(e.to_string()) as Box<dyn std::error::Error + Send + Sync>)
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in process_image: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }

    async fn process_image_file(
        &self,
        path: &std::path::Path,
        config: &kreuzberg::OcrConfig,
    ) -> std::result::Result<kreuzberg::ExtractionResult, kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let path = path.to_path_buf();
        let config = config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.process_image_file else {
                return Err(Box::from("vtable.process_image_file is null — bridge not initialised"));
            };
            let _path_cs = match std::ffi::CString::new(path.to_string_lossy().as_ref()) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in param path"));
                }
            };
            let path_ptr = _path_cs.as_ptr();
            let _config_json = serde_json::to_string(&config).unwrap_or_default();
            let _config_cs = match std::ffi::CString::new(_config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param config"));
                }
            };
            let config_ptr = _config_cs.as_ptr();
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe { fp(user_data, path_ptr, config_ptr, &mut _out_result, &mut _out_error) };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.process_image_file returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            if _out_result.is_null() {
                return Err(Box::from("vtable.process_image_file returned null out_result"));
            }
            // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
            let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                .to_string_lossy()
                .into_owned();
            if let Some(free_fn) = vtable.free_string {
                // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                unsafe { free_fn(_out_result) };
            }
            serde_json::from_str::<kreuzberg::ExtractionResult>(&json)
                .map_err(|e| Box::from(e.to_string()) as Box<dyn std::error::Error + Send + Sync>)
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| {
                kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in process_image_file: {}", e))
            })?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }

    fn supports_language(&self, lang: &str) -> bool {
        let Some(fp) = self.vtable.supports_language else {
            return false;
        };
        let _lang_cs = match std::ffi::CString::new(lang) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let lang_ptr = _lang_cs.as_ptr();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, lang_ptr) };
        _rc != 0
    }

    fn backend_type(&self) -> kreuzberg::OcrBackendType {
        let Some(fp) = self.vtable.backend_type else {
            return Default::default();
        };
        let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, &mut _out_result, &mut _out_error) };
        if _out_result.is_null() {
            return Default::default();
        }
        // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
        let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
            .to_string_lossy()
            .into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out_result) };
        }
        serde_json::from_str::<kreuzberg::OcrBackendType>(&json).unwrap_or_default()
    }

    fn supported_languages(&self) -> Vec<String> {
        let Some(fp) = self.vtable.supported_languages else {
            return Vec::new();
        };
        let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, &mut _out_result, &mut _out_error) };
        if _out_result.is_null() {
            return Default::default();
        }
        // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
        let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
            .to_string_lossy()
            .into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out_result) };
        }
        serde_json::from_str::<Vec<String>>(&json).unwrap_or_default()
    }

    fn supports_table_detection(&self) -> bool {
        let Some(fp) = self.vtable.supports_table_detection else {
            return false;
        };
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data) };
        _rc != 0
    }

    fn supports_document_processing(&self) -> bool {
        let Some(fp) = self.vtable.supports_document_processing else {
            return false;
        };
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data) };
        _rc != 0
    }

    async fn process_document(
        &self,
        _path: &std::path::Path,
        _config: &kreuzberg::OcrConfig,
    ) -> std::result::Result<kreuzberg::ExtractionResult, kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let _path = _path.to_path_buf();
        let _config = _config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.process_document else {
                return Err(Box::from("vtable.process_document is null — bridge not initialised"));
            };
            let __path_cs = match std::ffi::CString::new(_path.to_string_lossy().as_ref()) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in param _path"));
                }
            };
            let _path_ptr = __path_cs.as_ptr();
            let __config_json = serde_json::to_string(&_config).unwrap_or_default();
            let __config_cs = match std::ffi::CString::new(__config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param _config"));
                }
            };
            let _config_ptr = __config_cs.as_ptr();
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe { fp(user_data, _path_ptr, _config_ptr, &mut _out_result, &mut _out_error) };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.process_document returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            if _out_result.is_null() {
                return Err(Box::from("vtable.process_document returned null out_result"));
            }
            // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
            let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                .to_string_lossy()
                .into_owned();
            if let Some(free_fn) = vtable.free_string {
                // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                unsafe { free_fn(_out_result) };
            }
            serde_json::from_str::<kreuzberg::ExtractionResult>(&json)
                .map_err(|e| Box::from(e.to_string()) as Box<dyn std::error::Error + Send + Sync>)
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in process_document: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }
}

/// Register a C plugin implementing `OcrBackend` via a vtable.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `vtable`: vtable with function pointers implementing the trait.
/// - `user_data`: opaque pointer forwarded to every vtable function.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// All function pointers in `vtable` must remain valid until the plugin is
/// unregistered. `user_data` must be safe to use from any thread that calls
/// into the plugin.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_register_ocr_backend(
    name: *const std::ffi::c_char,
    vtable: KreuzbergOcrBackendVTable,
    user_data: *const std::ffi::c_void,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    if vtable.free_string.is_none() {
        ffi_set_out_error(out_error, "vtable.free_string must not be null");
        return 1;
    }
    if vtable.process_image.is_none() {
        ffi_set_out_error(out_error, "vtable.process_image must not be null");
        return 1;
    }
    if vtable.supports_language.is_none() {
        ffi_set_out_error(out_error, "vtable.supports_language must not be null");
        return 1;
    }
    if vtable.backend_type.is_none() {
        ffi_set_out_error(out_error, "vtable.backend_type must not be null");
        return 1;
    }

    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_owned(),
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    // SAFETY: vtable and user_data validity is the caller's responsibility.
    let bridge = unsafe { KreuzbergOcrBackendBridge::new(plugin_name, vtable, user_data) };
    let arc: Arc<dyn kreuzberg::OcrBackend> = Arc::new(bridge);

    let registry = kreuzberg::plugins::registry::get_ocr_backend_registry();
    let mut registry = registry.write();

    if let Err(e) = registry.register(arc) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Unregister a previously registered C plugin by name.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `name` must point to a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_unregister_ocr_backend(
    name: *const std::ffi::c_char,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    let registry = kreuzberg::plugins::registry::get_ocr_backend_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.remove(plugin_name) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Remove all registered C plugins of this trait.
///
/// # Parameters
///
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `out_error` may be null. When non-null, the caller owns the resulting string
/// and must free it with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_clear_ocr_backend(out_error: *mut *mut std::ffi::c_char) -> i32 {
    let registry = kreuzberg::plugins::registry::get_ocr_backend_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.clear() {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// VTable for C plugin bridges implementing the `PostProcessor` trait.
///
/// # Safety
///
/// All function pointers must be valid for the lifetime of any bridge created from
/// this vtable.  `free_user_data`, when non-null, is called once with `user_data`
/// when the bridge is dropped.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct KreuzbergPostProcessorVTable {
    /// Return a null-terminated UTF-8 name string into `out_name`; return 0 on success.
    pub name_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_name: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Return a null-terminated UTF-8 version string into `out_version`; return 0 on success.
    pub version_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_version: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Initialise the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub initialize_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Shut down the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub shutdown_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Process an extraction result.
    ///
    /// Transform or enrich the extraction result. Can modify:
    /// - `content` - The extracted text
    /// - `metadata` - Add or update metadata fields
    /// - `tables` - Modify or enhance table data
    ///
    /// # Arguments
    ///
    /// * `result` - Mutable reference to the extraction result to process
    /// * `config` - Extraction configuration
    ///
    /// # Returns
    ///
    /// `Ok(())` if processing succeeded, `Err(...)` for fatal failures.
    ///
    /// # Errors
    ///
    /// Return errors for fatal processing failures. Non-fatal errors should be
    /// captured in metadata directly on the result.
    ///
    /// # Performance
    ///
    /// This signature avoids unnecessary cloning of large extraction results by
    /// taking a mutable reference instead of ownership. Processors modify the
    /// result in place.
    ///
    /// # Example - Language Detection
    ///
    /// ```rust
    /// async fn process(&self, result: &mut ExtractionResult, config: &ExtractionConfig)
    /// -> Result<()> {
    /// // Detect language (simplified - use real detection library in practice)
    /// let language = "en"; // Placeholder detection
    ///
    /// // Add to metadata
    /// result.metadata.additional.insert("detected_language".to_string().into(), serde_json::json!(language));
    ///
    /// Ok(())
    /// }
    /// ```
    ///
    /// # Example - Text Cleaning
    ///
    /// ```rust
    /// async fn process(&self, result: &mut ExtractionResult, config: &ExtractionConfig)
    /// -> Result<()> {
    /// // Remove excessive whitespace
    /// result.content = result
    /// .content
    /// .split_whitespace()
    /// .collect::<Vec<_>>()
    /// .join(" ");
    ///
    /// Ok(())
    /// }
    /// ```
    pub process: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            result: *const std::ffi::c_char,
            config: *const std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Get the processing stage for this post-processor.
    ///
    /// Determines when this processor runs in the pipeline.
    ///
    /// # Returns
    ///
    /// The `ProcessingStage` (Early, Middle, or Late).
    ///
    /// # Example
    ///
    /// ```rust
    /// fn processing_stage(&self) -> ProcessingStage {
    /// ProcessingStage::Early  // Run before other processors
    /// }
    /// ```
    pub processing_stage: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional: Check if this processor should run for a given result.
    ///
    /// Allows conditional processing based on MIME type, metadata, or content.
    /// Defaults to `true` (always run).
    ///
    /// # Arguments
    ///
    /// * `result` - The extraction result to check
    /// * `config` - Extraction configuration
    ///
    /// # Returns
    ///
    /// `true` if the processor should run, `false` to skip.
    ///
    /// # Example
    ///
    /// ```rust
    /// Only process PDF documents
    /// fn should_process(&self, result: &ExtractionResult, config: &ExtractionConfig) -> bool {
    /// result.mime_type == "application/pdf"
    /// }
    /// ```
    pub should_process: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            _result: *const std::ffi::c_char,
            _config: *const std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional: Estimate processing time in milliseconds.
    ///
    /// Used for logging and debugging. Defaults to 0 (unknown).
    ///
    /// # Arguments
    ///
    /// * `result` - The extraction result to estimate for
    ///
    /// # Returns
    ///
    /// Estimated processing time in milliseconds.
    pub estimated_duration_ms:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, _result: *const std::ffi::c_char) -> u64>,
    /// Execution priority within the processing stage.
    ///
    /// Higher values run first within the same `ProcessingStage`. Defaults to 50.
    /// Use 0-49 for fallback processors, 50 for normal processors, and 51-255
    /// for high-priority processors that should run early in their stage.
    pub priority: Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void) -> i32>,
    /// Optional string destructor: called for strings returned by vtable callbacks.
    pub free_string: Option<unsafe extern "C" fn(*mut std::ffi::c_char)>,
    /// Optional destructor: called once with `user_data` when the bridge is dropped.
    pub free_user_data: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
}

// SAFETY: all fields are function pointers and free_user_data, which are Send + Sync.
unsafe impl Send for KreuzbergPostProcessorVTable {}
unsafe impl Sync for KreuzbergPostProcessorVTable {}

/// Rust-side bridge that holds a C vtable pointer and opaque `user_data`.
///
/// Implements `PostProcessor` by forwarding calls through the vtable.
pub struct KreuzbergPostProcessorBridge {
    vtable: KreuzbergPostProcessorVTable,
    user_data: *const std::ffi::c_void,
    cached_name: String,
    cached_version: String,
}

impl std::fmt::Debug for KreuzbergPostProcessorBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KreuzbergPostProcessorBridge")
            .field("cached_name", &self.cached_name)
            .field("cached_version", &self.cached_version)
            .finish_non_exhaustive()
    }
}

// SAFETY: The caller is responsible for ensuring `user_data` is safe to send across
// thread boundaries. This is documented in `KreuzbergPostProcessorVTable` and the registration function.
unsafe impl Send for KreuzbergPostProcessorBridge {}
unsafe impl Sync for KreuzbergPostProcessorBridge {}

impl Drop for KreuzbergPostProcessorBridge {
    fn drop(&mut self) {
        if let Some(free_fn) = self.vtable.free_user_data {
            // SAFETY: free_fn is a valid function pointer; user_data is the pointer
            // originally provided at registration. Called exactly once here.
            unsafe { free_fn(self.user_data as *mut std::ffi::c_void) }
        }
    }
}

impl KreuzbergPostProcessorBridge {
    /// Create a new bridge from a vtable and opaque user_data pointer.
    ///
    /// # Safety
    ///
    /// `vtable` must remain valid for the lifetime of the returned bridge.
    /// `user_data` must be valid for any thread that calls methods on this bridge.
    /// All required fn pointers in `vtable` must be non-null.
    pub unsafe fn new(name: String, vtable: KreuzbergPostProcessorVTable, user_data: *const std::ffi::c_void) -> Self {
        Self {
            vtable,
            user_data,
            cached_name: name,
            cached_version: String::new(),
        }
    }
}

impl kreuzberg::plugins::Plugin for KreuzbergPostProcessorBridge {
    fn name(&self) -> &str {
        &self.cached_name
    }
    fn version(&self) -> String {
        let Some(fp) = self.vtable.version_fn else {
            return String::new();
        };
        let mut _out: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out, &mut _out_error) };
        if rc != 0 {
            if !_out_error.is_null() {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let _ = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
            }
            return self.cached_version.clone();
        }
        if _out.is_null() {
            return self.cached_version.clone();
        }
        // SAFETY: _out is a callee-allocated NUL-terminated string.
        let version = unsafe { std::ffi::CStr::from_ptr(_out) }.to_string_lossy().into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out) };
        }
        version
    }
    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.initialize_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("initialize returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.shutdown_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("shutdown returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::PostProcessor for KreuzbergPostProcessorBridge {
    async fn process(
        &self,
        result: &mut kreuzberg::ExtractionResult,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let result = result.clone();
        let config = config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.process else {
                return Err(Box::from("vtable.process is null — bridge not initialised"));
            };
            let _result_json = serde_json::to_string(&result).unwrap_or_default();
            let _result_cs = match std::ffi::CString::new(_result_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param result"));
                }
            };
            let result_ptr = _result_cs.as_ptr();
            let _config_json = serde_json::to_string(&config).unwrap_or_default();
            let _config_cs = match std::ffi::CString::new(_config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param config"));
                }
            };
            let config_ptr = _config_cs.as_ptr();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe { fp(user_data, result_ptr, config_ptr, &mut _out_error) };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.process returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            Ok(())
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in process: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }

    fn processing_stage(&self) -> kreuzberg::ProcessingStage {
        let Some(fp) = self.vtable.processing_stage else {
            return Default::default();
        };
        let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, &mut _out_result, &mut _out_error) };
        if _out_result.is_null() {
            return Default::default();
        }
        // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
        let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
            .to_string_lossy()
            .into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out_result) };
        }
        serde_json::from_str::<kreuzberg::ProcessingStage>(&json).unwrap_or_default()
    }

    fn should_process(&self, _result: &kreuzberg::ExtractionResult, _config: &kreuzberg::ExtractionConfig) -> bool {
        let Some(fp) = self.vtable.should_process else {
            return false;
        };
        let __result_json = serde_json::to_string(&_result).unwrap_or_default();
        let __result_cs = match std::ffi::CString::new(__result_json) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let _result_ptr = __result_cs.as_ptr();
        let __config_json = serde_json::to_string(&_config).unwrap_or_default();
        let __config_cs = match std::ffi::CString::new(__config_json) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let _config_ptr = __config_cs.as_ptr();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, _result_ptr, _config_ptr) };
        _rc != 0
    }

    fn estimated_duration_ms(&self, _result: &kreuzberg::ExtractionResult) -> u64 {
        let Some(fp) = self.vtable.estimated_duration_ms else {
            return 0;
        };
        let __result_json = serde_json::to_string(&_result).unwrap_or_default();
        let __result_cs = match std::ffi::CString::new(__result_json) {
            Ok(s) => s,
            Err(_) => {
                return 0;
            }
        };
        let _result_ptr = __result_cs.as_ptr();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        unsafe { fp(self.user_data, _result_ptr) }
    }

    fn priority(&self) -> i32 {
        let Some(fp) = self.vtable.priority else {
            return 0;
        };
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        unsafe { fp(self.user_data) }
    }
}

/// Register a C plugin implementing `PostProcessor` via a vtable.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `vtable`: vtable with function pointers implementing the trait.
/// - `user_data`: opaque pointer forwarded to every vtable function.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// All function pointers in `vtable` must remain valid until the plugin is
/// unregistered. `user_data` must be safe to use from any thread that calls
/// into the plugin.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_register_post_processor(
    name: *const std::ffi::c_char,
    vtable: KreuzbergPostProcessorVTable,
    user_data: *const std::ffi::c_void,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    if vtable.free_string.is_none() {
        ffi_set_out_error(out_error, "vtable.free_string must not be null");
        return 1;
    }
    if vtable.process.is_none() {
        ffi_set_out_error(out_error, "vtable.process must not be null");
        return 1;
    }
    if vtable.processing_stage.is_none() {
        ffi_set_out_error(out_error, "vtable.processing_stage must not be null");
        return 1;
    }

    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_owned(),
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    // SAFETY: vtable and user_data validity is the caller's responsibility.
    let bridge = unsafe { KreuzbergPostProcessorBridge::new(plugin_name, vtable, user_data) };
    let arc: Arc<dyn kreuzberg::PostProcessor> = Arc::new(bridge);

    let registry = kreuzberg::plugins::registry::get_post_processor_registry();
    let mut registry = registry.write();

    if let Err(e) = registry.register(arc) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Unregister a previously registered C plugin by name.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `name` must point to a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_unregister_post_processor(
    name: *const std::ffi::c_char,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    let registry = kreuzberg::plugins::registry::get_post_processor_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.remove(plugin_name) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Remove all registered C plugins of this trait.
///
/// # Parameters
///
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `out_error` may be null. When non-null, the caller owns the resulting string
/// and must free it with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_clear_post_processor(out_error: *mut *mut std::ffi::c_char) -> i32 {
    let registry = kreuzberg::plugins::registry::get_post_processor_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.clear() {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// VTable for C plugin bridges implementing the `Validator` trait.
///
/// # Safety
///
/// All function pointers must be valid for the lifetime of any bridge created from
/// this vtable.  `free_user_data`, when non-null, is called once with `user_data`
/// when the bridge is dropped.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct KreuzbergValidatorVTable {
    /// Return a null-terminated UTF-8 name string into `out_name`; return 0 on success.
    pub name_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_name: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Return a null-terminated UTF-8 version string into `out_version`; return 0 on success.
    pub version_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_version: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Initialise the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub initialize_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Shut down the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub shutdown_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Validate an extraction result.
    ///
    /// Check the extraction result and return `Ok(())` if valid, or an error
    /// if validation fails.
    ///
    /// # Arguments
    ///
    /// * `result` - The extraction result to validate
    /// * `config` - Extraction configuration
    ///
    /// # Returns
    ///
    /// - `Ok(())` if validation passes
    /// - `Err(...)` if validation fails (extraction will fail)
    ///
    /// # Errors
    ///
    /// - `KreuzbergError::Validation` - Validation failed
    /// - Any other error type appropriate for the failure
    ///
    /// # Example - Content Length Validation
    ///
    /// ```rust
    /// async fn validate(&self, result: &ExtractionResult, config: &ExtractionConfig)
    /// -> Result<()> {
    /// let length = result.content.len();
    ///
    /// if length < self.min {
    /// return Err(KreuzbergError::validation(format!(
    /// "Content too short: {} < {} characters",
    /// length, self.min
    /// )));
    /// }
    ///
    /// if length > self.max {
    /// return Err(KreuzbergError::validation(format!(
    /// "Content too long: {} > {} characters",
    /// length, self.max
    /// )));
    /// }
    ///
    /// Ok(())
    /// }
    /// ```
    ///
    /// # Example - Quality Score Validation
    ///
    /// ```rust
    /// async fn validate(&self, result: &ExtractionResult, config: &ExtractionConfig)
    /// -> Result<()> {
    /// // Check if quality_score exists in metadata
    /// let score = result.metadata
    /// .additional
    /// .get("quality_score")
    /// .and_then(|v| v.as_f64())
    /// .unwrap_or(0.0);
    ///
    /// if score < self.min_score {
    /// return Err(KreuzbergError::validation(format!(
    /// "Quality score too low: {} < {}",
    /// score, self.min_score
    /// )));
    /// }
    ///
    /// Ok(())
    /// }
    /// ```
    ///
    /// # Example - Security Validation
    ///
    /// ```rust
    /// async fn validate(&self, result: &ExtractionResult, config: &ExtractionConfig)
    /// -> Result<()> {
    /// // Check for blocked patterns
    /// for pattern in &self.blocked_patterns {
    /// if result.content.contains(pattern) {
    /// return Err(KreuzbergError::validation(format!(
    /// "Content contains blocked pattern: {}",
    /// pattern
    /// )));
    /// }
    /// }
    ///
    /// Ok(())
    /// }
    /// ```
    pub validate: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            result: *const std::ffi::c_char,
            config: *const std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional: Check if this validator should run for a given result.
    ///
    /// Allows conditional validation based on MIME type, metadata, or content.
    /// Defaults to `true` (always run).
    ///
    /// # Arguments
    ///
    /// * `result` - The extraction result to check
    /// * `config` - Extraction configuration
    ///
    /// # Returns
    ///
    /// `true` if the validator should run, `false` to skip.
    ///
    /// # Example
    ///
    /// ```rust
    /// Only validate PDF documents
    /// fn should_validate(&self, result: &ExtractionResult, config: &ExtractionConfig) -> bool {
    /// result.mime_type == "application/pdf"
    /// }
    /// ```
    pub should_validate: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            _result: *const std::ffi::c_char,
            _config: *const std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional: Get the validation priority.
    ///
    /// Higher priority validators run first. Useful for ordering validation checks
    /// (e.g., run cheap validations before expensive ones).
    ///
    /// Default priority is 50.
    ///
    /// # Returns
    ///
    /// Priority value (higher = runs earlier).
    ///
    /// # Example
    ///
    /// ```rust
    /// Run this validator first (it's fast)
    /// fn priority(&self) -> i32 {
    /// 100
    /// }
    /// ```
    pub priority: Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void) -> i32>,
    /// Optional string destructor: called for strings returned by vtable callbacks.
    pub free_string: Option<unsafe extern "C" fn(*mut std::ffi::c_char)>,
    /// Optional destructor: called once with `user_data` when the bridge is dropped.
    pub free_user_data: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
}

// SAFETY: all fields are function pointers and free_user_data, which are Send + Sync.
unsafe impl Send for KreuzbergValidatorVTable {}
unsafe impl Sync for KreuzbergValidatorVTable {}

/// Rust-side bridge that holds a C vtable pointer and opaque `user_data`.
///
/// Implements `Validator` by forwarding calls through the vtable.
pub struct KreuzbergValidatorBridge {
    vtable: KreuzbergValidatorVTable,
    user_data: *const std::ffi::c_void,
    cached_name: String,
    cached_version: String,
}

impl std::fmt::Debug for KreuzbergValidatorBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KreuzbergValidatorBridge")
            .field("cached_name", &self.cached_name)
            .field("cached_version", &self.cached_version)
            .finish_non_exhaustive()
    }
}

// SAFETY: The caller is responsible for ensuring `user_data` is safe to send across
// thread boundaries. This is documented in `KreuzbergValidatorVTable` and the registration function.
unsafe impl Send for KreuzbergValidatorBridge {}
unsafe impl Sync for KreuzbergValidatorBridge {}

impl Drop for KreuzbergValidatorBridge {
    fn drop(&mut self) {
        if let Some(free_fn) = self.vtable.free_user_data {
            // SAFETY: free_fn is a valid function pointer; user_data is the pointer
            // originally provided at registration. Called exactly once here.
            unsafe { free_fn(self.user_data as *mut std::ffi::c_void) }
        }
    }
}

impl KreuzbergValidatorBridge {
    /// Create a new bridge from a vtable and opaque user_data pointer.
    ///
    /// # Safety
    ///
    /// `vtable` must remain valid for the lifetime of the returned bridge.
    /// `user_data` must be valid for any thread that calls methods on this bridge.
    /// All required fn pointers in `vtable` must be non-null.
    pub unsafe fn new(name: String, vtable: KreuzbergValidatorVTable, user_data: *const std::ffi::c_void) -> Self {
        Self {
            vtable,
            user_data,
            cached_name: name,
            cached_version: String::new(),
        }
    }
}

impl kreuzberg::plugins::Plugin for KreuzbergValidatorBridge {
    fn name(&self) -> &str {
        &self.cached_name
    }
    fn version(&self) -> String {
        let Some(fp) = self.vtable.version_fn else {
            return String::new();
        };
        let mut _out: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out, &mut _out_error) };
        if rc != 0 {
            if !_out_error.is_null() {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let _ = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
            }
            return self.cached_version.clone();
        }
        if _out.is_null() {
            return self.cached_version.clone();
        }
        // SAFETY: _out is a callee-allocated NUL-terminated string.
        let version = unsafe { std::ffi::CStr::from_ptr(_out) }.to_string_lossy().into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out) };
        }
        version
    }
    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.initialize_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("initialize returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.shutdown_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("shutdown returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::Validator for KreuzbergValidatorBridge {
    async fn validate(
        &self,
        result: &kreuzberg::ExtractionResult,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let result = result.clone();
        let config = config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.validate else {
                return Err(Box::from("vtable.validate is null — bridge not initialised"));
            };
            let _result_json = serde_json::to_string(&result).unwrap_or_default();
            let _result_cs = match std::ffi::CString::new(_result_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param result"));
                }
            };
            let result_ptr = _result_cs.as_ptr();
            let _config_json = serde_json::to_string(&config).unwrap_or_default();
            let _config_cs = match std::ffi::CString::new(_config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param config"));
                }
            };
            let config_ptr = _config_cs.as_ptr();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe { fp(user_data, result_ptr, config_ptr, &mut _out_error) };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.validate returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            Ok(())
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in validate: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }

    fn should_validate(&self, _result: &kreuzberg::ExtractionResult, _config: &kreuzberg::ExtractionConfig) -> bool {
        let Some(fp) = self.vtable.should_validate else {
            return false;
        };
        let __result_json = serde_json::to_string(&_result).unwrap_or_default();
        let __result_cs = match std::ffi::CString::new(__result_json) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let _result_ptr = __result_cs.as_ptr();
        let __config_json = serde_json::to_string(&_config).unwrap_or_default();
        let __config_cs = match std::ffi::CString::new(__config_json) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let _config_ptr = __config_cs.as_ptr();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, _result_ptr, _config_ptr) };
        _rc != 0
    }

    fn priority(&self) -> i32 {
        let Some(fp) = self.vtable.priority else {
            return 0;
        };
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        unsafe { fp(self.user_data) }
    }
}

/// Register a C plugin implementing `Validator` via a vtable.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `vtable`: vtable with function pointers implementing the trait.
/// - `user_data`: opaque pointer forwarded to every vtable function.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// All function pointers in `vtable` must remain valid until the plugin is
/// unregistered. `user_data` must be safe to use from any thread that calls
/// into the plugin.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_register_validator(
    name: *const std::ffi::c_char,
    vtable: KreuzbergValidatorVTable,
    user_data: *const std::ffi::c_void,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    if vtable.free_string.is_none() {
        ffi_set_out_error(out_error, "vtable.free_string must not be null");
        return 1;
    }
    if vtable.validate.is_none() {
        ffi_set_out_error(out_error, "vtable.validate must not be null");
        return 1;
    }

    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_owned(),
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    // SAFETY: vtable and user_data validity is the caller's responsibility.
    let bridge = unsafe { KreuzbergValidatorBridge::new(plugin_name, vtable, user_data) };
    let arc: Arc<dyn kreuzberg::Validator> = Arc::new(bridge);

    let registry = kreuzberg::plugins::registry::get_validator_registry();
    let mut registry = registry.write();

    if let Err(e) = registry.register(arc) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Unregister a previously registered C plugin by name.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `name` must point to a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_unregister_validator(
    name: *const std::ffi::c_char,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    let registry = kreuzberg::plugins::registry::get_validator_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.remove(plugin_name) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Remove all registered C plugins of this trait.
///
/// # Parameters
///
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `out_error` may be null. When non-null, the caller owns the resulting string
/// and must free it with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_clear_validator(out_error: *mut *mut std::ffi::c_char) -> i32 {
    let registry = kreuzberg::plugins::registry::get_validator_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.clear() {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// VTable for C plugin bridges implementing the `EmbeddingBackend` trait.
///
/// # Safety
///
/// All function pointers must be valid for the lifetime of any bridge created from
/// this vtable.  `free_user_data`, when non-null, is called once with `user_data`
/// when the bridge is dropped.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct KreuzbergEmbeddingBackendVTable {
    /// Return a null-terminated UTF-8 name string into `out_name`; return 0 on success.
    pub name_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_name: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Return a null-terminated UTF-8 version string into `out_version`; return 0 on success.
    pub version_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_version: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Initialise the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub initialize_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Shut down the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub shutdown_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Embedding vector dimension. Must be `> 0` and must match the length of
    /// every vector returned by `embed`.
    pub dimensions: Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void) -> usize>,
    /// Embed a batch of texts, returning one vector per input in order.
    ///
    /// # Errors
    ///
    /// Implementations should return `Plugin` for
    /// backend-specific failures. The dispatcher layers its own validation
    /// (length, per-vector dimension) on top.
    pub embed: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            texts: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional string destructor: called for strings returned by vtable callbacks.
    pub free_string: Option<unsafe extern "C" fn(*mut std::ffi::c_char)>,
    /// Optional destructor: called once with `user_data` when the bridge is dropped.
    pub free_user_data: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
}

// SAFETY: all fields are function pointers and free_user_data, which are Send + Sync.
unsafe impl Send for KreuzbergEmbeddingBackendVTable {}
unsafe impl Sync for KreuzbergEmbeddingBackendVTable {}

/// Rust-side bridge that holds a C vtable pointer and opaque `user_data`.
///
/// Implements `EmbeddingBackend` by forwarding calls through the vtable.
pub struct KreuzbergEmbeddingBackendBridge {
    vtable: KreuzbergEmbeddingBackendVTable,
    user_data: *const std::ffi::c_void,
    cached_name: String,
    cached_version: String,
}

impl std::fmt::Debug for KreuzbergEmbeddingBackendBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KreuzbergEmbeddingBackendBridge")
            .field("cached_name", &self.cached_name)
            .field("cached_version", &self.cached_version)
            .finish_non_exhaustive()
    }
}

// SAFETY: The caller is responsible for ensuring `user_data` is safe to send across
// thread boundaries. This is documented in `KreuzbergEmbeddingBackendVTable` and the registration function.
unsafe impl Send for KreuzbergEmbeddingBackendBridge {}
unsafe impl Sync for KreuzbergEmbeddingBackendBridge {}

impl Drop for KreuzbergEmbeddingBackendBridge {
    fn drop(&mut self) {
        if let Some(free_fn) = self.vtable.free_user_data {
            // SAFETY: free_fn is a valid function pointer; user_data is the pointer
            // originally provided at registration. Called exactly once here.
            unsafe { free_fn(self.user_data as *mut std::ffi::c_void) }
        }
    }
}

impl KreuzbergEmbeddingBackendBridge {
    /// Create a new bridge from a vtable and opaque user_data pointer.
    ///
    /// # Safety
    ///
    /// `vtable` must remain valid for the lifetime of the returned bridge.
    /// `user_data` must be valid for any thread that calls methods on this bridge.
    /// All required fn pointers in `vtable` must be non-null.
    pub unsafe fn new(
        name: String,
        vtable: KreuzbergEmbeddingBackendVTable,
        user_data: *const std::ffi::c_void,
    ) -> Self {
        Self {
            vtable,
            user_data,
            cached_name: name,
            cached_version: String::new(),
        }
    }
}

impl kreuzberg::plugins::Plugin for KreuzbergEmbeddingBackendBridge {
    fn name(&self) -> &str {
        &self.cached_name
    }
    fn version(&self) -> String {
        let Some(fp) = self.vtable.version_fn else {
            return String::new();
        };
        let mut _out: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out, &mut _out_error) };
        if rc != 0 {
            if !_out_error.is_null() {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let _ = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
            }
            return self.cached_version.clone();
        }
        if _out.is_null() {
            return self.cached_version.clone();
        }
        // SAFETY: _out is a callee-allocated NUL-terminated string.
        let version = unsafe { std::ffi::CStr::from_ptr(_out) }.to_string_lossy().into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out) };
        }
        version
    }
    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.initialize_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("initialize returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.shutdown_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("shutdown returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::EmbeddingBackend for KreuzbergEmbeddingBackendBridge {
    fn dimensions(&self) -> usize {
        let Some(fp) = self.vtable.dimensions else {
            return 0;
        };
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        unsafe { fp(self.user_data) }
    }

    async fn embed(&self, texts: Vec<String>) -> std::result::Result<Vec<Vec<f32>>, kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let texts = texts.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.embed else {
                return Err(Box::from("vtable.embed is null — bridge not initialised"));
            };
            let _texts_json = serde_json::to_string(&texts).unwrap_or_default();
            let _texts_cs = match std::ffi::CString::new(_texts_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param texts"));
                }
            };
            let texts_ptr = _texts_cs.as_ptr();
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe { fp(user_data, texts_ptr, &mut _out_result, &mut _out_error) };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.embed returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            if _out_result.is_null() {
                return Err(Box::from("vtable.embed returned null out_result"));
            }
            // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
            let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                .to_string_lossy()
                .into_owned();
            if let Some(free_fn) = vtable.free_string {
                // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                unsafe { free_fn(_out_result) };
            }
            serde_json::from_str::<Vec<Vec<f32>>>(&json)
                .map_err(|e| Box::from(e.to_string()) as Box<dyn std::error::Error + Send + Sync>)
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in embed: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }
}

/// Register a C plugin implementing `EmbeddingBackend` via a vtable.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `vtable`: vtable with function pointers implementing the trait.
/// - `user_data`: opaque pointer forwarded to every vtable function.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// All function pointers in `vtable` must remain valid until the plugin is
/// unregistered. `user_data` must be safe to use from any thread that calls
/// into the plugin.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_register_embedding_backend(
    name: *const std::ffi::c_char,
    vtable: KreuzbergEmbeddingBackendVTable,
    user_data: *const std::ffi::c_void,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    if vtable.free_string.is_none() {
        ffi_set_out_error(out_error, "vtable.free_string must not be null");
        return 1;
    }
    if vtable.dimensions.is_none() {
        ffi_set_out_error(out_error, "vtable.dimensions must not be null");
        return 1;
    }
    if vtable.embed.is_none() {
        ffi_set_out_error(out_error, "vtable.embed must not be null");
        return 1;
    }

    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_owned(),
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    // SAFETY: vtable and user_data validity is the caller's responsibility.
    let bridge = unsafe { KreuzbergEmbeddingBackendBridge::new(plugin_name, vtable, user_data) };
    let arc: Arc<dyn kreuzberg::EmbeddingBackend> = Arc::new(bridge);

    let registry = kreuzberg::plugins::registry::get_embedding_backend_registry();
    let mut registry = registry.write();

    if let Err(e) = registry.register(arc) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Unregister a previously registered C plugin by name.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `name` must point to a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_unregister_embedding_backend(
    name: *const std::ffi::c_char,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    let registry = kreuzberg::plugins::registry::get_embedding_backend_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.remove(plugin_name) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Remove all registered C plugins of this trait.
///
/// # Parameters
///
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `out_error` may be null. When non-null, the caller owns the resulting string
/// and must free it with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_clear_embedding_backend(out_error: *mut *mut std::ffi::c_char) -> i32 {
    let registry = kreuzberg::plugins::registry::get_embedding_backend_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.clear() {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// VTable for C plugin bridges implementing the `DocumentExtractor` trait.
///
/// # Safety
///
/// All function pointers must be valid for the lifetime of any bridge created from
/// this vtable.  `free_user_data`, when non-null, is called once with `user_data`
/// when the bridge is dropped.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct KreuzbergDocumentExtractorVTable {
    /// Return a null-terminated UTF-8 name string into `out_name`; return 0 on success.
    pub name_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_name: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Return a null-terminated UTF-8 version string into `out_version`; return 0 on success.
    pub version_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_version: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Initialise the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub initialize_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Shut down the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub shutdown_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Extract content from a byte array.
    ///
    /// This is the core extraction method that processes in-memory document data.
    ///
    /// # Arguments
    ///
    /// * `content` - Raw document bytes
    /// * `mime_type` - MIME type of the document (already validated)
    /// * `config` - Extraction configuration
    ///
    /// # Returns
    ///
    /// An `InternalDocument` containing the extracted elements, metadata, and tables.
    /// The pipeline will convert this into the public `ExtractionResult`.
    ///
    /// # Errors
    ///
    /// - `KreuzbergError::Parsing` - Document parsing failed
    /// - `KreuzbergError::Validation` - Invalid document structure
    /// - `KreuzbergError::Io` - I/O errors (these always bubble up)
    /// - `KreuzbergError::MissingDependency` - Required dependency not available
    pub extract_bytes: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            content: *const u8,
            content_len: usize,
            mime_type: *const std::ffi::c_char,
            config: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Extract content from a file.
    ///
    /// Default implementation reads the file and calls `extract_bytes`.
    /// Override for custom file handling, streaming, or memory optimizations.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the document file
    /// * `mime_type` - MIME type of the document (already validated)
    /// * `config` - Extraction configuration
    ///
    /// # Returns
    ///
    /// An `InternalDocument` containing the extracted elements, metadata, and tables.
    ///
    /// # Errors
    ///
    /// Same as `extract_bytes`, plus file I/O errors.
    pub extract_file: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            path: *const std::ffi::c_char,
            mime_type: *const std::ffi::c_char,
            config: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Get the list of MIME types supported by this extractor.
    ///
    /// Can include exact MIME types and prefix patterns:
    /// - Exact: `"application/pdf"`, `"text/plain"`
    /// - Prefix: `"image/*"` (matches any image type)
    ///
    /// # Returns
    ///
    /// A slice of MIME type strings.
    pub supported_mime_types: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Get the priority of this extractor.
    ///
    /// Higher priority extractors are preferred when multiple extractors
    /// support the same MIME type.
    ///
    /// # Priority Guidelines
    ///
    /// - **0-25**: Fallback/low-quality extractors
    /// - **26-49**: Alternative extractors
    /// - **50**: Default priority (built-in extractors)
    /// - **51-75**: Premium/enhanced extractors
    /// - **76-100**: Specialized/high-priority extractors
    ///
    /// # Returns
    ///
    /// Priority value (default: 50)
    pub priority: Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void) -> i32>,
    /// Optional: Check if this extractor can handle a specific file.
    ///
    /// Allows for more sophisticated detection beyond MIME types.
    /// Defaults to `true` (rely on MIME type matching).
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the file to check
    /// * `mime_type` - Detected MIME type
    ///
    /// # Returns
    ///
    /// `true` if the extractor can handle this file, `false` otherwise.
    pub can_handle: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            _path: *const std::ffi::c_char,
            _mime_type: *const std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional string destructor: called for strings returned by vtable callbacks.
    pub free_string: Option<unsafe extern "C" fn(*mut std::ffi::c_char)>,
    /// Optional destructor: called once with `user_data` when the bridge is dropped.
    pub free_user_data: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
}

// SAFETY: all fields are function pointers and free_user_data, which are Send + Sync.
unsafe impl Send for KreuzbergDocumentExtractorVTable {}
unsafe impl Sync for KreuzbergDocumentExtractorVTable {}

/// Rust-side bridge that holds a C vtable pointer and opaque `user_data`.
///
/// Implements `DocumentExtractor` by forwarding calls through the vtable.
pub struct KreuzbergDocumentExtractorBridge {
    vtable: KreuzbergDocumentExtractorVTable,
    user_data: *const std::ffi::c_void,
    cached_name: String,
    cached_version: String,
    supported_mime_types_strs: &'static [&'static str],
}

impl std::fmt::Debug for KreuzbergDocumentExtractorBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KreuzbergDocumentExtractorBridge")
            .field("cached_name", &self.cached_name)
            .field("cached_version", &self.cached_version)
            .finish_non_exhaustive()
    }
}

// SAFETY: The caller is responsible for ensuring `user_data` is safe to send across
// thread boundaries. This is documented in `KreuzbergDocumentExtractorVTable` and the registration function.
unsafe impl Send for KreuzbergDocumentExtractorBridge {}
unsafe impl Sync for KreuzbergDocumentExtractorBridge {}

impl Drop for KreuzbergDocumentExtractorBridge {
    fn drop(&mut self) {
        if let Some(free_fn) = self.vtable.free_user_data {
            // SAFETY: free_fn is a valid function pointer; user_data is the pointer
            // originally provided at registration. Called exactly once here.
            unsafe { free_fn(self.user_data as *mut std::ffi::c_void) }
        }
    }
}

impl KreuzbergDocumentExtractorBridge {
    /// Create a new bridge from a vtable and opaque user_data pointer.
    ///
    /// # Safety
    ///
    /// `vtable` must remain valid for the lifetime of the returned bridge.
    /// `user_data` must be valid for any thread that calls methods on this bridge.
    /// All required fn pointers in `vtable` must be non-null.
    pub unsafe fn new(
        name: String,
        vtable: KreuzbergDocumentExtractorVTable,
        user_data: *const std::ffi::c_void,
    ) -> Self {
        // Cache supported_mime_types() result for the lifetime of the bridge (required: returns &[&str]).
        let supported_mime_types_strs: &'static [&'static str] = if let Some(fp) = vtable.supported_mime_types {
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; user_data validity is the caller's
            // responsibility (documented in the vtable API contract).
            let _rc = unsafe { fp(user_data, &mut _out_result, &mut _out_error) };
            if _out_result.is_null() {
                &[]
            } else {
                // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
                let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_result) };
                }
                let owned: Vec<String> = serde_json::from_str(&json).unwrap_or_default();
                // Leak each string into a `'static str`; these live for the process lifetime.
                // The bridge struct is expected to live for the duration of the program
                // (registered plugins are not typically removed), so this is acceptable.
                let leaked: Vec<&'static str> = owned
                    .into_iter()
                    .map(|s| -> &'static str { Box::leak(s.into_boxed_str()) })
                    .collect();
                Box::leak(leaked.into_boxed_slice())
            }
        } else {
            &[]
        };

        Self {
            vtable,
            user_data,
            cached_name: name,
            cached_version: String::new(),
            supported_mime_types_strs,
        }
    }
}

impl kreuzberg::plugins::Plugin for KreuzbergDocumentExtractorBridge {
    fn name(&self) -> &str {
        &self.cached_name
    }
    fn version(&self) -> String {
        let Some(fp) = self.vtable.version_fn else {
            return String::new();
        };
        let mut _out: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out, &mut _out_error) };
        if rc != 0 {
            if !_out_error.is_null() {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let _ = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
            }
            return self.cached_version.clone();
        }
        if _out.is_null() {
            return self.cached_version.clone();
        }
        // SAFETY: _out is a callee-allocated NUL-terminated string.
        let version = unsafe { std::ffi::CStr::from_ptr(_out) }.to_string_lossy().into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out) };
        }
        version
    }
    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.initialize_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("initialize returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.shutdown_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("shutdown returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
}

#[async_trait::async_trait]
impl kreuzberg::DocumentExtractor for KreuzbergDocumentExtractorBridge {
    async fn extract_bytes(
        &self,
        content: &[u8],
        mime_type: &str,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<kreuzberg::InternalDocument, kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let content = content.to_vec();
        let mime_type = mime_type.to_string();
        let config = config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.extract_bytes else {
                return Err(Box::from("vtable.extract_bytes is null — bridge not initialised"));
            };
            let _mime_type_cs = match std::ffi::CString::new(mime_type) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in param mime_type"));
                }
            };
            let mime_type_ptr = _mime_type_cs.as_ptr();
            let _config_json = serde_json::to_string(&config).unwrap_or_default();
            let _config_cs = match std::ffi::CString::new(_config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param config"));
                }
            };
            let config_ptr = _config_cs.as_ptr();
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe {
                fp(
                    user_data,
                    content.as_ptr(),
                    content.len(),
                    mime_type_ptr,
                    config_ptr,
                    &mut _out_result,
                    &mut _out_error,
                )
            };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.extract_bytes returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            if _out_result.is_null() {
                return Err(Box::from("vtable.extract_bytes returned null out_result"));
            }
            // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
            let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                .to_string_lossy()
                .into_owned();
            if let Some(free_fn) = vtable.free_string {
                // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                unsafe { free_fn(_out_result) };
            }
            serde_json::from_str::<kreuzberg::InternalDocument>(&json)
                .map_err(|e| Box::from(e.to_string()) as Box<dyn std::error::Error + Send + Sync>)
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in extract_bytes: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }

    async fn extract_file(
        &self,
        path: &std::path::Path,
        mime_type: &str,
        config: &kreuzberg::ExtractionConfig,
    ) -> std::result::Result<kreuzberg::InternalDocument, kreuzberg::KreuzbergError> {
        struct _SendFn<F>(F);
        // SAFETY: caller guarantees vtable fn pointers and user_data are valid across threads.
        unsafe impl<F> Send for _SendFn<F> {}
        impl<F: FnOnce() -> R, R> _SendFn<F> {
            fn call(self) -> R {
                (self.0)()
            }
        }

        let _cached_name = self.cached_name.clone();
        let vtable = self.vtable;
        let user_data = self.user_data;
        let path = path.to_path_buf();
        let mime_type = mime_type.to_string();
        let config = config.clone();

        let _task = _SendFn(move || {
            // Inline the sync body:
            let Some(fp) = vtable.extract_file else {
                return Err(Box::from("vtable.extract_file is null — bridge not initialised"));
            };
            let _path_cs = match std::ffi::CString::new(path.to_string_lossy().as_ref()) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in param path"));
                }
            };
            let path_ptr = _path_cs.as_ptr();
            let _mime_type_cs = match std::ffi::CString::new(mime_type) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in param mime_type"));
                }
            };
            let mime_type_ptr = _mime_type_cs.as_ptr();
            let _config_json = serde_json::to_string(&config).unwrap_or_default();
            let _config_cs = match std::ffi::CString::new(_config_json) {
                Ok(s) => s,
                Err(_) => {
                    return Err(Box::from("nul byte in serialized param config"));
                }
            };
            let config_ptr = _config_cs.as_ptr();
            let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
            let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
            // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
            // user_data validity is the caller's responsibility (documented in the vtable API).
            let _rc = unsafe {
                fp(
                    user_data,
                    path_ptr,
                    mime_type_ptr,
                    config_ptr,
                    &mut _out_result,
                    &mut _out_error,
                )
            };
            if _rc != 0 {
                let msg = if _out_error.is_null() {
                    format!("vtable.extract_file returned error code {}", _rc)
                } else {
                    // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                    let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                        .to_string_lossy()
                        .into_owned();
                    if let Some(free_fn) = vtable.free_string {
                        // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                        unsafe { free_fn(_out_error) };
                    }
                    msg
                };
                return Err(Box::from(msg));
            }
            if _out_result.is_null() {
                return Err(Box::from("vtable.extract_file returned null out_result"));
            }
            // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
            let json = unsafe { std::ffi::CStr::from_ptr(_out_result) }
                .to_string_lossy()
                .into_owned();
            if let Some(free_fn) = vtable.free_string {
                // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                unsafe { free_fn(_out_result) };
            }
            serde_json::from_str::<kreuzberg::InternalDocument>(&json)
                .map_err(|e| Box::from(e.to_string()) as Box<dyn std::error::Error + Send + Sync>)
        });
        tokio::task::spawn_blocking(move || _task.call())
            .await
            .map_err(|e| kreuzberg::KreuzbergError::Other(format!("spawn_blocking failed in extract_file: {}", e)))?
            .map_err(|e: Box<dyn std::error::Error + Send + Sync>| kreuzberg::KreuzbergError::Other(e.to_string()))
    }

    fn supported_mime_types(&self) -> &[&str] {
        self.supported_mime_types_strs
    }

    fn priority(&self) -> i32 {
        let Some(fp) = self.vtable.priority else {
            return 0;
        };
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        unsafe { fp(self.user_data) }
    }

    fn can_handle(&self, _path: &std::path::Path, _mime_type: &str) -> bool {
        let Some(fp) = self.vtable.can_handle else {
            return false;
        };
        let __path_cs = match std::ffi::CString::new(_path.to_string_lossy().as_ref()) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let _path_ptr = __path_cs.as_ptr();
        let __mime_type_cs = match std::ffi::CString::new(_mime_type) {
            Ok(s) => s,
            Err(_) => {
                return false;
            }
        };
        let _mime_type_ptr = __mime_type_cs.as_ptr();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, _path_ptr, _mime_type_ptr) };
        _rc != 0
    }
}

/// Register a C plugin implementing `DocumentExtractor` via a vtable.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `vtable`: vtable with function pointers implementing the trait.
/// - `user_data`: opaque pointer forwarded to every vtable function.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// All function pointers in `vtable` must remain valid until the plugin is
/// unregistered. `user_data` must be safe to use from any thread that calls
/// into the plugin.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_register_document_extractor(
    name: *const std::ffi::c_char,
    vtable: KreuzbergDocumentExtractorVTable,
    user_data: *const std::ffi::c_void,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    if vtable.free_string.is_none() {
        ffi_set_out_error(out_error, "vtable.free_string must not be null");
        return 1;
    }
    if vtable.extract_bytes.is_none() {
        ffi_set_out_error(out_error, "vtable.extract_bytes must not be null");
        return 1;
    }
    if vtable.supported_mime_types.is_none() {
        ffi_set_out_error(out_error, "vtable.supported_mime_types must not be null");
        return 1;
    }

    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_owned(),
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    // SAFETY: vtable and user_data validity is the caller's responsibility.
    let bridge = unsafe { KreuzbergDocumentExtractorBridge::new(plugin_name, vtable, user_data) };
    let arc: Arc<dyn kreuzberg::DocumentExtractor> = Arc::new(bridge);

    let registry = kreuzberg::plugins::registry::get_document_extractor_registry();
    let mut registry = registry.write();

    if let Err(e) = registry.register(arc) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Unregister a previously registered C plugin by name.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `name` must point to a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_unregister_document_extractor(
    name: *const std::ffi::c_char,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    let registry = kreuzberg::plugins::registry::get_document_extractor_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.remove(plugin_name) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Remove all registered C plugins of this trait.
///
/// # Parameters
///
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `out_error` may be null. When non-null, the caller owns the resulting string
/// and must free it with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_clear_document_extractor(out_error: *mut *mut std::ffi::c_char) -> i32 {
    let registry = kreuzberg::plugins::registry::get_document_extractor_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.clear() {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// VTable for C plugin bridges implementing the `Renderer` trait.
///
/// # Safety
///
/// All function pointers must be valid for the lifetime of any bridge created from
/// this vtable.  `free_user_data`, when non-null, is called once with `user_data`
/// when the bridge is dropped.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct KreuzbergRendererVTable {
    /// Return a null-terminated UTF-8 name string into `out_name`; return 0 on success.
    pub name_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_name: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Return a null-terminated UTF-8 version string into `out_version`; return 0 on success.
    pub version_fn: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            out_version: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Initialise the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub initialize_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Shut down the plugin; return 0 on success, non-zero on failure (error text in `out_error`).
    pub shutdown_fn:
        Option<unsafe extern "C" fn(user_data: *const std::ffi::c_void, out_error: *mut *mut std::ffi::c_char) -> i32>,
    /// Render an [`InternalDocument`] to the output format.
    ///
    /// # Arguments
    ///
    /// * `doc` - The internal document to render
    ///
    /// # Returns
    ///
    /// The rendered output as a string.
    ///
    /// # Errors
    ///
    /// Returns an error if rendering fails.
    pub render: Option<
        unsafe extern "C" fn(
            user_data: *const std::ffi::c_void,
            doc: *const std::ffi::c_char,
            out_result: *mut *mut std::ffi::c_char,
            out_error: *mut *mut std::ffi::c_char,
        ) -> i32,
    >,
    /// Optional string destructor: called for strings returned by vtable callbacks.
    pub free_string: Option<unsafe extern "C" fn(*mut std::ffi::c_char)>,
    /// Optional destructor: called once with `user_data` when the bridge is dropped.
    pub free_user_data: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
}

// SAFETY: all fields are function pointers and free_user_data, which are Send + Sync.
unsafe impl Send for KreuzbergRendererVTable {}
unsafe impl Sync for KreuzbergRendererVTable {}

/// Rust-side bridge that holds a C vtable pointer and opaque `user_data`.
///
/// Implements `Renderer` by forwarding calls through the vtable.
pub struct KreuzbergRendererBridge {
    vtable: KreuzbergRendererVTable,
    user_data: *const std::ffi::c_void,
    cached_name: String,
    cached_version: String,
}

impl std::fmt::Debug for KreuzbergRendererBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KreuzbergRendererBridge")
            .field("cached_name", &self.cached_name)
            .field("cached_version", &self.cached_version)
            .finish_non_exhaustive()
    }
}

// SAFETY: The caller is responsible for ensuring `user_data` is safe to send across
// thread boundaries. This is documented in `KreuzbergRendererVTable` and the registration function.
unsafe impl Send for KreuzbergRendererBridge {}
unsafe impl Sync for KreuzbergRendererBridge {}

impl Drop for KreuzbergRendererBridge {
    fn drop(&mut self) {
        if let Some(free_fn) = self.vtable.free_user_data {
            // SAFETY: free_fn is a valid function pointer; user_data is the pointer
            // originally provided at registration. Called exactly once here.
            unsafe { free_fn(self.user_data as *mut std::ffi::c_void) }
        }
    }
}

impl KreuzbergRendererBridge {
    /// Create a new bridge from a vtable and opaque user_data pointer.
    ///
    /// # Safety
    ///
    /// `vtable` must remain valid for the lifetime of the returned bridge.
    /// `user_data` must be valid for any thread that calls methods on this bridge.
    /// All required fn pointers in `vtable` must be non-null.
    pub unsafe fn new(name: String, vtable: KreuzbergRendererVTable, user_data: *const std::ffi::c_void) -> Self {
        Self {
            vtable,
            user_data,
            cached_name: name,
            cached_version: String::new(),
        }
    }
}

impl kreuzberg::plugins::Plugin for KreuzbergRendererBridge {
    fn name(&self) -> &str {
        &self.cached_name
    }
    fn version(&self) -> String {
        let Some(fp) = self.vtable.version_fn else {
            return String::new();
        };
        let mut _out: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out, &mut _out_error) };
        if rc != 0 {
            if !_out_error.is_null() {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let _ = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
            }
            return self.cached_version.clone();
        }
        if _out.is_null() {
            return self.cached_version.clone();
        }
        // SAFETY: _out is a callee-allocated NUL-terminated string.
        let version = unsafe { std::ffi::CStr::from_ptr(_out) }.to_string_lossy().into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out) };
        }
        version
    }
    fn initialize(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.initialize_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("initialize returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
    fn shutdown(&self) -> std::result::Result<(), kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.shutdown_fn else {
            return Ok(());
        };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() {
                format!("shutdown returned {}", rc)
            } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Plugin {
                message: msg,
                plugin_name: String::new(),
            });
        }
        Ok(())
    }
}

impl kreuzberg::Renderer for KreuzbergRendererBridge {
    fn render(&self, doc: &kreuzberg::InternalDocument) -> std::result::Result<String, kreuzberg::KreuzbergError> {
        let Some(fp) = self.vtable.render else {
            return Err(kreuzberg::KreuzbergError::Other(
                "vtable.render is null — bridge not initialised".to_string(),
            ));
        };
        let _doc_json = serde_json::to_string(&doc).unwrap_or_default();
        let _doc_cs = match std::ffi::CString::new(_doc_json) {
            Ok(s) => s,
            Err(_) => {
                return Err(kreuzberg::KreuzbergError::Other(
                    "nul byte in serialized param doc".to_string(),
                ));
            }
        };
        let doc_ptr = _doc_cs.as_ptr();
        let mut _out_result: *mut std::ffi::c_char = std::ptr::null_mut();
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is a valid non-null function pointer; all temporaries outlive this call;
        // user_data validity is the caller's responsibility (documented in the vtable API).
        let _rc = unsafe { fp(self.user_data, doc_ptr, &mut _out_result, &mut _out_error) };
        if _rc != 0 {
            let msg = if _out_error.is_null() {
                format!("vtable.render returned error code {}", _rc)
            } else {
                // SAFETY: out_error was written by the callee as a valid NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }
                    .to_string_lossy()
                    .into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err(kreuzberg::KreuzbergError::Other(msg));
        }
        if _out_result.is_null() {
            return Ok(String::new());
        }
        // SAFETY: out_result was written by the callee as a valid NUL-terminated string.
        let out = unsafe { std::ffi::CStr::from_ptr(_out_result) }
            .to_string_lossy()
            .into_owned();
        if let Some(free_fn) = self.vtable.free_string {
            // SAFETY: free_fn is the vtable-provided destructor for callback strings.
            unsafe { free_fn(_out_result) };
        }
        Ok(out)
    }
}

/// Register a C plugin implementing `Renderer` via a vtable.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `vtable`: vtable with function pointers implementing the trait.
/// - `user_data`: opaque pointer forwarded to every vtable function.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// All function pointers in `vtable` must remain valid until the plugin is
/// unregistered. `user_data` must be safe to use from any thread that calls
/// into the plugin.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_register_renderer(
    name: *const std::ffi::c_char,
    vtable: KreuzbergRendererVTable,
    user_data: *const std::ffi::c_void,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    if vtable.free_string.is_none() {
        ffi_set_out_error(out_error, "vtable.free_string must not be null");
        return 1;
    }
    if vtable.render.is_none() {
        ffi_set_out_error(out_error, "vtable.render must not be null");
        return 1;
    }

    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s.to_owned(),
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    // SAFETY: vtable and user_data validity is the caller's responsibility.
    let bridge = unsafe { KreuzbergRendererBridge::new(plugin_name, vtable, user_data) };
    let arc: Arc<dyn kreuzberg::Renderer> = Arc::new(bridge);

    let registry = kreuzberg::plugins::registry::get_renderer_registry();
    let mut registry = registry.write();

    if let Err(e) = registry.register(arc) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Unregister a previously registered C plugin by name.
///
/// # Parameters
///
/// - `name`: null-terminated UTF-8 plugin name. Must not be null.
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `name` must point to a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_unregister_renderer(
    name: *const std::ffi::c_char,
    out_error: *mut *mut std::ffi::c_char,
) -> i32 {
    if name.is_null() {
        ffi_set_out_error(out_error, "name must not be null");
        return 1;
    }
    // SAFETY: name is non-null (checked above); it points to a valid C string.
    let plugin_name = match unsafe { std::ffi::CStr::from_ptr(name) }.to_str() {
        Ok(s) => s,
        Err(_) => {
            ffi_set_out_error(out_error, "name is not valid UTF-8");
            return 1;
        }
    };

    let registry = kreuzberg::plugins::registry::get_renderer_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.remove(plugin_name) {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}

/// Remove all registered C plugins of this trait.
///
/// # Parameters
///
/// - `out_error`: receives a heap-allocated error string on failure.
///
/// # Safety
///
/// `out_error` may be null. When non-null, the caller owns the resulting string
/// and must free it with `kreuzberg_free_string`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kreuzberg_clear_renderer(out_error: *mut *mut std::ffi::c_char) -> i32 {
    let registry = kreuzberg::plugins::registry::get_renderer_registry();
    let mut registry = registry.write();
    if let Err(e) = registry.clear() {
        ffi_set_out_error(out_error, &e.to_string());
        return 1;
    }
    0
}
