//! FFI Convenience Wrappers
//!
//! This module provides simple, single-call functions for common PDF operations.
//! These wrappers handle resource management internally, making them easier to use
//! from C, Go, Python, and other FFI consumers.
//!
//! All functions in this module:
//! - Accept file paths directly (no need to create handles)
//! - Return allocated strings/buffers that must be freed by the caller
//! - Use simple error codes for failure conditions
//!
//! # Memory Management
//!
//! Functions returning strings allocate memory that must be freed using `mp_free_string()`.
//! Functions returning byte buffers allocate memory that must be freed using `mp_free_bytes()`.

use std::ffi::{CStr, CString, c_char};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;

use super::buffer::{fz_buffer_data, fz_drop_buffer};
use super::colorspace::FZ_COLORSPACE_RGB;
use super::compat::{fz_new_pixmap_from_page, fz_new_stext_page_from_page};
use super::document::{
    fz_bound_page, fz_count_pages, fz_drop_document, fz_drop_page, fz_load_page, fz_open_document,
};
use super::enhanced::print_production::{mp_quick_validate, mp_repair_pdf};
use super::pixmap::{
    fz_drop_pixmap, fz_pixmap_height, fz_pixmap_samples, fz_pixmap_samples_size, fz_pixmap_width,
};
use super::stext::{fz_drop_stext_page, fz_stext_page_as_text};
use super::write_pixmap::fz_new_buffer_from_pixmap_as_png;
use super::{BUFFERS, DOCUMENTS, Handle};

// ============================================================================
// Result Structures
// ============================================================================

/// PDF document information
#[repr(C)]
pub struct MpPdfInfo {
    /// Number of pages in the document
    pub page_count: i32,
    /// Whether the PDF is encrypted
    pub is_encrypted: i32,
    /// Whether a password is required
    pub needs_password: i32,
    /// PDF version string (e.g., "1.7") - must be freed with mp_free_string
    pub version: *mut c_char,
    /// Document title - must be freed with mp_free_string (null if not present)
    pub title: *mut c_char,
    /// Document author - must be freed with mp_free_string (null if not present)
    pub author: *mut c_char,
    /// Document subject - must be freed with mp_free_string (null if not present)
    pub subject: *mut c_char,
    /// Document creator - must be freed with mp_free_string (null if not present)
    pub creator: *mut c_char,
}

/// Page dimensions
#[repr(C)]
pub struct MpPageDimensions {
    /// Page width in points (1/72 inch)
    pub width: f32,
    /// Page height in points
    pub height: f32,
}

/// Rendered page data
#[repr(C)]
pub struct MpRenderedPage {
    /// PNG image data - must be freed with mp_free_bytes
    pub data: *mut u8,
    /// Length of PNG data in bytes
    pub data_len: usize,
    /// Image width in pixels
    pub width: i32,
    /// Image height in pixels
    pub height: i32,
}

/// Text extraction result
#[repr(C)]
pub struct MpExtractedText {
    /// Extracted text - must be freed with mp_free_string
    pub text: *mut c_char,
    /// Length of text in bytes (not including null terminator)
    pub text_len: usize,
    /// Number of pages processed
    pub pages_processed: i32,
}

// ============================================================================
// Document Information Functions
// ============================================================================

/// Get basic information about a PDF file.
///
/// This is a convenience function that opens a PDF, extracts information,
/// and closes it in a single call.
///
/// # Parameters
/// - `pdf_path`: Path to the PDF file (null-terminated C string)
/// - `info_out`: Pointer to MpPdfInfo structure to fill
///
/// # Returns
/// - `0` on success
/// - `-1` if pdf_path is null
/// - `-2` if info_out is null
/// - `-3` if file cannot be opened
///
/// # Memory
/// The caller must free the string fields (version, title, author, subject, creator)
/// using `mp_free_string()`.
#[unsafe(no_mangle)]
pub extern "C" fn mp_get_pdf_info(pdf_path: *const c_char, info_out: *mut MpPdfInfo) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }
        if info_out.is_null() {
            return -2;
        }

        // Open document
        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return -3;
        }

        // Get page count
        let page_count = fz_count_pages(0, doc_handle);

        // Get metadata from document
        let (version, title, author, subject, creator, is_encrypted, needs_password) =
            if let Some(doc) = DOCUMENTS.get(doc_handle) {
                if let Ok(guard) = doc.lock() {
                    let data = guard.data();

                    // Extract PDF version from header
                    let version = extract_pdf_version(data);

                    // Extract metadata
                    let title = extract_metadata(data, b"/Title");
                    let author = extract_metadata(data, b"/Author");
                    let subject = extract_metadata(data, b"/Subject");
                    let creator = extract_metadata(data, b"/Creator");

                    // Check encryption
                    let is_encrypted = if data
                        .windows(8)
                        .any(|w| w == b"/Encrypt" || w.starts_with(b"/Encrypt "))
                    {
                        1
                    } else {
                        0
                    };

                    (version, title, author, subject, creator, is_encrypted, 0)
                } else {
                    (None, None, None, None, None, 0, 0)
                }
            } else {
                (None, None, None, None, None, 0, 0)
            };

        // Close document
        fz_drop_document(0, doc_handle);

        // Fill output structure
        unsafe {
            (*info_out).page_count = page_count;
            (*info_out).is_encrypted = is_encrypted;
            (*info_out).needs_password = needs_password;
            (*info_out).version = string_to_c(version);
            (*info_out).title = string_to_c(title);
            (*info_out).author = string_to_c(author);
            (*info_out).subject = string_to_c(subject);
            (*info_out).creator = string_to_c(creator);
        }

        0
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_get_pdf_info: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_get_pdf_info: panic: {}", s)
            } else {
                "mp_get_pdf_info: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Free an MpPdfInfo structure's string fields.
///
/// This does NOT free the MpPdfInfo struct itself, only its string fields.
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_pdf_info(info: *mut MpPdfInfo) {
    if info.is_null() {
        return;
    }

    unsafe {
        if !(*info).version.is_null() {
            let _ = CString::from_raw((*info).version);
            (*info).version = ptr::null_mut();
        }
        if !(*info).title.is_null() {
            let _ = CString::from_raw((*info).title);
            (*info).title = ptr::null_mut();
        }
        if !(*info).author.is_null() {
            let _ = CString::from_raw((*info).author);
            (*info).author = ptr::null_mut();
        }
        if !(*info).subject.is_null() {
            let _ = CString::from_raw((*info).subject);
            (*info).subject = ptr::null_mut();
        }
        if !(*info).creator.is_null() {
            let _ = CString::from_raw((*info).creator);
            (*info).creator = ptr::null_mut();
        }
    }
}

/// Get the number of pages in a PDF file.
///
/// Simple convenience function that just returns the page count.
///
/// # Returns
/// - Page count on success (>= 0)
/// - `-1` if pdf_path is null
/// - `-3` if file cannot be opened
#[unsafe(no_mangle)]
pub extern "C" fn mp_get_page_count(pdf_path: *const c_char) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }

        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return -3;
        }

        let count = fz_count_pages(0, doc_handle);
        fz_drop_document(0, doc_handle);

        count
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_get_page_count: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_get_page_count: panic: {}", s)
            } else {
                "mp_get_page_count: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Get the dimensions of a specific page.
///
/// # Parameters
/// - `pdf_path`: Path to the PDF file
/// - `page_num`: Zero-based page number
/// - `dims_out`: Pointer to MpPageDimensions to fill
///
/// # Returns
/// - `0` on success
/// - `-1` if pdf_path is null
/// - `-2` if dims_out is null
/// - `-3` if file cannot be opened
/// - `-4` if page number is out of range
#[unsafe(no_mangle)]
pub extern "C" fn mp_get_page_dimensions(
    pdf_path: *const c_char,
    page_num: i32,
    dims_out: *mut MpPageDimensions,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }
        if dims_out.is_null() {
            return -2;
        }

        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return -3;
        }

        let page_count = fz_count_pages(0, doc_handle);
        if page_num < 0 || page_num >= page_count {
            fz_drop_document(0, doc_handle);
            return -4;
        }

        let page_handle = fz_load_page(0, doc_handle, page_num);
        if page_handle == 0 {
            fz_drop_document(0, doc_handle);
            return -4;
        }

        let bounds = fz_bound_page(0, page_handle);

        unsafe {
            (*dims_out).width = bounds.x1 - bounds.x0;
            (*dims_out).height = bounds.y1 - bounds.y0;
        }

        fz_drop_page(0, page_handle);
        fz_drop_document(0, doc_handle);

        0
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_get_page_dimensions: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_get_page_dimensions: panic: {}", s)
            } else {
                "mp_get_page_dimensions: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

// ============================================================================
// Text Extraction Functions
// ============================================================================

/// Extract all text from a PDF file.
///
/// # Parameters
/// - `pdf_path`: Path to the PDF file
/// - `result_out`: Pointer to MpExtractedText to fill
///
/// # Returns
/// - `0` on success
/// - `-1` if pdf_path is null
/// - `-2` if result_out is null
/// - `-3` if file cannot be opened
///
/// # Memory
/// The caller must free `result_out->text` using `mp_free_extracted_text()`.
#[unsafe(no_mangle)]
pub extern "C" fn mp_extract_text(
    pdf_path: *const c_char,
    result_out: *mut MpExtractedText,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }
        if result_out.is_null() {
            return -2;
        }

        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return -3;
        }

        let page_count = fz_count_pages(0, doc_handle);
        let mut all_text = String::new();
        let mut pages_processed = 0;

        for page_num in 0..page_count {
            let page_handle = fz_load_page(0, doc_handle, page_num);
            if page_handle == 0 {
                continue;
            }

            let stext_handle = fz_new_stext_page_from_page(0, page_handle, ptr::null());
            if stext_handle != 0 {
                let text_ptr = fz_stext_page_as_text(0, stext_handle);
                if !text_ptr.is_null() {
                    if let Ok(text) = unsafe { CStr::from_ptr(text_ptr) }.to_str() {
                        if !all_text.is_empty() && !text.is_empty() {
                            all_text.push('\n');
                        }
                        all_text.push_str(text);
                    }
                    // Note: Do NOT free text_ptr - it's owned by a thread-local in fz_stext_page_as_text
                }
                fz_drop_stext_page(0, stext_handle);
            }

            fz_drop_page(0, page_handle);
            pages_processed += 1;
        }

        fz_drop_document(0, doc_handle);

        let text_len = all_text.len();
        let text_ptr = match CString::new(all_text) {
            Ok(cs) => cs.into_raw(),
            Err(_) => ptr::null_mut(),
        };

        unsafe {
            (*result_out).text = text_ptr;
            (*result_out).text_len = text_len;
            (*result_out).pages_processed = pages_processed;
        }

        0
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_extract_text: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_extract_text: panic: {}", s)
            } else {
                "mp_extract_text: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Extract text from a specific page of a PDF file.
///
/// # Parameters
/// - `pdf_path`: Path to the PDF file
/// - `page_num`: Zero-based page number
///
/// # Returns
/// - Pointer to null-terminated text string on success (must be freed with mp_free_string)
/// - NULL if extraction fails
#[unsafe(no_mangle)]
pub extern "C" fn mp_extract_page_text(pdf_path: *const c_char, page_num: i32) -> *mut c_char {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return ptr::null_mut();
        }

        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return ptr::null_mut();
        }

        let page_count = fz_count_pages(0, doc_handle);
        if page_num < 0 || page_num >= page_count {
            fz_drop_document(0, doc_handle);
            return ptr::null_mut();
        }

        let page_handle = fz_load_page(0, doc_handle, page_num);
        if page_handle == 0 {
            fz_drop_document(0, doc_handle);
            return ptr::null_mut();
        }

        let mut result: *mut c_char = ptr::null_mut();

        let stext_handle = fz_new_stext_page_from_page(0, page_handle, ptr::null());
        if stext_handle != 0 {
            let text_ptr = fz_stext_page_as_text(0, stext_handle);
            if !text_ptr.is_null() {
                // Copy the string - the original is owned by a thread-local in stext module
                if let Ok(text) = unsafe { CStr::from_ptr(text_ptr) }.to_str() {
                    if let Ok(cs) = CString::new(text) {
                        result = cs.into_raw();
                    }
                }
                // Note: Do NOT free text_ptr - it's owned by a thread-local in fz_stext_page_as_text
            }
            fz_drop_stext_page(0, stext_handle);
        }

        fz_drop_page(0, page_handle);
        fz_drop_document(0, doc_handle);

        result
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_extract_page_text: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_extract_page_text: panic: {}", s)
            } else {
                "mp_extract_page_text: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            ptr::null_mut()
        }
    }
}

// ============================================================================
// Page Rendering Functions
// ============================================================================

/// Render a page to PNG image data.
///
/// # Parameters
/// - `pdf_path`: Path to the PDF file
/// - `page_num`: Zero-based page number
/// - `scale`: Scale factor (1.0 = 72 DPI, 2.0 = 144 DPI, etc.)
/// - `result_out`: Pointer to MpRenderedPage to fill
///
/// # Returns
/// - `0` on success
/// - `-1` if pdf_path is null
/// - `-2` if result_out is null
/// - `-3` if file cannot be opened
/// - `-4` if page number is out of range
/// - `-5` if rendering fails
///
/// # Memory
/// The caller must free `result_out->data` using `mp_free_bytes()`.
#[unsafe(no_mangle)]
pub extern "C" fn mp_render_page_to_png(
    pdf_path: *const c_char,
    page_num: i32,
    scale: f32,
    result_out: *mut MpRenderedPage,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }
        if result_out.is_null() {
            return -2;
        }

        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return -3;
        }

        let page_count = fz_count_pages(0, doc_handle);
        if page_num < 0 || page_num >= page_count {
            fz_drop_document(0, doc_handle);
            return -4;
        }

        let page_handle = fz_load_page(0, doc_handle, page_num);
        if page_handle == 0 {
            fz_drop_document(0, doc_handle);
            return -4;
        }

        // Create transformation matrix with scale
        let ctm = super::geometry::fz_matrix {
            a: scale,
            b: 0.0,
            c: 0.0,
            d: scale,
            e: 0.0,
            f: 0.0,
        };

        // Render to pixmap
        let pix_handle = fz_new_pixmap_from_page(0, page_handle, ctm, FZ_COLORSPACE_RGB, 0);
        if pix_handle == 0 {
            fz_drop_page(0, page_handle);
            fz_drop_document(0, doc_handle);
            return -5;
        }

        let width = fz_pixmap_width(0, pix_handle);
        let height = fz_pixmap_height(0, pix_handle);

        // Convert to PNG buffer
        let buf_handle = fz_new_buffer_from_pixmap_as_png(0, pix_handle);
        fz_drop_pixmap(0, pix_handle);
        fz_drop_page(0, page_handle);
        fz_drop_document(0, doc_handle);

        if buf_handle == 0 {
            return -5;
        }

        // Get buffer data
        let mut len: usize = 0;
        let data_ptr = fz_buffer_data(0, buf_handle, &mut len);

        if data_ptr.is_null() || len == 0 {
            fz_drop_buffer(0, buf_handle);
            return -5;
        }

        // Copy data to output (since we'll drop the buffer)
        let mut data = vec![0u8; len];
        unsafe {
            ptr::copy_nonoverlapping(data_ptr, data.as_mut_ptr(), len);
        }

        fz_drop_buffer(0, buf_handle);

        let data_ptr = Box::into_raw(data.into_boxed_slice()) as *mut u8;

        unsafe {
            (*result_out).data = data_ptr;
            (*result_out).data_len = len;
            (*result_out).width = width;
            (*result_out).height = height;
        }

        0
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_render_page_to_png: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_render_page_to_png: panic: {}", s)
            } else {
                "mp_render_page_to_png: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Render a page to raw RGB pixel data.
///
/// # Parameters
/// - `pdf_path`: Path to the PDF file
/// - `page_num`: Zero-based page number
/// - `scale`: Scale factor (1.0 = 72 DPI)
/// - `result_out`: Pointer to MpRenderedPage to fill (data will be raw RGB)
///
/// # Returns
/// - `0` on success
/// - Negative error codes on failure
///
/// # Memory
/// The caller must free `result_out->data` using `mp_free_bytes()`.
#[unsafe(no_mangle)]
pub extern "C" fn mp_render_page_to_rgb(
    pdf_path: *const c_char,
    page_num: i32,
    scale: f32,
    result_out: *mut MpRenderedPage,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }
        if result_out.is_null() {
            return -2;
        }

        let doc_handle = fz_open_document(0, pdf_path);
        if doc_handle == 0 {
            return -3;
        }

        let page_count = fz_count_pages(0, doc_handle);
        if page_num < 0 || page_num >= page_count {
            fz_drop_document(0, doc_handle);
            return -4;
        }

        let page_handle = fz_load_page(0, doc_handle, page_num);
        if page_handle == 0 {
            fz_drop_document(0, doc_handle);
            return -4;
        }

        let ctm = super::geometry::fz_matrix {
            a: scale,
            b: 0.0,
            c: 0.0,
            d: scale,
            e: 0.0,
            f: 0.0,
        };

        let pix_handle = fz_new_pixmap_from_page(0, page_handle, ctm, FZ_COLORSPACE_RGB, 0);
        if pix_handle == 0 {
            fz_drop_page(0, page_handle);
            fz_drop_document(0, doc_handle);
            return -5;
        }

        let width = fz_pixmap_width(0, pix_handle);
        let height = fz_pixmap_height(0, pix_handle);

        // Get raw samples
        let samples_ptr = fz_pixmap_samples(0, pix_handle);
        let samples_size = fz_pixmap_samples_size(0, pix_handle);

        let data_ptr = if !samples_ptr.is_null() && samples_size > 0 {
            // Copy samples to new buffer
            let mut data = vec![0u8; samples_size];
            unsafe {
                ptr::copy_nonoverlapping(samples_ptr, data.as_mut_ptr(), samples_size);
            }
            Box::into_raw(data.into_boxed_slice()) as *mut u8
        } else {
            ptr::null_mut()
        };

        fz_drop_pixmap(0, pix_handle);
        fz_drop_page(0, page_handle);
        fz_drop_document(0, doc_handle);

        if data_ptr.is_null() {
            return -5;
        }

        unsafe {
            (*result_out).data = data_ptr;
            (*result_out).data_len = samples_size;
            (*result_out).width = width;
            (*result_out).height = height;
        }

        0
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_render_page_to_rgb: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_render_page_to_rgb: panic: {}", s)
            } else {
                "mp_render_page_to_rgb: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Free a rendered page's data buffer.
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_rendered_page(result: *mut MpRenderedPage) {
    if result.is_null() {
        return;
    }

    unsafe {
        if !(*result).data.is_null() && (*result).data_len > 0 {
            // Reconstruct the boxed slice and let it drop
            let slice_ptr = ptr::slice_from_raw_parts_mut((*result).data, (*result).data_len);
            let _ = Box::from_raw(slice_ptr);
            (*result).data = ptr::null_mut();
            (*result).data_len = 0;
        }
    }
}

// ============================================================================
// File Operations
// ============================================================================

/// Merge multiple PDF files into one.
///
/// Simple wrapper around mp_merge_pdfs that takes an array of paths.
///
/// # Parameters
/// - `input_paths`: Array of null-terminated path strings
/// - `input_count`: Number of paths in the array
/// - `output_path`: Path for the output merged PDF
///
/// # Returns
/// - `0` on success
/// - Negative error code on failure
#[unsafe(no_mangle)]
pub extern "C" fn mp_merge_pdf_files(
    input_paths: *const *const c_char,
    input_count: i32,
    output_path: *const c_char,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if input_paths.is_null() || output_path.is_null() || input_count <= 0 {
            return -1;
        }

        super::enhanced::mp_merge_pdfs(0, input_paths, input_count, output_path)
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_merge_pdf_files: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_merge_pdf_files: panic: {}", s)
            } else {
                "mp_merge_pdf_files: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Split a PDF into individual page files.
///
/// Creates files named page_001.pdf, page_002.pdf, etc. in the output directory.
///
/// # Parameters
/// - `pdf_path`: Path to the input PDF
/// - `output_dir`: Directory to write individual page PDFs
///
/// # Returns
/// - Number of pages created on success
/// - Negative error code on failure
#[unsafe(no_mangle)]
pub extern "C" fn mp_split_pdf_to_pages(pdf_path: *const c_char, output_dir: *const c_char) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() || output_dir.is_null() {
            return -1;
        }

        super::enhanced::mp_split_pdf(0, pdf_path, output_dir)
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_split_pdf_to_pages: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_split_pdf_to_pages: panic: {}", s)
            } else {
                "mp_split_pdf_to_pages: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Copy specific pages from a PDF to a new file.
///
/// # Parameters
/// - `pdf_path`: Path to the input PDF
/// - `output_path`: Path for the output PDF
/// - `page_numbers`: Array of zero-based page numbers to copy
/// - `page_count`: Number of pages to copy
///
/// # Returns
/// - `0` on success
/// - Negative error code on failure
#[unsafe(no_mangle)]
pub extern "C" fn mp_copy_pages(
    pdf_path: *const c_char,
    output_path: *const c_char,
    page_numbers: *const i32,
    page_count: i32,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() || output_path.is_null() || page_numbers.is_null() || page_count <= 0
        {
            return -1;
        }

        // Get input path
        let input_path = match c_str_to_string(pdf_path) {
            Some(s) => s,
            None => return -2,
        };

        let out_path = match c_str_to_string(output_path) {
            Some(s) => s,
            None => return -2,
        };

        // Read the original PDF
        let data = match std::fs::read(&input_path) {
            Ok(d) => d,
            Err(_) => return -3,
        };

        // Get page numbers array
        let pages: Vec<i32> =
            unsafe { std::slice::from_raw_parts(page_numbers, page_count as usize).to_vec() };

        // Optimization: copy the whole file if selecting all pages in order.
        // Otherwise, extract individual pages and merge them.
        let total_pages = {
            let doc_handle = fz_open_document(0, pdf_path);
            if doc_handle == 0 {
                return -3;
            }
            let count = fz_count_pages(0, doc_handle);
            fz_drop_document(0, doc_handle);
            count
        };

        // Validate page numbers
        for &page in &pages {
            if page < 0 || page >= total_pages {
                return -4;
            }
        }

        // If copying all pages in order, just copy the file
        if pages.len() == total_pages as usize {
            let mut all_in_order = true;
            for (i, &page) in pages.iter().enumerate() {
                if page != i as i32 {
                    all_in_order = false;
                    break;
                }
            }
            if all_in_order {
                return match std::fs::write(&out_path, &data) {
                    Ok(_) => 0,
                    Err(_) => -5,
                };
            }
        }

        // Extract individual pages using page_copy and merge them
        // Create temp files for each page, then merge
        let temp_dir = std::env::temp_dir();
        let mut temp_files = Vec::new();

        for (i, &page) in pages.iter().enumerate() {
            let temp_path = temp_dir
                .join(format!("micropdf_page_{}_{}.pdf", std::process::id(), i))
                .to_string_lossy()
                .to_string();

            if crate::enhanced::page_copy::copy_page_complete(
                &input_path,
                page as usize,
                &temp_path,
            )
            .is_err()
            {
                // Clean up temp files on failure
                for tf in &temp_files {
                    let _ = std::fs::remove_file(tf);
                }
                return -5;
            }
            temp_files.push(temp_path);
        }

        // Merge the individual page PDFs into the output
        let result = if temp_files.len() == 1 {
            // Single page - just copy it
            std::fs::copy(&temp_files[0], &out_path)
                .map(|_| 0)
                .unwrap_or(-5)
        } else {
            // Multiple pages - use merge
            let temp_strings: Vec<String> = temp_files.clone();
            match crate::enhanced::page_ops::merge_pdf(&temp_strings, &out_path) {
                Ok(_) => 0,
                Err(_) => -5,
            }
        };

        // Clean up temp files
        for tf in &temp_files {
            let _ = std::fs::remove_file(tf);
        }

        result
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_copy_pages: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_copy_pages: panic: {}", s)
            } else {
                "mp_copy_pages: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

// ============================================================================
// Validation and Repair
// ============================================================================

/// Quick validation check on a PDF file.
///
/// # Returns
/// - `1` if PDF appears valid
/// - `0` if PDF appears invalid
/// - Negative error code on failure
#[unsafe(no_mangle)]
pub extern "C" fn mp_is_valid_pdf(pdf_path: *const c_char) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() {
            return -1;
        }

        unsafe { mp_quick_validate(pdf_path) as i32 }
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_is_valid_pdf: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_is_valid_pdf: panic: {}", s)
            } else {
                "mp_is_valid_pdf: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

/// Attempt to repair a damaged PDF.
///
/// # Returns
/// - `0` on success
/// - Negative error code on failure
#[unsafe(no_mangle)]
pub extern "C" fn mp_repair_damaged_pdf(
    pdf_path: *const c_char,
    output_path: *const c_char,
) -> i32 {
    match catch_unwind(AssertUnwindSafe(|| {
        if pdf_path.is_null() || output_path.is_null() {
            return -1;
        }

        unsafe { mp_repair_pdf(pdf_path, output_path) as i32 }
    })) {
        Ok(result) => result,
        Err(panic_info) => {
            let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
                format!("mp_repair_damaged_pdf: panic: {}", s)
            } else if let Some(s) = panic_info.downcast_ref::<String>() {
                format!("mp_repair_damaged_pdf: panic: {}", s)
            } else {
                "mp_repair_damaged_pdf: panic (unknown payload)".to_string()
            };
            eprintln!("{}", msg);
            -1
        }
    }
}

// ============================================================================
// Memory Management
// ============================================================================

/// Free a byte buffer allocated by convenience functions.
///
/// Use this to free data returned by mp_render_page_to_png, etc.
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_bytes(data: *mut u8, len: usize) {
    if !data.is_null() && len > 0 {
        unsafe {
            // Reconstruct the boxed slice and let it drop
            let slice_ptr = ptr::slice_from_raw_parts_mut(data, len);
            let _ = Box::from_raw(slice_ptr);
        }
    }
}

/// Free extracted text result.
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_extracted_text(result: *mut MpExtractedText) {
    if result.is_null() {
        return;
    }

    unsafe {
        if !(*result).text.is_null() {
            let _ = CString::from_raw((*result).text);
            (*result).text = ptr::null_mut();
            (*result).text_len = 0;
        }
    }
}

// ============================================================================
// Helper Functions
// ============================================================================

/// Convert an Option<String> to a C string pointer.
fn string_to_c(s: Option<String>) -> *mut c_char {
    match s {
        Some(string) => match CString::new(string) {
            Ok(cs) => cs.into_raw(),
            Err(_) => ptr::null_mut(),
        },
        None => ptr::null_mut(),
    }
}

/// Convert a C string to a Rust String.
fn c_str_to_string(ptr: *const c_char) -> Option<String> {
    if ptr.is_null() {
        return None;
    }
    unsafe { CStr::from_ptr(ptr).to_str().ok().map(|s| s.to_string()) }
}

/// Extract PDF version from header.
fn extract_pdf_version(data: &[u8]) -> Option<String> {
    if data.len() < 8 || !data.starts_with(b"%PDF-") {
        return None;
    }

    // Find end of version string (typically ends with newline or whitespace)
    let start = 5;
    let mut end = start;
    while end < data.len() && end < 10 {
        let b = data[end];
        if b == b'\n' || b == b'\r' || b == b' ' {
            break;
        }
        end += 1;
    }

    std::str::from_utf8(&data[start..end])
        .ok()
        .map(|s| s.to_string())
}

/// Extract metadata value from PDF data.
fn extract_metadata(data: &[u8], key: &[u8]) -> Option<String> {
    // Simple pattern matching for PDF metadata
    // This looks for patterns like /Title (value) or /Title <hex>
    let key_len = key.len();

    for i in 0..data.len().saturating_sub(key_len + 2) {
        if &data[i..i + key_len] == key {
            let after_key = i + key_len;

            // Skip whitespace
            let mut start = after_key;
            while start < data.len() && (data[start] == b' ' || data[start] == b'\n') {
                start += 1;
            }

            if start >= data.len() {
                continue;
            }

            // Check for string delimiter
            if data[start] == b'(' {
                // Literal string
                start += 1;
                let mut end = start;
                let mut depth = 1;
                while end < data.len() && depth > 0 {
                    match data[end] {
                        b'(' => depth += 1,
                        b')' => depth -= 1,
                        b'\\' if end + 1 < data.len() => end += 1, // Skip escaped char
                        _ => {}
                    }
                    if depth > 0 {
                        end += 1;
                    }
                }
                if depth == 0 {
                    return std::str::from_utf8(&data[start..end])
                        .ok()
                        .map(|s| s.to_string());
                }
            } else if data[start] == b'<' && start + 1 < data.len() && data[start + 1] != b'<' {
                // Hex string
                start += 1;
                let mut end = start;
                while end < data.len() && data[end] != b'>' {
                    end += 1;
                }
                // Decode hex string
                let hex = &data[start..end];
                let mut bytes = Vec::new();
                for i in (0..hex.len()).step_by(2) {
                    if i + 1 < hex.len() {
                        if let (Some(h), Some(l)) = (hex_val(hex[i]), hex_val(hex[i + 1])) {
                            bytes.push((h << 4) | l);
                        }
                    }
                }
                return String::from_utf8(bytes).ok();
            }
        }
    }

    None
}

/// Convert hex character to value.
fn hex_val(b: u8) -> Option<u8> {
    match b {
        b'0'..=b'9' => Some(b - b'0'),
        b'a'..=b'f' => Some(b - b'a' + 10),
        b'A'..=b'F' => Some(b - b'A' + 10),
        _ => None,
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::io::Write;

    // ========================================================================
    // Test Helpers
    // ========================================================================

    /// Create a minimal valid PDF for testing
    fn create_test_pdf() -> Vec<u8> {
        // This PDF has correct xref offsets
        let pdf = b"%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /Resources 4 0 R /MediaBox [0 0 612 792] /Contents 5 0 R >>
endobj
4 0 obj
<< /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>
endobj
5 0 obj
<< /Length 55 >>
stream
BT
/F1 18 Tf
50 700 Td
(Hello, World!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000229 00000 n
0000000325 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
429
%%EOF
";
        pdf.to_vec()
    }

    /// Create a test PDF file and return its path
    fn create_test_pdf_file() -> String {
        use std::sync::atomic::{AtomicU64, Ordering};
        static COUNTER: AtomicU64 = AtomicU64::new(0);
        let id = COUNTER.fetch_add(1, Ordering::SeqCst);
        let path = format!("/tmp/test_convenience_{}_{}.pdf", std::process::id(), id);
        let mut file = fs::File::create(&path).unwrap();
        file.write_all(&create_test_pdf()).unwrap();
        path
    }

    /// Create a multi-page test PDF
    fn create_multipage_pdf() -> Vec<u8> {
        let pdf = b"%PDF-1.4
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 3 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >> endobj
4 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] >> endobj
5 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 842 595] >> endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000131 00000 n
0000000210 00000 n
0000000289 00000 n
trailer << /Size 6 /Root 1 0 R >>
startxref
368
%%EOF";
        pdf.to_vec()
    }

    fn create_multipage_pdf_file() -> String {
        use std::sync::atomic::{AtomicU64, Ordering};
        static COUNTER: AtomicU64 = AtomicU64::new(0);
        let id = COUNTER.fetch_add(1, Ordering::SeqCst);
        let path = format!("/tmp/test_multipage_{}_{}.pdf", std::process::id(), id);
        let mut file = fs::File::create(&path).unwrap();
        file.write_all(&create_multipage_pdf()).unwrap();
        path
    }

    // ========================================================================
    // Helper Function Tests
    // ========================================================================

    #[test]
    fn test_pdf_version_extraction() {
        let data = b"%PDF-1.7\n%\xE2\xE3\xCF\xD3\n";
        let version = extract_pdf_version(data);
        assert_eq!(version, Some("1.7".to_string()));
    }

    #[test]
    fn test_pdf_version_extraction_14() {
        let data = b"%PDF-1.4\r\n";
        let version = extract_pdf_version(data);
        assert_eq!(version, Some("1.4".to_string()));
    }

    #[test]
    fn test_pdf_version_extraction_20() {
        let data = b"%PDF-2.0 ";
        let version = extract_pdf_version(data);
        assert_eq!(version, Some("2.0".to_string()));
    }

    #[test]
    fn test_pdf_version_extraction_invalid() {
        // Not a PDF
        let data = b"Not a PDF";
        let version = extract_pdf_version(data);
        assert_eq!(version, None);

        // Too short
        let data = b"%PDF";
        let version = extract_pdf_version(data);
        assert_eq!(version, None);

        // Empty
        let version = extract_pdf_version(b"");
        assert_eq!(version, None);
    }

    #[test]
    fn test_hex_val() {
        // Digits 0-9
        assert_eq!(hex_val(b'0'), Some(0));
        assert_eq!(hex_val(b'1'), Some(1));
        assert_eq!(hex_val(b'5'), Some(5));
        assert_eq!(hex_val(b'9'), Some(9));

        // Lowercase a-f
        assert_eq!(hex_val(b'a'), Some(10));
        assert_eq!(hex_val(b'b'), Some(11));
        assert_eq!(hex_val(b'c'), Some(12));
        assert_eq!(hex_val(b'd'), Some(13));
        assert_eq!(hex_val(b'e'), Some(14));
        assert_eq!(hex_val(b'f'), Some(15));

        // Uppercase A-F
        assert_eq!(hex_val(b'A'), Some(10));
        assert_eq!(hex_val(b'B'), Some(11));
        assert_eq!(hex_val(b'C'), Some(12));
        assert_eq!(hex_val(b'D'), Some(13));
        assert_eq!(hex_val(b'E'), Some(14));
        assert_eq!(hex_val(b'F'), Some(15));

        // Invalid characters
        assert_eq!(hex_val(b'g'), None);
        assert_eq!(hex_val(b'G'), None);
        assert_eq!(hex_val(b'z'), None);
        assert_eq!(hex_val(b' '), None);
        assert_eq!(hex_val(b'\n'), None);
    }

    #[test]
    fn test_metadata_extraction_literal() {
        let data = b"<< /Title (Hello World) /Author (Test) >>";
        let title = extract_metadata(data, b"/Title");
        assert_eq!(title, Some("Hello World".to_string()));

        let author = extract_metadata(data, b"/Author");
        assert_eq!(author, Some("Test".to_string()));
    }

    #[test]
    fn test_metadata_extraction_literal_nested_parens() {
        let data = b"<< /Title (Hello (World)) >>";
        let title = extract_metadata(data, b"/Title");
        assert_eq!(title, Some("Hello (World)".to_string()));
    }

    #[test]
    fn test_metadata_extraction_literal_escaped() {
        let data = b"<< /Title (Hello\\)World) >>";
        let title = extract_metadata(data, b"/Title");
        // Escaped paren should be handled
        assert!(title.is_some());
    }

    #[test]
    fn test_metadata_extraction_hex() {
        let data = b"<< /Title <48656C6C6F> >>";
        let title = extract_metadata(data, b"/Title");
        assert_eq!(title, Some("Hello".to_string()));
    }

    #[test]
    fn test_metadata_extraction_hex_lowercase() {
        let data = b"<< /Title <48656c6c6f> >>";
        let title = extract_metadata(data, b"/Title");
        assert_eq!(title, Some("Hello".to_string()));
    }

    #[test]
    fn test_metadata_extraction_not_found() {
        let data = b"<< /Title (Test) >>";
        let author = extract_metadata(data, b"/Author");
        assert_eq!(author, None);
    }

    #[test]
    fn test_metadata_extraction_empty_data() {
        let data = b"";
        let result = extract_metadata(data, b"/Title");
        assert_eq!(result, None);
    }

    #[test]
    fn test_string_to_c() {
        // Valid string
        let ptr = string_to_c(Some("Hello".to_string()));
        assert!(!ptr.is_null());
        unsafe {
            let cs = CStr::from_ptr(ptr);
            assert_eq!(cs.to_str().unwrap(), "Hello");
            let _ = CString::from_raw(ptr);
        }

        // None becomes null
        let ptr = string_to_c(None);
        assert!(ptr.is_null());

        // Empty string is valid
        let ptr = string_to_c(Some("".to_string()));
        assert!(!ptr.is_null());
        unsafe {
            let cs = CStr::from_ptr(ptr);
            assert_eq!(cs.to_str().unwrap(), "");
            let _ = CString::from_raw(ptr);
        }

        // String with unicode
        let ptr = string_to_c(Some("Hello 世界".to_string()));
        assert!(!ptr.is_null());
        unsafe {
            let cs = CStr::from_ptr(ptr);
            assert_eq!(cs.to_str().unwrap(), "Hello 世界");
            let _ = CString::from_raw(ptr);
        }
    }

    #[test]
    fn test_string_to_c_with_null_byte() {
        // String with embedded null - should fail
        let ptr = string_to_c(Some("Hello\0World".to_string()));
        assert!(ptr.is_null());
    }

    #[test]
    fn test_c_str_to_string() {
        let cs = CString::new("Hello").unwrap();
        let result = c_str_to_string(cs.as_ptr());
        assert_eq!(result, Some("Hello".to_string()));

        let result = c_str_to_string(ptr::null());
        assert_eq!(result, None);

        // Empty string
        let cs = CString::new("").unwrap();
        let result = c_str_to_string(cs.as_ptr());
        assert_eq!(result, Some("".to_string()));

        // Unicode
        let cs = CString::new("Hello 世界").unwrap();
        let result = c_str_to_string(cs.as_ptr());
        assert_eq!(result, Some("Hello 世界".to_string()));
    }

    // ========================================================================
    // Document Info Function Tests
    // ========================================================================

    #[test]
    fn test_mp_get_pdf_info_null_path() {
        let mut info = MpPdfInfo {
            page_count: 0,
            is_encrypted: 0,
            needs_password: 0,
            version: ptr::null_mut(),
            title: ptr::null_mut(),
            author: ptr::null_mut(),
            subject: ptr::null_mut(),
            creator: ptr::null_mut(),
        };
        let result = mp_get_pdf_info(ptr::null(), &mut info);
        assert_eq!(result, -1);
    }

    #[test]
    fn test_mp_get_pdf_info_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let result = mp_get_pdf_info(path.as_ptr(), ptr::null_mut());
        assert_eq!(result, -2);
    }

    #[test]
    fn test_mp_get_pdf_info_nonexistent_file() {
        let path = CString::new("/tmp/nonexistent_file_12345.pdf").unwrap();
        let mut info = MpPdfInfo {
            page_count: 0,
            is_encrypted: 0,
            needs_password: 0,
            version: ptr::null_mut(),
            title: ptr::null_mut(),
            author: ptr::null_mut(),
            subject: ptr::null_mut(),
            creator: ptr::null_mut(),
        };
        let result = mp_get_pdf_info(path.as_ptr(), &mut info);
        assert_eq!(result, -3);
    }

    #[test]
    fn test_mp_get_pdf_info_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut info = MpPdfInfo {
            page_count: 0,
            is_encrypted: 0,
            needs_password: 0,
            version: ptr::null_mut(),
            title: ptr::null_mut(),
            author: ptr::null_mut(),
            subject: ptr::null_mut(),
            creator: ptr::null_mut(),
        };

        let result = mp_get_pdf_info(path.as_ptr(), &mut info);
        assert_eq!(result, 0);
        assert_eq!(info.page_count, 1);
        assert_eq!(info.is_encrypted, 0);

        // Check version
        if !info.version.is_null() {
            let version = unsafe { CStr::from_ptr(info.version).to_str().unwrap() };
            assert_eq!(version, "1.4");
        }

        // Clean up
        mp_free_pdf_info(&mut info);
        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_free_pdf_info_null() {
        // Should not crash
        mp_free_pdf_info(ptr::null_mut());
    }

    #[test]
    fn test_mp_free_pdf_info_empty() {
        let mut info = MpPdfInfo {
            page_count: 0,
            is_encrypted: 0,
            needs_password: 0,
            version: ptr::null_mut(),
            title: ptr::null_mut(),
            author: ptr::null_mut(),
            subject: ptr::null_mut(),
            creator: ptr::null_mut(),
        };
        // Should not crash with all null pointers
        mp_free_pdf_info(&mut info);
    }

    #[test]
    fn test_mp_free_pdf_info_with_data() {
        let mut info = MpPdfInfo {
            page_count: 1,
            is_encrypted: 0,
            needs_password: 0,
            version: CString::new("1.4").unwrap().into_raw(),
            title: CString::new("Title").unwrap().into_raw(),
            author: CString::new("Author").unwrap().into_raw(),
            subject: ptr::null_mut(),
            creator: ptr::null_mut(),
        };

        mp_free_pdf_info(&mut info);

        // All pointers should be null after free
        assert!(info.version.is_null());
        assert!(info.title.is_null());
        assert!(info.author.is_null());
    }

    #[test]
    fn test_mp_get_page_count_null_path() {
        let result = mp_get_page_count(ptr::null());
        assert_eq!(result, -1);
    }

    #[test]
    fn test_mp_get_page_count_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let result = mp_get_page_count(path.as_ptr());
        assert_eq!(result, -3);
    }

    #[test]
    fn test_mp_get_page_count_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();

        let result = mp_get_page_count(path.as_ptr());
        assert_eq!(result, 1);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_get_page_count_multipage() {
        let pdf_path = create_multipage_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();

        let result = mp_get_page_count(path.as_ptr());
        assert_eq!(result, 3);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_get_page_dimensions_null_path() {
        let mut dims = MpPageDimensions {
            width: 0.0,
            height: 0.0,
        };
        let result = mp_get_page_dimensions(ptr::null(), 0, &mut dims);
        assert_eq!(result, -1);
    }

    #[test]
    fn test_mp_get_page_dimensions_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let result = mp_get_page_dimensions(path.as_ptr(), 0, ptr::null_mut());
        assert_eq!(result, -2);
    }

    #[test]
    fn test_mp_get_page_dimensions_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let mut dims = MpPageDimensions {
            width: 0.0,
            height: 0.0,
        };
        let result = mp_get_page_dimensions(path.as_ptr(), 0, &mut dims);
        assert_eq!(result, -3);
    }

    #[test]
    fn test_mp_get_page_dimensions_invalid_page() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut dims = MpPageDimensions {
            width: 0.0,
            height: 0.0,
        };

        // Page -1 is invalid
        let result = mp_get_page_dimensions(path.as_ptr(), -1, &mut dims);
        assert_eq!(result, -4);

        // Page 100 is out of range
        let result = mp_get_page_dimensions(path.as_ptr(), 100, &mut dims);
        assert_eq!(result, -4);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_get_page_dimensions_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut dims = MpPageDimensions {
            width: 0.0,
            height: 0.0,
        };

        let result = mp_get_page_dimensions(path.as_ptr(), 0, &mut dims);
        assert_eq!(result, 0);
        // Letter size: 612 x 792 points
        assert!((dims.width - 612.0).abs() < 1.0);
        assert!((dims.height - 792.0).abs() < 1.0);

        let _ = fs::remove_file(&pdf_path);
    }

    // ========================================================================
    // Text Extraction Function Tests
    // ========================================================================

    #[test]
    fn test_mp_extract_text_null_path() {
        let mut result = MpExtractedText {
            text: ptr::null_mut(),
            text_len: 0,
            pages_processed: 0,
        };
        let ret = mp_extract_text(ptr::null(), &mut result);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_extract_text_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let ret = mp_extract_text(path.as_ptr(), ptr::null_mut());
        assert_eq!(ret, -2);
    }

    #[test]
    fn test_mp_extract_text_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let mut result = MpExtractedText {
            text: ptr::null_mut(),
            text_len: 0,
            pages_processed: 0,
        };
        let ret = mp_extract_text(path.as_ptr(), &mut result);
        assert_eq!(ret, -3);
    }

    #[test]
    fn test_mp_extract_text_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut result = MpExtractedText {
            text: ptr::null_mut(),
            text_len: 0,
            pages_processed: 0,
        };

        let ret = mp_extract_text(path.as_ptr(), &mut result);
        assert_eq!(ret, 0);
        assert_eq!(result.pages_processed, 1);

        mp_free_extracted_text(&mut result);
        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_extract_page_text_null_path() {
        let result = mp_extract_page_text(ptr::null(), 0);
        assert!(result.is_null());
    }

    #[test]
    fn test_mp_extract_page_text_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let result = mp_extract_page_text(path.as_ptr(), 0);
        assert!(result.is_null());
    }

    #[test]
    fn test_mp_extract_page_text_invalid_page() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();

        // Page -1 is invalid
        let result = mp_extract_page_text(path.as_ptr(), -1);
        assert!(result.is_null());

        // Page 100 is out of range
        let result = mp_extract_page_text(path.as_ptr(), 100);
        assert!(result.is_null());

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_extract_page_text_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();

        let result = mp_extract_page_text(path.as_ptr(), 0);
        // May or may not extract text depending on font support
        if !result.is_null() {
            unsafe {
                let _ = CString::from_raw(result);
            }
        }

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_free_extracted_text_null() {
        // Should not crash
        mp_free_extracted_text(ptr::null_mut());
    }

    #[test]
    fn test_mp_free_extracted_text_empty() {
        let mut result = MpExtractedText {
            text: ptr::null_mut(),
            text_len: 0,
            pages_processed: 0,
        };
        // Should not crash with null text
        mp_free_extracted_text(&mut result);
    }

    #[test]
    fn test_mp_free_extracted_text_with_data() {
        let mut result = MpExtractedText {
            text: CString::new("Test text").unwrap().into_raw(),
            text_len: 9,
            pages_processed: 1,
        };

        mp_free_extracted_text(&mut result);
        assert!(result.text.is_null());
        assert_eq!(result.text_len, 0);
    }

    // ========================================================================
    // Page Rendering Function Tests
    // ========================================================================

    #[test]
    fn test_mp_render_page_to_png_null_path() {
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };
        let ret = mp_render_page_to_png(ptr::null(), 0, 1.0, &mut result);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_render_page_to_png_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let ret = mp_render_page_to_png(path.as_ptr(), 0, 1.0, ptr::null_mut());
        assert_eq!(ret, -2);
    }

    #[test]
    fn test_mp_render_page_to_png_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };
        let ret = mp_render_page_to_png(path.as_ptr(), 0, 1.0, &mut result);
        assert_eq!(ret, -3);
    }

    #[test]
    fn test_mp_render_page_to_png_invalid_page() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };

        // Page -1 is invalid
        let ret = mp_render_page_to_png(path.as_ptr(), -1, 1.0, &mut result);
        assert_eq!(ret, -4);

        // Page 100 is out of range
        let ret = mp_render_page_to_png(path.as_ptr(), 100, 1.0, &mut result);
        assert_eq!(ret, -4);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_render_page_to_png_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };

        let ret = mp_render_page_to_png(path.as_ptr(), 0, 1.0, &mut result);
        // Rendering may fail if font support is limited, so we accept both success and failure
        if ret == 0 {
            assert!(!result.data.is_null());
            assert!(result.data_len > 0);
            assert!(result.width > 0);
            assert!(result.height > 0);
            mp_free_rendered_page(&mut result);
        }

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_render_page_to_rgb_null_path() {
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };
        let ret = mp_render_page_to_rgb(ptr::null(), 0, 1.0, &mut result);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_render_page_to_rgb_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let ret = mp_render_page_to_rgb(path.as_ptr(), 0, 1.0, ptr::null_mut());
        assert_eq!(ret, -2);
    }

    #[test]
    fn test_mp_render_page_to_rgb_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };
        let ret = mp_render_page_to_rgb(path.as_ptr(), 0, 1.0, &mut result);
        assert_eq!(ret, -3);
    }

    #[test]
    fn test_mp_render_page_to_rgb_invalid_page() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };

        // Page -1 is invalid
        let ret = mp_render_page_to_rgb(path.as_ptr(), -1, 1.0, &mut result);
        assert_eq!(ret, -4);

        // Page 100 is out of range
        let ret = mp_render_page_to_rgb(path.as_ptr(), 100, 1.0, &mut result);
        assert_eq!(ret, -4);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_render_page_to_rgb_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };

        let ret = mp_render_page_to_rgb(path.as_ptr(), 0, 1.0, &mut result);
        // Rendering may fail if font support is limited, so we accept both success and failure
        if ret == 0 {
            assert!(!result.data.is_null());
            assert!(result.data_len > 0);
            assert!(result.width > 0);
            assert!(result.height > 0);
            mp_free_rendered_page(&mut result);
        }

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_free_rendered_page_null() {
        // Should not crash
        mp_free_rendered_page(ptr::null_mut());
    }

    #[test]
    fn test_mp_free_rendered_page_empty() {
        let mut result = MpRenderedPage {
            data: ptr::null_mut(),
            data_len: 0,
            width: 0,
            height: 0,
        };
        // Should not crash with null data
        mp_free_rendered_page(&mut result);
    }

    #[test]
    fn test_mp_free_rendered_page_with_data() {
        let data = vec![1u8, 2, 3, 4, 5];
        let len = data.len();
        let mut result = MpRenderedPage {
            data: Box::into_raw(data.into_boxed_slice()) as *mut u8,
            data_len: len,
            width: 100,
            height: 100,
        };

        mp_free_rendered_page(&mut result);
        assert!(result.data.is_null());
        assert_eq!(result.data_len, 0);
    }

    // ========================================================================
    // File Operation Function Tests
    // ========================================================================

    #[test]
    fn test_mp_merge_pdf_files_null_paths() {
        let output = CString::new("/tmp/output.pdf").unwrap();
        let ret = mp_merge_pdf_files(ptr::null(), 2, output.as_ptr());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_merge_pdf_files_null_output() {
        let path1 = CString::new("/tmp/test1.pdf").unwrap();
        let paths: [*const c_char; 1] = [path1.as_ptr()];
        let ret = mp_merge_pdf_files(paths.as_ptr(), 1, ptr::null());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_merge_pdf_files_zero_count() {
        let output = CString::new("/tmp/output.pdf").unwrap();
        let path1 = CString::new("/tmp/test1.pdf").unwrap();
        let paths: [*const c_char; 1] = [path1.as_ptr()];
        let ret = mp_merge_pdf_files(paths.as_ptr(), 0, output.as_ptr());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_merge_pdf_files_negative_count() {
        let output = CString::new("/tmp/output.pdf").unwrap();
        let path1 = CString::new("/tmp/test1.pdf").unwrap();
        let paths: [*const c_char; 1] = [path1.as_ptr()];
        let ret = mp_merge_pdf_files(paths.as_ptr(), -1, output.as_ptr());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_merge_pdf_files_nonexistent() {
        let path1 = CString::new("/tmp/nonexistent_merge_1.pdf").unwrap();
        let path2 = CString::new("/tmp/nonexistent_merge_2.pdf").unwrap();
        let paths: [*const c_char; 2] = [path1.as_ptr(), path2.as_ptr()];
        let output = CString::new("/tmp/merge_output_test.pdf").unwrap();
        let ret = mp_merge_pdf_files(paths.as_ptr(), 2, output.as_ptr());
        // Should fail since input files don't exist
        assert!(ret < 0);
    }

    #[test]
    fn test_mp_merge_pdf_files_valid() {
        let pdf_path1 = create_test_pdf_file();
        let pdf_path2 = create_test_pdf_file();
        let output_path = format!("/tmp/merge_output_{}.pdf", std::process::id());

        let path1 = CString::new(pdf_path1.clone()).unwrap();
        let path2 = CString::new(pdf_path2.clone()).unwrap();
        let output = CString::new(output_path.clone()).unwrap();

        let paths: [*const c_char; 2] = [path1.as_ptr(), path2.as_ptr()];
        let ret = mp_merge_pdf_files(paths.as_ptr(), 2, output.as_ptr());
        // Merge may succeed (0 or positive) or fail (negative)
        // Just verify it doesn't crash
        // Positive values may indicate number of pages merged
        let _ = ret; // Use the value to avoid warning

        let _ = fs::remove_file(&pdf_path1);
        let _ = fs::remove_file(&pdf_path2);
        let _ = fs::remove_file(&output_path);
    }

    #[test]
    fn test_mp_split_pdf_to_pages_null_path() {
        let output = CString::new("/tmp/output_dir").unwrap();
        let ret = mp_split_pdf_to_pages(ptr::null(), output.as_ptr());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_split_pdf_to_pages_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let ret = mp_split_pdf_to_pages(path.as_ptr(), ptr::null());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_split_pdf_to_pages_nonexistent() {
        let path = CString::new("/tmp/nonexistent_split_12345.pdf").unwrap();
        let output = CString::new("/tmp/split_output_dir").unwrap();
        let ret = mp_split_pdf_to_pages(path.as_ptr(), output.as_ptr());
        assert!(ret < 0); // Should fail
    }

    #[test]
    fn test_mp_split_pdf_to_pages_valid() {
        let pdf_path = create_multipage_pdf_file();
        let output_dir = format!("/tmp/split_output_{}", std::process::id());

        // Create output directory
        let _ = fs::create_dir_all(&output_dir);

        let path = CString::new(pdf_path.clone()).unwrap();
        let output = CString::new(output_dir.clone()).unwrap();

        let ret = mp_split_pdf_to_pages(path.as_ptr(), output.as_ptr());
        // Split may succeed (returning page count) or fail
        // Just verify it doesn't crash
        if ret > 0 {
            assert_eq!(ret, 3); // Should split into 3 pages
        }

        // Clean up
        let _ = fs::remove_file(&pdf_path);
        let _ = fs::remove_dir_all(&output_dir);
    }

    #[test]
    fn test_mp_copy_pages_null_path() {
        let output = CString::new("/tmp/output.pdf").unwrap();
        let pages = [0, 1];
        let ret = mp_copy_pages(ptr::null(), output.as_ptr(), pages.as_ptr(), 2);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_copy_pages_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let pages = [0, 1];
        let ret = mp_copy_pages(path.as_ptr(), ptr::null(), pages.as_ptr(), 2);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_copy_pages_null_pages() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let output = CString::new("/tmp/output.pdf").unwrap();
        let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), ptr::null(), 2);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_copy_pages_zero_count() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let output = CString::new("/tmp/output.pdf").unwrap();
        let pages = [0, 1];
        let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 0);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_copy_pages_negative_count() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let output = CString::new("/tmp/output.pdf").unwrap();
        let pages = [0, 1];
        let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), -1);
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_copy_pages_invalid_page_number() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let output_path = format!("/tmp/output_copy_{}.pdf", std::process::id());
        let output = CString::new(output_path.clone()).unwrap();
        let pages = [-1]; // Invalid page number

        let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 1);
        assert_eq!(ret, -4);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_copy_pages_out_of_range_page() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let output_path = format!("/tmp/output_copy_{}.pdf", std::process::id());
        let output = CString::new(output_path.clone()).unwrap();
        let pages = [100]; // Out of range

        let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 1);
        assert_eq!(ret, -4);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_copy_pages_valid_all_pages() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();
        let output_path = format!("/tmp/output_copy_all_{}.pdf", std::process::id());
        let output = CString::new(output_path.clone()).unwrap();
        let pages = [0]; // All pages in order

        let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 1);
        assert_eq!(ret, 0);

        // Verify output file exists
        assert!(fs::metadata(&output_path).is_ok());

        let _ = fs::remove_file(&pdf_path);
        let _ = fs::remove_file(&output_path);
    }

    // ========================================================================
    // Validation Function Tests
    // ========================================================================

    #[test]
    fn test_mp_is_valid_pdf_null_path() {
        let ret = mp_is_valid_pdf(ptr::null());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_is_valid_pdf_nonexistent() {
        let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
        let ret = mp_is_valid_pdf(path.as_ptr());
        // Should return 0 (invalid) or negative error
        assert!(ret <= 0);
    }

    #[test]
    fn test_mp_is_valid_pdf_valid() {
        let pdf_path = create_test_pdf_file();
        let path = CString::new(pdf_path.clone()).unwrap();

        let ret = mp_is_valid_pdf(path.as_ptr());
        assert_eq!(ret, 1);

        let _ = fs::remove_file(&pdf_path);
    }

    #[test]
    fn test_mp_is_valid_pdf_invalid_content() {
        // Create a file with non-PDF content
        let path_str = format!("/tmp/not_a_pdf_{}.pdf", std::process::id());
        let mut file = fs::File::create(&path_str).unwrap();
        file.write_all(b"This is not a PDF file").unwrap();

        let path = CString::new(path_str.clone()).unwrap();
        let ret = mp_is_valid_pdf(path.as_ptr());
        // Should return 0 (invalid)
        assert_eq!(ret, 0);

        let _ = fs::remove_file(&path_str);
    }

    #[test]
    fn test_mp_repair_damaged_pdf_null_path() {
        let output = CString::new("/tmp/output.pdf").unwrap();
        let ret = mp_repair_damaged_pdf(ptr::null(), output.as_ptr());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_repair_damaged_pdf_null_output() {
        let path = CString::new("/tmp/test.pdf").unwrap();
        let ret = mp_repair_damaged_pdf(path.as_ptr(), ptr::null());
        assert_eq!(ret, -1);
    }

    #[test]
    fn test_mp_repair_damaged_pdf_nonexistent() {
        let path = CString::new("/tmp/nonexistent_repair_12345.pdf").unwrap();
        let output = CString::new("/tmp/repair_output.pdf").unwrap();
        let ret = mp_repair_damaged_pdf(path.as_ptr(), output.as_ptr());
        // Should fail since input file doesn't exist
        assert!(ret < 0);
    }

    #[test]
    fn test_mp_repair_damaged_pdf_valid() {
        let pdf_path = create_test_pdf_file();
        let output_path = format!("/tmp/repair_output_{}.pdf", std::process::id());

        let path = CString::new(pdf_path.clone()).unwrap();
        let output = CString::new(output_path.clone()).unwrap();

        let ret = mp_repair_damaged_pdf(path.as_ptr(), output.as_ptr());
        // Repair may succeed (0) or fail (negative)
        // Just verify it doesn't crash
        assert!(ret <= 0);

        let _ = fs::remove_file(&pdf_path);
        let _ = fs::remove_file(&output_path);
    }

    // ========================================================================
    // Memory Management Function Tests
    // ========================================================================

    #[test]
    fn test_mp_free_bytes_null() {
        // Should not crash
        mp_free_bytes(ptr::null_mut(), 0);
    }

    #[test]
    fn test_mp_free_bytes_zero_len() {
        // Should not crash even with non-null pointer but zero length
        let data = vec![1u8, 2, 3];
        let ptr = Box::into_raw(data.into_boxed_slice()) as *mut u8;
        // This is technically a leak, but we're testing the zero length case
        mp_free_bytes(ptr, 0);
        // Clean up the actual allocation using the correct method for boxed slices
        unsafe {
            let slice_ptr = ptr::slice_from_raw_parts_mut(ptr, 3);
            let _ = Box::from_raw(slice_ptr);
        }
    }

    #[test]
    fn test_mp_free_bytes_valid() {
        let data = vec![1u8, 2, 3, 4, 5];
        let len = data.len();
        let ptr = Box::into_raw(data.into_boxed_slice()) as *mut u8;
        // Should free without crash
        mp_free_bytes(ptr, len);
    }

    // ========================================================================
    // Edge Case Tests
    // ========================================================================

    #[test]
    fn test_metadata_with_multiple_keys() {
        let data = b"<< /Title (First) >> << /Title (Second) >>";
        let title = extract_metadata(data, b"/Title");
        // Should return the first match
        assert_eq!(title, Some("First".to_string()));
    }

    #[test]
    fn test_metadata_with_whitespace() {
        let data = b"<<  /Title   (Hello World)  >>";
        let title = extract_metadata(data, b"/Title");
        assert_eq!(title, Some("Hello World".to_string()));
    }

    #[test]
    fn test_metadata_with_newline() {
        let data = b"<< /Title\n(Hello World) >>";
        let title = extract_metadata(data, b"/Title");
        assert_eq!(title, Some("Hello World".to_string()));
    }

    #[test]
    fn test_pdf_version_with_space() {
        let data = b"%PDF-1.5 more data";
        let version = extract_pdf_version(data);
        assert_eq!(version, Some("1.5".to_string()));
    }

    #[test]
    fn test_string_to_c_long_string() {
        let long_string = "a".repeat(10000);
        let ptr = string_to_c(Some(long_string.clone()));
        assert!(!ptr.is_null());
        unsafe {
            let cs = CStr::from_ptr(ptr);
            assert_eq!(cs.to_str().unwrap().len(), 10000);
            let _ = CString::from_raw(ptr);
        }
    }

    #[test]
    fn test_c_str_to_string_long() {
        let long_string = "a".repeat(10000);
        let cs = CString::new(long_string.clone()).unwrap();
        let result = c_str_to_string(cs.as_ptr());
        assert_eq!(result.unwrap().len(), 10000);
    }
}
