//! C FFI for pdf_form - MicroPDF compatible form handling
//!
//! Provides FFI bindings for PDF interactive forms (AcroForms).

use super::{Handle, HandleStore};
use crate::pdf::form::{ChoiceOption, FieldFlags, Form, FormField, TextFormat, WidgetType};
use std::ffi::{CStr, c_char};
use std::sync::LazyLock;

/// Form storage
pub static FORMS: LazyLock<HandleStore<Form>> = LazyLock::new(HandleStore::default);

/// Form field storage (widgets)
pub static FORM_FIELDS: LazyLock<HandleStore<FormField>> = LazyLock::new(HandleStore::default);

// ============================================================================
// Form Access
// ============================================================================

/// Get form from document.
///
/// Looks up the document by handle, scans its raw PDF bytes for an
/// /AcroForm dictionary, and extracts any /Fields entries it finds.
/// Each field is turned into a `FormField` and added to the returned
/// `Form`.  When no /AcroForm is present an empty form is returned.
#[unsafe(no_mangle)]
pub extern "C" fn pdf_form(_ctx: Handle, doc: Handle) -> Handle {
    let mut form = Form::new();

    // Look up the document handle and extract form fields from its PDF data
    if let Some(doc_arc) = crate::ffi::DOCUMENTS.get(doc) {
        if let Ok(doc_guard) = doc_arc.lock() {
            let data = doc_guard.data();
            parse_acroform_fields(data, &mut form);
        }
    }

    FORMS.insert(form)
}

/// Parse form fields from raw PDF bytes. Used for testing.
#[cfg(test)]
pub(crate) fn test_parse_form_from_bytes(data: &[u8]) -> Form {
    let mut form = Form::new();
    parse_acroform_fields(data, &mut form);
    form
}

/// Scan raw PDF bytes for an /AcroForm dictionary and extract field
/// definitions from any /Fields array found inside it.
fn parse_acroform_fields(data: &[u8], form: &mut Form) {
    let text = String::from_utf8_lossy(data);

    // Look for /AcroForm dictionary in the document catalog
    let acroform_start = match text.find("/AcroForm") {
        Some(pos) => pos,
        None => return,
    };

    // Find the enclosing dictionary for AcroForm.  We search forward
    // for "<<" from the /AcroForm key to handle both inline and
    // indirect-object layouts.
    let search_region = &text[acroform_start..];
    let dict_start = match search_region.find("<<") {
        Some(pos) => acroform_start + pos,
        None => return,
    };

    // Collect the balanced dictionary text
    let dict_text = match extract_balanced_dict(&text, dict_start) {
        Some(t) => t,
        None => return,
    };

    // Look for /Fields inside the AcroForm dict
    if let Some(fields_pos) = dict_text.find("/Fields") {
        let after_fields = &dict_text[fields_pos + 7..];
        let obj_refs = extract_pdf_array_refs(&dict_text, "/Fields");
        for obj_num in obj_refs {
            parse_and_add_field_with_kids(text.as_ref(), obj_num, form);
        }
    }
}

/// Parse a field object and add it to the form; recursively process /Kids.
fn parse_and_add_field_with_kids(text: &str, obj_num: i32, form: &mut Form) {
    if let Some(field) = parse_field_object(text, obj_num) {
        form.add_field(field);
    }

    let dict_text = get_object_dict(text, obj_num);
    let kid_refs = extract_pdf_array_refs(&dict_text, "/Kids");
    for kid_num in kid_refs {
        parse_and_add_field_with_kids(text, kid_num, form);
    }
}

/// Get the dictionary content of a PDF object by number.
fn get_object_dict(text: &str, obj_num: i32) -> String {
    let obj_header = format!("{} 0 obj", obj_num);
    let obj_start = match text.find(&obj_header) {
        Some(p) => p,
        None => return String::new(),
    };
    let after_obj = &text[obj_start + obj_header.len()..];
    let dict_offset = match after_obj.find("<<") {
        Some(p) => p,
        None => return String::new(),
    };
    extract_balanced_dict(&text[obj_start + obj_header.len()..], dict_offset).unwrap_or_default()
}

/// Extract a balanced dictionary string starting at the "<<" position.
fn extract_balanced_dict(text: &str, start: usize) -> Option<String> {
    let bytes = text.as_bytes();
    if start + 1 >= bytes.len() || bytes[start] != b'<' || bytes[start + 1] != b'<' {
        return None;
    }

    let mut depth = 0i32;
    let mut i = start;
    while i < bytes.len() - 1 {
        if bytes[i] == b'<' && bytes[i + 1] == b'<' {
            depth += 1;
            i += 2;
        } else if bytes[i] == b'>' && bytes[i + 1] == b'>' {
            depth -= 1;
            i += 2;
            if depth == 0 {
                return Some(text[start..i].to_string());
            }
        } else {
            i += 1;
        }
    }
    // If we ran out of data, return what we have
    Some(text[start..].to_string())
}

/// Parse /DA default appearance string: "/Font 12 Tf 0 g" -> font, size, color.
fn parse_default_appearance(da: &str) -> (Option<String>, f32, [f32; 3]) {
    let mut font_name = None;
    let mut font_size = 12.0;
    let parts: Vec<&str> = da.split_whitespace().collect();

    // Tf operator: operands are font size (number) and font name (/Name)
    for (i, &token) in parts.iter().enumerate() {
        if token == "Tf" && i >= 2 {
            if let Ok(size) = parts[i - 1].parse::<f32>() {
                font_size = size;
            }
            if parts[i - 2].starts_with('/') {
                font_name = Some(parts[i - 2][1..].to_string());
            }
            break;
        }
    }

    // Look for "g" (gray) or "rg" (RGB) color operator
    let mut color = [0.0, 0.0, 0.0];
    let mut i = 0;
    while i < parts.len() {
        if parts[i] == "g" && i >= 1 {
            if let Ok(g) = parts[i - 1].parse::<f32>() {
                color = [g, g, g];
            }
            break;
        } else if parts[i] == "rg" && i >= 3 {
            if let (Ok(r), Ok(g), Ok(b)) = (
                parts[i - 3].parse::<f32>(),
                parts[i - 2].parse::<f32>(),
                parts[i - 1].parse::<f32>(),
            ) {
                color = [r, g, b];
            }
            break;
        }
        i += 1;
    }

    (font_name, font_size, color)
}

/// Try to locate and parse a PDF object by number, extracting a
/// FormField from its dictionary entries.
fn parse_field_object(text: &str, obj_num: i32) -> Option<FormField> {
    // Look for "N 0 obj" where N is the object number
    let obj_header = format!("{} 0 obj", obj_num);
    let obj_start = text.find(&obj_header)?;
    let after_obj = &text[obj_start + obj_header.len()..];

    // Find the dictionary start
    let dict_offset = after_obj.find("<<")?;
    let dict_text = extract_balanced_dict(&text[obj_start + obj_header.len()..], dict_offset)?;

    // Extract /T (field name)
    let name = extract_pdf_string(&dict_text, "/T")?;

    // Extract /Ff first (needed for Btn/Ch type determination)
    let flags_val = extract_pdf_integer(&dict_text, "/Ff").unwrap_or(0) as u32;
    let flags = FieldFlags::new(flags_val);

    // Determine field type from /FT and /Ff
    let ft = extract_pdf_name(&dict_text, "/FT").unwrap_or_default();
    let widget_type = match ft.as_str() {
        "Tx" => WidgetType::Text,
        "Btn" => {
            if flags.has(FieldFlags::RADIO) {
                WidgetType::RadioButton
            } else if flags.has(FieldFlags::PUSHBUTTON) {
                WidgetType::Button
            } else {
                WidgetType::Checkbox
            }
        }
        "Ch" => {
            if flags.has(FieldFlags::COMBO) {
                WidgetType::ComboBox
            } else {
                WidgetType::ListBox
            }
        }
        "Sig" => WidgetType::Signature,
        _ => WidgetType::Unknown,
    };

    // Extract /Rect
    let rect = extract_pdf_rect(&dict_text, "/Rect").unwrap_or(crate::fitz::geometry::Rect {
        x0: 0.0,
        y0: 0.0,
        x1: 0.0,
        y1: 0.0,
    });

    let mut field = FormField::new(name, widget_type, rect);
    field.set_flags(flags);

    // Extract /V (value)
    let has_v = if let Some(value) = extract_pdf_string(&dict_text, "/V") {
        let _ = field.set_value(value);
        true
    } else {
        false
    };

    // Extract /AS (appearance state) when /V absent - for checkbox/radio
    if !has_v && matches!(widget_type, WidgetType::Checkbox | WidgetType::RadioButton) {
        if let Some(as_state) = extract_pdf_name(&dict_text, "/AS") {
            if as_state != "Off" && !as_state.is_empty() {
                let _ = field.set_value("Yes".to_string());
            }
        }
    }

    // Extract /DV (default value)
    if let Some(default_value) = extract_pdf_string(&dict_text, "/DV") {
        field.default_value = default_value;
    }

    // Extract /MaxLen
    if let Some(max_len) = extract_pdf_integer(&dict_text, "/MaxLen") {
        if max_len > 0 {
            field.set_max_len(Some(max_len as usize));
        }
    }

    // Extract /TU (tooltip)
    if let Some(tooltip) = extract_pdf_string(&dict_text, "/TU") {
        field.set_tooltip(Some(tooltip));
    }

    // Extract /DA (default appearance) - font, size, color
    if let Some(da) = extract_pdf_string(&dict_text, "/DA") {
        let (font_name, font_size, color) = parse_default_appearance(&da);
        field.font_name = font_name;
        field.font_size = font_size;
        field.border_color = color;
    }

    // Extract /Opt for choice fields
    if matches!(widget_type, WidgetType::ComboBox | WidgetType::ListBox) {
        let options = extract_pdf_opt_array(&dict_text);
        if !options.is_empty() {
            for opt in &options {
                field.choices.push((opt.label.clone(), opt.value.clone()));
            }
            field.set_options(options);
            field.is_combo = widget_type == WidgetType::ComboBox;
            field.editable = flags.has(FieldFlags::EDIT);
            field.multi_select = flags.has(FieldFlags::MULTI_SELECT);
        }
    }

    Some(field)
}

/// Extract a PDF name value (e.g., /FT /Tx -> "Tx").
fn extract_pdf_name(dict: &str, key: &str) -> Option<String> {
    let pos = dict.find(key)?;
    let after = dict[pos + key.len()..].trim_start();
    if after.starts_with('/') {
        let name_end = after[1..]
            .find(|c: char| {
                c.is_whitespace() || c == '/' || c == '>' || c == '<' || c == '[' || c == '('
            })
            .map(|i| i + 1)
            .unwrap_or(after.len());
        Some(after[1..name_end].to_string())
    } else {
        None
    }
}

/// Decode a PDF hex string (e.g. `<48656C6C6F>` -> "Hello").
fn decode_hex_string(hex: &str) -> String {
    let hex = hex.trim();
    if hex.len() < 2 || hex.len() % 2 != 0 {
        return String::new();
    }
    let mut result = String::with_capacity(hex.len() / 2);
    let mut chars = hex.chars();
    while let (Some(h), Some(l)) = (chars.next(), chars.next()) {
        if let (Some(high), Some(low)) = (h.to_digit(16), l.to_digit(16)) {
            result.push(char::from_u32((high << 4) | low).unwrap_or('\u{fffd}'));
        }
    }
    result
}

/// Extract a PDF string value (parenthesised literal, hex, or name).
fn extract_pdf_string(dict: &str, key: &str) -> Option<String> {
    let pos = dict.find(key)?;
    let after = dict[pos + key.len()..].trim_start();
    if after.starts_with('(') {
        // Literal string
        let mut depth = 0i32;
        let mut end = 0;
        for (i, ch) in after.chars().enumerate() {
            match ch {
                '(' => depth += 1,
                ')' => {
                    depth -= 1;
                    if depth == 0 {
                        end = i;
                        break;
                    }
                }
                _ => {}
            }
        }
        if end > 1 {
            Some(after[1..end].to_string())
        } else {
            None
        }
    } else if after.starts_with('<') {
        // Hex string: <48656C6C6F>
        let hex_end = after[1..].find('>').map(|i| i + 1).unwrap_or(after.len());
        let hex_content = &after[1..hex_end];
        let decoded = decode_hex_string(hex_content);
        if decoded.is_empty() {
            None
        } else {
            Some(decoded)
        }
    } else if after.starts_with('/') {
        // Name value
        extract_pdf_name(dict, key)
    } else {
        None
    }
}

/// Extract a PDF integer value.
fn extract_pdf_integer(dict: &str, key: &str) -> Option<i64> {
    let pos = dict.find(key)?;
    let after = dict[pos + key.len()..].trim_start();
    let end = after
        .find(|c: char| !c.is_ascii_digit() && c != '-')
        .unwrap_or(after.len());
    if end > 0 {
        after[..end].parse::<i64>().ok()
    } else {
        None
    }
}

/// Extract object references from a PDF array (e.g. /Kids [ 5 0 R 6 0 R ]).
fn extract_pdf_array_refs(dict: &str, key: &str) -> Vec<i32> {
    let pos = match dict.find(key) {
        Some(p) => p,
        None => return vec![],
    };
    let after = dict[pos + key.len()..].trim_start();
    let arr_start = match after.find('[') {
        Some(p) => p,
        None => return vec![],
    };
    let arr_end = match after[arr_start..].find(']') {
        Some(p) => p,
        None => return vec![],
    };
    let arr_text = &after[arr_start + 1..arr_start + arr_end];
    let parts: Vec<&str> = arr_text.split_whitespace().collect();
    let mut refs = Vec::new();
    // PDF refs are "obj gen R" e.g. "5 0 R 6 0 R"
    let mut i = 0;
    while i + 2 < parts.len() {
        if let (Ok(obj), Ok(_gen)) = (parts[i].parse::<i32>(), parts[i + 1].parse::<i32>()) {
            if parts[i + 2] == "R" && obj > 0 {
                refs.push(obj);
            }
        }
        i += 3;
    }
    refs
}

/// Extract /Opt array for choice fields. Supports both [ (A)(B)(C) ] and
/// [ [(e1)(d1)] [(e2)(d2)] ] formats.
fn extract_pdf_opt_array(dict: &str) -> Vec<ChoiceOption> {
    let pos = match dict.find("/Opt") {
        Some(p) => p,
        None => return vec![],
    };
    let after = dict[pos + 4..].trim_start();
    if !after.starts_with('[') {
        return vec![];
    }
    let arr_content = match extract_balanced_brackets(after) {
        Some(s) => s,
        None => return vec![],
    };
    let mut options = Vec::new();
    let mut i = 0;
    while i < arr_content.len() {
        let rest = arr_content[i..].trim_start();
        let skip = arr_content[i..].len() - rest.len();
        i += skip;
        if rest.is_empty() {
            break;
        }
        let consumed = if rest.starts_with('[') {
            if let Some(inner) = extract_balanced_brackets(rest) {
                let parts = parse_strings_from_array_like(&inner);
                if parts.len() >= 2 {
                    options.push(ChoiceOption::new(parts[1].clone(), parts[0].clone()));
                } else if let Some(first) = parts.first() {
                    options.push(ChoiceOption::simple(first.clone()));
                }
                inner.len() + 2
            } else {
                break;
            }
        } else if rest.starts_with('(') || rest.starts_with('<') {
            if let Some((val, n)) = extract_next_pdf_string_with_len(rest) {
                options.push(ChoiceOption::simple(val));
                n
            } else {
                break;
            }
        } else {
            break;
        };
        i += consumed;
    }
    options
}

fn extract_balanced_brackets(s: &str) -> Option<String> {
    if !s.starts_with('[') {
        return None;
    }
    let mut depth = 0i32;
    for (i, ch) in s.char_indices() {
        match ch {
            '[' => depth += 1,
            ']' => {
                depth -= 1;
                if depth == 0 {
                    return Some(s[1..i].to_string());
                }
            }
            _ => {}
        }
    }
    None
}

fn parse_strings_from_array_like(s: &str) -> Vec<String> {
    let mut result = Vec::new();
    let mut i = 0;
    while i < s.len() {
        let rest = s[i..].trim_start();
        i += s.len() - rest.len();
        if rest.is_empty() {
            break;
        }
        if let Some((val, consumed)) = extract_next_pdf_string_with_len(rest) {
            result.push(val);
            i += consumed;
        } else {
            break;
        }
    }
    result
}

/// Returns (value, bytes_consumed).
fn extract_next_pdf_string_with_len(s: &str) -> Option<(String, usize)> {
    let s = s.trim_start();
    if s.starts_with('(') {
        let mut depth = 0i32;
        for (i, ch) in s.char_indices() {
            match ch {
                '(' => depth += 1,
                ')' => {
                    depth -= 1;
                    if depth == 0 && i > 0 {
                        return Some((s[1..i].to_string(), i + 1));
                    }
                }
                _ => {}
            }
        }
        None
    } else if s.starts_with('<') {
        if let Some(end) = s[1..].find('>') {
            let hex = &s[1..=end];
            let decoded = decode_hex_string(hex);
            if !decoded.is_empty() {
                return Some((decoded, end + 2));
            }
        }
        None
    } else {
        None
    }
}

/// Extract a PDF rectangle array.
fn extract_pdf_rect(dict: &str, key: &str) -> Option<crate::fitz::geometry::Rect> {
    let pos = dict.find(key)?;
    let after = dict[pos + key.len()..].trim_start();
    let arr_start = after.find('[')?;
    let arr_end = after[arr_start..].find(']')?;
    let arr_text = &after[arr_start + 1..arr_start + arr_end];
    let values: Vec<f32> = arr_text
        .split_whitespace()
        .filter_map(|s| s.parse::<f32>().ok())
        .collect();
    if values.len() == 4 {
        Some(crate::fitz::geometry::Rect {
            x0: values[0],
            y0: values[1],
            x1: values[2],
            y1: values[3],
        })
    } else {
        None
    }
}

/// Keep form reference
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_form(_ctx: Handle, form: Handle) -> Handle {
    if FORMS.get(form).is_some() {
        return form;
    }
    0
}

/// Drop form reference
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_form(_ctx: Handle, form: Handle) {
    FORMS.remove(form);
}

// ============================================================================
// Field Iteration
// ============================================================================

/// Get first widget on page
#[unsafe(no_mangle)]
pub extern "C" fn pdf_first_widget(_ctx: Handle, page: Handle) -> Handle {
    if let Some(p) = super::document::PAGES.get(page) {
        if let Ok(guard) = p.lock() {
            return guard.first_widget().unwrap_or(0);
        }
    }
    0
}

/// Get next widget
#[unsafe(no_mangle)]
pub extern "C" fn pdf_next_widget(_ctx: Handle, widget: Handle) -> Handle {
    // Find the page this widget belongs to by searching all loaded pages
    if FORM_FIELDS.get(widget).is_some() {
        for page_handle in 1..10000 {
            // Reasonable page limit
            if let Some(p) = super::document::PAGES.get(page_handle) {
                if let Ok(guard) = p.lock() {
                    if guard.widgets.contains(&widget) {
                        return guard.next_widget(widget).unwrap_or(0);
                    }
                }
            }
        }
    }
    0
}

// ============================================================================
// Field Creation
// ============================================================================

/// Create a text field
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_text_field(
    _ctx: Handle,
    _form: Handle,
    name: *const std::ffi::c_char,
    x: f32,
    y: f32,
    width: f32,
    height: f32,
    max_len: i32,
) -> Handle {
    if name.is_null() {
        return 0;
    }

    if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
        let rect = crate::fitz::geometry::Rect {
            x0: x,
            y0: y,
            x1: x + width,
            y1: y + height,
        };

        let max_length = if max_len > 0 {
            Some(max_len as usize)
        } else {
            None
        };

        let field = FormField::text_field(field_name.to_string(), rect, max_length);
        return FORM_FIELDS.insert(field);
    }

    0
}

/// Create a checkbox
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_checkbox(
    _ctx: Handle,
    _form: Handle,
    name: *const std::ffi::c_char,
    x: f32,
    y: f32,
    width: f32,
    height: f32,
    checked: i32,
) -> Handle {
    if name.is_null() {
        return 0;
    }

    if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
        let rect = crate::fitz::geometry::Rect {
            x0: x,
            y0: y,
            x1: x + width,
            y1: y + height,
        };

        let field = FormField::checkbox(field_name.to_string(), rect, checked != 0);
        return FORM_FIELDS.insert(field);
    }

    0
}

/// Create a push button
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_push_button(
    _ctx: Handle,
    _form: Handle,
    name: *const std::ffi::c_char,
    x: f32,
    y: f32,
    width: f32,
    height: f32,
    caption: *const std::ffi::c_char,
) -> Handle {
    if name.is_null() {
        return 0;
    }

    if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
        let rect = crate::fitz::geometry::Rect {
            x0: x,
            y0: y,
            x1: x + width,
            y1: y + height,
        };

        let caption_str = if caption.is_null() {
            ""
        } else {
            super::safe_helpers::c_str_to_str(caption).unwrap_or("")
        };

        let field = FormField::push_button(field_name.to_string(), rect, caption_str);
        return FORM_FIELDS.insert(field);
    }

    0
}

/// Create a combo box (dropdown)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_combo_box(
    _ctx: Handle,
    _form: Handle,
    name: *const std::ffi::c_char,
    x: f32,
    y: f32,
    width: f32,
    height: f32,
) -> Handle {
    if name.is_null() {
        return 0;
    }

    if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
        let rect = crate::fitz::geometry::Rect {
            x0: x,
            y0: y,
            x1: x + width,
            y1: y + height,
        };

        let field = FormField::combo_box(field_name.to_string(), rect, Vec::new());
        return FORM_FIELDS.insert(field);
    }

    0
}

/// Create a signature field
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_signature_field(
    _ctx: Handle,
    _form: Handle,
    name: *const std::ffi::c_char,
    x: f32,
    y: f32,
    width: f32,
    height: f32,
) -> Handle {
    if name.is_null() {
        return 0;
    }

    if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
        let rect = crate::fitz::geometry::Rect {
            x0: x,
            y0: y,
            x1: x + width,
            y1: y + height,
        };

        let field = FormField::signature(field_name.to_string(), rect);
        return FORM_FIELDS.insert(field);
    }

    0
}

// ============================================================================
// Field Properties
// ============================================================================

/// Get field name
///
/// # Safety
/// Caller must ensure buf points to valid memory of at least size bytes
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_name(
    _ctx: Handle,
    field: Handle,
    buf: *mut std::ffi::c_char,
    size: i32,
) -> i32 {
    if buf.is_null() || size <= 0 {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return super::safe_helpers::str_to_c_buffer(guard.name(), buf, size);
        }
    }

    0
}

/// Get field type
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_type(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return match guard.field_type() {
                WidgetType::Unknown => -1,
                WidgetType::Button => 0,
                WidgetType::Checkbox => 1,
                WidgetType::ComboBox => 2,
                WidgetType::ListBox => 3,
                WidgetType::RadioButton => 4,
                WidgetType::Signature => 5,
                WidgetType::Text => 6,
            };
        }
    }
    -1
}

/// Get field value
///
/// # Safety
/// Caller must ensure buf points to valid memory of at least size bytes
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_value(
    _ctx: Handle,
    field: Handle,
    buf: *mut std::ffi::c_char,
    size: i32,
) -> i32 {
    if buf.is_null() || size <= 0 {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return super::safe_helpers::str_to_c_buffer(guard.value(), buf, size);
        }
    }

    0
}

/// Set field value
///
/// # Safety
/// Caller must ensure value is a valid null-terminated C string
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_value(
    _ctx: Handle,
    field: Handle,
    value: *const std::ffi::c_char,
) -> i32 {
    if value.is_null() {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            if let Some(val_str) = super::safe_helpers::c_str_to_str(value) {
                if guard.set_value(val_str.to_string()).is_ok() {
                    return 1;
                }
            }
        }
    }

    0
}

/// Get field rectangle
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_rect(_ctx: Handle, field: Handle) -> super::geometry::fz_rect {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            let rect = guard.rect();
            return super::geometry::fz_rect {
                x0: rect.x0,
                y0: rect.y0,
                x1: rect.x1,
                y1: rect.y1,
            };
        }
    }

    super::geometry::fz_rect {
        x0: 0.0,
        y0: 0.0,
        x1: 0.0,
        y1: 0.0,
    }
}

/// Get field flags
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_flags(_ctx: Handle, field: Handle) -> u32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.flags().value();
        }
    }
    0
}

/// Set field flags
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_flags(_ctx: Handle, field: Handle, flags: u32) {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            guard.set_flags(FieldFlags::new(flags));
        }
    }
}

// ============================================================================
// Field State
// ============================================================================

/// Check if field is read-only
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_read_only(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_read_only() as i32;
        }
    }
    0
}

/// Check if field is required
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_required(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_required() as i32;
        }
    }
    0
}

/// Check if checkbox/radio button is checked
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_checked(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_checked() as i32;
        }
    }
    0
}

/// Set checkbox/radio button checked state
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_checked(_ctx: Handle, field: Handle, checked: i32) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            if guard.set_checked(checked != 0).is_ok() {
                return 1;
            }
        }
    }
    0
}

/// Check if text field is multiline
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_multiline(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_multiline() as i32;
        }
    }
    0
}

/// Check if text field is password
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_password(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_password() as i32;
        }
    }
    0
}

/// Check if signature field is signed
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_signed(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_signed() as i32;
        }
    }
    0
}

// ============================================================================
// Text Field Properties
// ============================================================================

/// Get text field maximum length
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_max_len(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.max_len().map(|l| l as i32).unwrap_or(0);
        }
    }
    0
}

/// Set text field maximum length
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_max_len(_ctx: Handle, field: Handle, max_len: i32) {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            let max = if max_len > 0 {
                Some(max_len as usize)
            } else {
                None
            };
            guard.set_max_len(max);
        }
    }
}

/// Get text field format
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_text_format(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return match guard.text_format() {
                TextFormat::None => 0,
                TextFormat::Number => 1,
                TextFormat::Special => 2,
                TextFormat::Date => 3,
                TextFormat::Time => 4,
            };
        }
    }
    0
}

// ============================================================================
// Choice Field Properties
// ============================================================================

/// Get number of choice options
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_choice_count(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.options().len() as i32;
        }
    }
    0
}

/// Get choice option label
///
/// # Safety
/// Caller must ensure buf points to valid memory of at least size bytes
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_choice_label(
    _ctx: Handle,
    field: Handle,
    index: i32,
    buf: *mut std::ffi::c_char,
    size: i32,
) -> i32 {
    if buf.is_null() || size <= 0 || index < 0 {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            let options = guard.options();
            if let Some(option) = options.get(index as usize) {
                return super::safe_helpers::str_to_c_buffer(&option.label, buf, size);
            }
        }
    }

    0
}

/// Get choice option value
///
/// # Safety
/// Caller must ensure buf points to valid memory of at least size bytes
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_choice_value(
    _ctx: Handle,
    field: Handle,
    index: i32,
    buf: *mut std::ffi::c_char,
    size: i32,
) -> i32 {
    if buf.is_null() || size <= 0 || index < 0 {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            let options = guard.options();
            if let Some(option) = options.get(index as usize) {
                return super::safe_helpers::str_to_c_buffer(&option.value, buf, size);
            }
        }
    }

    0
}

/// Add choice option
///
/// # Safety
/// Caller must ensure label and value are valid null-terminated C strings
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_field_choice(
    _ctx: Handle,
    field: Handle,
    label: *const std::ffi::c_char,
    value: *const std::ffi::c_char,
) -> i32 {
    if label.is_null() || value.is_null() {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            if let (Some(label_str), Some(value_str)) = (
                super::safe_helpers::c_str_to_str(label),
                super::safe_helpers::c_str_to_str(value),
            ) {
                let mut options = guard.options().to_vec();
                options.push(ChoiceOption::new(
                    label_str.to_string(),
                    value_str.to_string(),
                ));
                guard.set_options(options);
                return 1;
            }
        }
    }

    0
}

// ============================================================================
// Form-level Operations
// ============================================================================

/// Get form field count
#[unsafe(no_mangle)]
pub extern "C" fn pdf_form_field_count(_ctx: Handle, form: Handle) -> i32 {
    if let Some(f) = FORMS.get(form) {
        if let Ok(guard) = f.lock() {
            return guard.len() as i32;
        }
    }
    0
}

/// Get form field by name
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_field(
    _ctx: Handle,
    form: Handle,
    name: *const std::ffi::c_char,
) -> Handle {
    if name.is_null() {
        return 0;
    }

    if let Some(f) = FORMS.get(form) {
        if let Ok(guard) = f.lock() {
            if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
                if let Some(field) = guard.get_field(field_name) {
                    return FORM_FIELDS.insert(field.clone());
                }
            }
        }
    }

    0
}

/// Reset form to default values
#[unsafe(no_mangle)]
pub extern "C" fn pdf_reset_form(_ctx: Handle, form: Handle) {
    if let Some(f) = FORMS.get(form) {
        if let Ok(mut guard) = f.lock() {
            guard.reset();
        }
    }
}

/// Validate form
#[unsafe(no_mangle)]
pub extern "C" fn pdf_validate_form(_ctx: Handle, form: Handle) -> i32 {
    if let Some(f) = FORMS.get(form) {
        if let Ok(guard) = f.lock() {
            return guard.validate().is_ok() as i32;
        }
    }
    0
}

/// Delete a form field
#[unsafe(no_mangle)]
pub extern "C" fn pdf_delete_field(_ctx: Handle, _form: Handle, field: Handle) -> i32 {
    FORM_FIELDS.remove(field);
    1
}

/// Get field default value
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_default_value(
    _ctx: Handle,
    field: Handle,
    buf: *mut c_char,
    buf_size: i32,
) -> i32 {
    if buf.is_null() || buf_size <= 0 {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            let default_val = &guard.default_value;
            let bytes = default_val.as_bytes();
            let len = (bytes.len() as i32).min(buf_size - 1);

            unsafe {
                std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, len as usize);
                *buf.offset(len as isize) = 0;
            }
            return len;
        }
    }
    0
}

/// Set field default value
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_default_value(
    _ctx: Handle,
    field: Handle,
    value: *const c_char,
) -> i32 {
    if value.is_null() {
        return 0;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            if let Ok(val_str) = unsafe { CStr::from_ptr(value).to_str() } {
                guard.default_value = val_str.to_string();
                return 1;
            }
        }
    }
    0
}

/// Get field border width
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_border_width(_ctx: Handle, field: Handle) -> f32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.border_width;
        }
    }
    1.0
}

/// Set field border width
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_border_width(_ctx: Handle, field: Handle, width: f32) {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            guard.border_width = width.max(0.0);
        }
    }
}

/// Get field border color (RGB)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_border_color(_ctx: Handle, field: Handle, color: *mut f32) {
    if color.is_null() {
        return;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            unsafe {
                *color.offset(0) = guard.border_color[0];
                *color.offset(1) = guard.border_color[1];
                *color.offset(2) = guard.border_color[2];
            }
        }
    }
}

/// Set field border color (RGB)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_border_color(_ctx: Handle, field: Handle, color: *const f32) {
    if color.is_null() {
        return;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            unsafe {
                guard.border_color = [*color.offset(0), *color.offset(1), *color.offset(2)];
            }
        }
    }
}

/// Get field background color (RGB)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_bg_color(_ctx: Handle, field: Handle, color: *mut f32) {
    if color.is_null() {
        return;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            unsafe {
                *color.offset(0) = guard.bg_color[0];
                *color.offset(1) = guard.bg_color[1];
                *color.offset(2) = guard.bg_color[2];
            }
        }
    }
}

/// Set field background color (RGB)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_bg_color(_ctx: Handle, field: Handle, color: *const f32) {
    if color.is_null() {
        return;
    }

    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            unsafe {
                guard.bg_color = [*color.offset(0), *color.offset(1), *color.offset(2)];
            }
        }
    }
}

/// Get field font size
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_font_size(_ctx: Handle, field: Handle) -> f32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.font_size;
        }
    }
    12.0
}

/// Set field font size
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_font_size(_ctx: Handle, field: Handle, size: f32) {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            guard.font_size = size.max(1.0);
        }
    }
}

/// Get field alignment (0=left, 1=center, 2=right)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_alignment(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.alignment;
        }
    }
    0
}

/// Set field alignment (0=left, 1=center, 2=right)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_alignment(_ctx: Handle, field: Handle, align: i32) {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            guard.alignment = align.clamp(0, 2);
        }
    }
}

/// Check if field is combo (vs list)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_combo(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.is_combo as i32;
        }
    }
    0
}

/// Check if choice allows edit
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_edit(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.editable as i32;
        }
    }
    0
}

/// Check if choice allows multiple selection
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_multiselect(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.multi_select as i32;
        }
    }
    0
}

/// Get selected choice index
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_selected_index(_ctx: Handle, field: Handle) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            return guard.selected_index;
        }
    }
    -1
}

/// Set selected choice index
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_selected_index(_ctx: Handle, field: Handle, idx: i32) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            if idx >= 0 && (idx as usize) < guard.choices.len() {
                guard.selected_index = idx;
                return 1;
            }
        }
    }
    0
}

/// Clear all choice selections
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_clear_selection(_ctx: Handle, field: Handle) {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            guard.selected_index = -1;
        }
    }
}

/// Remove a choice option by index
#[unsafe(no_mangle)]
pub extern "C" fn pdf_remove_field_choice(_ctx: Handle, field: Handle, idx: i32) -> i32 {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(mut guard) = f.lock() {
            if idx >= 0 && (idx as usize) < guard.choices.len() {
                guard.choices.remove(idx as usize);
                return 1;
            }
        }
    }
    0
}

/// Check if field is valid
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_valid(_ctx: Handle, field: Handle) -> i32 {
    if FORM_FIELDS.get(field).is_some() {
        1
    } else {
        0
    }
}

/// Clone a field (create a copy with new handle)
#[unsafe(no_mangle)]
pub extern "C" fn pdf_clone_field(_ctx: Handle, field: Handle) -> Handle {
    if let Some(f) = FORM_FIELDS.get(field) {
        if let Ok(guard) = f.lock() {
            let cloned = guard.clone();
            return FORM_FIELDS.insert(cloned);
        }
    }
    0
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pdf::form::WidgetType;
    use std::ffi::CString;

    #[test]
    fn test_create_text_field() {
        let name = CString::new("username").unwrap();
        let field = pdf_create_text_field(0, 0, name.as_ptr(), 10.0, 10.0, 200.0, 30.0, 50);
        assert_ne!(field, 0);

        let field_type = pdf_field_type(0, field);
        assert_eq!(field_type, 6); // Text field

        FORM_FIELDS.remove(field);
    }

    #[test]
    fn test_create_checkbox() {
        let name = CString::new("agree").unwrap();
        let field = pdf_create_checkbox(0, 0, name.as_ptr(), 10.0, 10.0, 20.0, 20.0, 1);
        assert_ne!(field, 0);

        let checked = pdf_field_is_checked(0, field);
        assert_eq!(checked, 1);

        FORM_FIELDS.remove(field);
    }

    #[test]
    fn test_field_value() {
        let name = CString::new("testfield").unwrap();
        let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);

        let value = CString::new("Test Value").unwrap();
        let result = pdf_set_field_value(0, field, value.as_ptr());
        assert_eq!(result, 1);

        let mut buf = [0i8; 256];
        let len = pdf_field_value(0, field, buf.as_mut_ptr(), 256);
        assert!(len > 0);

        FORM_FIELDS.remove(field);
    }

    #[test]
    fn test_field_flags() {
        let name = CString::new("readonly").unwrap();
        let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);

        pdf_set_field_flags(0, field, 1); // READ_ONLY flag
        let is_readonly = pdf_field_is_read_only(0, field);
        assert_eq!(is_readonly, 1);

        FORM_FIELDS.remove(field);
    }

    #[test]
    fn test_choice_field() {
        let name = CString::new("country").unwrap();
        let field = pdf_create_combo_box(0, 0, name.as_ptr(), 0.0, 0.0, 150.0, 30.0);

        let label1 = CString::new("United States").unwrap();
        let value1 = CString::new("US").unwrap();
        pdf_add_field_choice(0, field, label1.as_ptr(), value1.as_ptr());

        let label2 = CString::new("Canada").unwrap();
        let value2 = CString::new("CA").unwrap();
        pdf_add_field_choice(0, field, label2.as_ptr(), value2.as_ptr());

        let count = pdf_field_choice_count(0, field);
        assert_eq!(count, 2);

        FORM_FIELDS.remove(field);
    }

    #[test]
    fn test_form_operations() {
        let form = pdf_form(0, 0);
        assert_ne!(form, 0);

        let count = pdf_form_field_count(0, form);
        assert_eq!(count, 0); // Empty form

        pdf_drop_form(0, form);
    }

    // MPDF-32: Form parsing tests
    #[test]
    fn test_parse_hex_string_field() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (name) /FT /Tx /V <48656C6C6F> /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("name").unwrap();
        assert_eq!(field.value(), "Hello");
    }

    #[test]
    fn test_parse_choice_field_with_opt() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (country) /FT /Ch /Ff 131072 /Opt [ (US) (CA) (UK) ] /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("country").unwrap();
        assert_eq!(field.options().len(), 3);
        assert_eq!(field.options()[0].value, "US");
        assert_eq!(field.options()[1].value, "CA");
        assert_eq!(field.options()[2].value, "UK");
    }

    #[test]
    fn test_parse_radio_via_ff_flags() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (choice) /FT /Btn /Ff 32768 /V (Yes) /Rect [ 0 0 20 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("choice").unwrap();
        assert_eq!(field.field_type(), WidgetType::RadioButton);
        assert!(field.is_checked());
    }

    #[test]
    fn test_parse_checkbox_as_state() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (agree) /FT /Btn /AS /Yes /Rect [ 0 0 20 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("agree").unwrap();
        assert_eq!(field.field_type(), WidgetType::Checkbox);
        assert!(field.is_checked());
    }

    #[test]
    fn test_parse_tooltip() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (email) /FT /Tx /TU (Enter your email) /Rect [ 0 0 200 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("email").unwrap();
        assert_eq!(field.tooltip(), Some("Enter your email"));
    }

    #[test]
    fn test_parse_default_appearance() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (text) /FT /Tx /DA (/Helvetica 14 Tf 0.2 0.4 0.6 rg) /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("text").unwrap();
        assert_eq!(field.font_size, 14.0);
        assert_eq!(field.font_name.as_deref(), Some("Helvetica"));
        assert!((field.border_color[0] - 0.2).abs() < 0.01);
        assert!((field.border_color[1] - 0.4).abs() < 0.01);
        assert!((field.border_color[2] - 0.6).abs() < 0.01);
    }

    #[test]
    fn test_parse_kids_recursive() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (parent) /FT /Tx /Kids [ 4 0 R ] /Rect [ 0 0 100 20 ] >> endobj
4 0 obj << /T (child) /FT /Tx /V (nested) /Rect [ 0 0 80 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert!(form.len() >= 2, "expected parent and child fields");
        assert!(form.get_field("parent").is_some());
        assert!(form.get_field("child").is_some());
        assert_eq!(form.get_field("child").unwrap().value(), "nested");
    }

    #[test]
    fn test_parse_opt_array_export_display() {
        let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (sel) /FT /Ch /Ff 131072 /Opt [ [(US)(United States)] [(CA)(Canada)] ] /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
        let form = test_parse_form_from_bytes(pdf);
        assert_eq!(form.len(), 1);
        let field = form.get_field("sel").unwrap();
        assert_eq!(field.options().len(), 2);
        assert_eq!(field.options()[0].value, "US");
        assert_eq!(field.options()[0].label, "United States");
        assert_eq!(field.options()[1].value, "CA");
        assert_eq!(field.options()[1].label, "Canada");
    }
}
