//! Advanced Forms - XFA and enhanced AcroForms
//!
//! Advanced form capabilities:
//! - XFA (XML Forms Architecture) support
//! - Dynamic forms with data binding
//! - Form calculations and validations
//! - JavaScript actions
//! - Form data integration (CSV/JSON)

use super::error::{EnhancedError, Result};
use std::collections::HashMap;
use std::fs;
use std::path::Path;

/// Form type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormType {
    /// AcroForm (static)
    AcroForm,
    /// XFA (dynamic)
    Xfa,
}

/// Field type for advanced forms
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldType {
    Text,
    Checkbox,
    Radio,
    Dropdown,
    ListBox,
    Button,
    Signature,
    Date,
    Number,
    Email,
    Barcode,
}

/// Form field configuration
#[derive(Debug, Clone)]
pub struct FormField {
    pub name: String,
    pub field_type: FieldType,
    pub page: u32,
    pub rect: (f32, f32, f32, f32),
    pub value: Option<String>,
    pub default_value: Option<String>,
    pub required: bool,
    pub readonly: bool,
    pub validation: Option<FieldValidation>,
    pub calculation: Option<String>,
}

/// Field validation rules
#[derive(Debug, Clone)]
pub struct FieldValidation {
    pub rule_type: ValidationType,
    pub pattern: Option<String>,
    pub min: Option<f64>,
    pub max: Option<f64>,
    pub custom_script: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationType {
    None,
    Email,
    Phone,
    Zip,
    Date,
    Number,
    Range,
    Pattern,
    Custom,
}

/// Create form with fields by adding AcroForm to the PDF
pub fn create_form(pdf_path: &str, _form_type: FormType, fields: Vec<FormField>) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let mut data = fs::read(pdf_path)?;
    if !data.starts_with(b"%PDF-") {
        return Err(EnhancedError::InvalidParameter(
            "Not a valid PDF file".into(),
        ));
    }

    let max_obj = crate::enhanced::overlay::find_max_object_number(&data);
    let mut next_obj = max_obj + 1;
    let mut field_refs = Vec::new();
    let mut new_objects = String::new();

    for field in &fields {
        let field_obj_num = next_obj;
        next_obj += 1;
        field_refs.push(format!("{} 0 R", field_obj_num));

        let field_type_str = match field.field_type {
            FieldType::Text | FieldType::Email | FieldType::Date | FieldType::Number => "Tx",
            FieldType::Checkbox | FieldType::Radio | FieldType::Button => "Btn",
            FieldType::Dropdown | FieldType::ListBox => "Ch",
            FieldType::Signature => "Sig",
            FieldType::Barcode => "Tx",
        };

        let escaped_name = field.name.replace('(', "\\(").replace(')', "\\)");
        let escaped_value = field
            .default_value
            .as_deref()
            .unwrap_or("")
            .replace('(', "\\(")
            .replace(')', "\\)");

        let (x1, y1, x2, y2) = field.rect;
        new_objects.push_str(&format!(
            "{} 0 obj\n<< /Type /Annot /Subtype /Widget /FT /{} /T ({}) /V ({}) /Rect [{:.2} {:.2} {:.2} {:.2}] /F 4 >>\nendobj\n",
            field_obj_num,
            field_type_str,
            escaped_name,
            escaped_value,
            x1, y1, x2, y2,
        ));
    }

    let catalog_needle = b"/Type /Catalog";
    if let Some(catalog_pos) = data
        .windows(catalog_needle.len())
        .position(|w| w == catalog_needle)
    {
        let search_region = &data[catalog_pos..];
        if let Some(enddict_pos) = search_region.windows(2).position(|w| w == b">>") {
            let insert_pos = catalog_pos + enddict_pos;
            let acroform = format!(" /AcroForm << /Fields [{}] >>", field_refs.join(" "));
            data.splice(insert_pos..insert_pos, acroform.bytes());
        }
    }

    data.extend_from_slice(new_objects.as_bytes());

    let catalog_obj_num = crate::enhanced::overlay::find_catalog_obj_num_from_bytes(&data);
    let obj_count = crate::enhanced::overlay::find_max_object_number(&data) + 1;
    crate::enhanced::overlay::rebuild_xref_and_trailer(&mut data, obj_count, catalog_obj_num)?;
    fs::write(pdf_path, &data)?;

    Ok(())
}

/// Fill form fields by replacing /V values in widget annotations
pub fn fill_form_from_data(
    pdf_path: &str,
    output_path: &str,
    data: HashMap<String, String>,
) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let mut pdf_data = fs::read(pdf_path)?;
    if !pdf_data.starts_with(b"%PDF-") {
        return Err(EnhancedError::InvalidParameter(
            "Not a valid PDF file".into(),
        ));
    }

    let mut content = String::from_utf8_lossy(&pdf_data).to_string();

    for (field_name, value) in &data {
        let escaped_name = field_name.replace('(', "\\(").replace(')', "\\)");
        let escaped_value = value.replace('(', "\\(").replace(')', "\\)");
        let pattern = format!("/T ({})", escaped_name);
        if let Some(field_pos) = content.find(&pattern) {
            if let Some(v_pos) = content[field_pos..].find("/V ") {
                let abs_v_pos = field_pos + v_pos;
                let after_v = &content[abs_v_pos + 3..];
                if after_v.starts_with('(') {
                    if let Some(close_paren) = find_matching_paren(after_v) {
                        let old_end = abs_v_pos + 3 + close_paren + 1;
                        let new_val = format!("/V ({})", escaped_value);
                        content.replace_range(abs_v_pos..old_end, &new_val);
                    }
                }
            }
        }
    }

    pdf_data = content.into_bytes();
    let catalog_obj_num = crate::enhanced::overlay::find_catalog_obj_num_from_bytes(&pdf_data);
    let obj_count = crate::enhanced::overlay::find_max_object_number(&pdf_data) + 1;
    crate::enhanced::overlay::rebuild_xref_and_trailer(&mut pdf_data, obj_count, catalog_obj_num)?;
    fs::write(output_path, &pdf_data)?;

    Ok(())
}

fn find_matching_paren(s: &str) -> Option<usize> {
    let mut depth = 0i32;
    let mut skip_next = false;
    for (i, c) in s.char_indices() {
        if skip_next {
            skip_next = false;
            continue;
        }
        match c {
            '\\' => {
                skip_next = true;
            }
            '(' => depth += 1,
            ')' => {
                depth -= 1;
                if depth == 0 {
                    return Some(i);
                }
            }
            _ => {}
        }
    }
    None
}

/// Import form data from CSV
pub fn import_form_data_csv(csv_path: &str) -> Result<Vec<HashMap<String, String>>> {
    if !Path::new(csv_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("CSV file not found: {}", csv_path),
        )));
    }

    let content = fs::read_to_string(csv_path)?;
    let mut lines = content.lines();

    let headers: Vec<String> = match lines.next() {
        Some(header_line) => parse_csv_line(header_line),
        None => return Ok(vec![]),
    };

    let mut result = Vec::new();
    for line in lines {
        if line.trim().is_empty() {
            continue;
        }
        let values = parse_csv_line(line);
        let mut map = HashMap::new();
        for (i, header) in headers.iter().enumerate() {
            let value = values.get(i).cloned().unwrap_or_default();
            map.insert(header.clone(), value);
        }
        result.push(map);
    }

    Ok(result)
}

fn parse_csv_line(line: &str) -> Vec<String> {
    let mut fields = Vec::new();
    let mut current = String::new();
    let mut in_quotes = false;

    for c in line.chars() {
        match c {
            '"' if !in_quotes => in_quotes = true,
            '"' if in_quotes => in_quotes = false,
            ',' if !in_quotes => {
                fields.push(current.trim().to_string());
                current = String::new();
            }
            _ => current.push(c),
        }
    }
    fields.push(current.trim().to_string());
    fields
}

/// Import form data from JSON
///
/// Expects either a JSON object `{"field": "value"}` or an array of objects.
/// Uses a simple JSON parser (no external dependency).
pub fn import_form_data_json(json_path: &str) -> Result<Vec<HashMap<String, String>>> {
    if !Path::new(json_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("JSON file not found: {}", json_path),
        )));
    }

    let content = fs::read_to_string(json_path)?;
    let trimmed = content.trim();

    if trimmed.starts_with('[') {
        parse_json_array(trimmed)
    } else if trimmed.starts_with('{') {
        let map = parse_json_object(trimmed)?;
        Ok(vec![map])
    } else {
        Err(EnhancedError::InvalidParameter(
            "JSON must be an object or array of objects".into(),
        ))
    }
}

fn parse_json_object(s: &str) -> Result<HashMap<String, String>> {
    let mut map = HashMap::new();
    let trimmed = s.trim();
    if !trimmed.starts_with('{') || !trimmed.ends_with('}') {
        return Ok(map);
    }
    let inner = &trimmed[1..trimmed.len() - 1];
    let pairs = split_json_top_level(inner, ',');

    for pair in pairs {
        let kv = split_json_top_level(&pair, ':');
        if kv.len() >= 2 {
            let key = kv[0].trim().trim_matches('"').to_string();
            let val = kv[1..].join(":").trim().to_string();
            let val = if val.starts_with('"') && val.ends_with('"') {
                val[1..val.len() - 1].to_string()
            } else {
                val
            };
            map.insert(key, val);
        }
    }
    Ok(map)
}

fn parse_json_array(s: &str) -> Result<Vec<HashMap<String, String>>> {
    let trimmed = s.trim();
    if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
        return Ok(vec![]);
    }
    let inner = &trimmed[1..trimmed.len() - 1];
    let items = split_json_top_level(inner, ',');
    let mut result = Vec::new();
    for item in items {
        let item = item.trim().to_string();
        if item.starts_with('{') {
            result.push(parse_json_object(&item)?);
        }
    }
    Ok(result)
}

fn split_json_top_level(s: &str, delimiter: char) -> Vec<String> {
    let mut parts = Vec::new();
    let mut current = String::new();
    let mut depth = 0i32;
    let mut in_string = false;
    let mut prev_char = '\0';

    for c in s.chars() {
        if c == '"' && prev_char != '\\' {
            in_string = !in_string;
        }
        if !in_string {
            match c {
                '{' | '[' => depth += 1,
                '}' | ']' => depth -= 1,
                _ if c == delimiter && depth == 0 => {
                    parts.push(current.clone());
                    current.clear();
                    prev_char = c;
                    continue;
                }
                _ => {}
            }
        }
        current.push(c);
        prev_char = c;
    }
    if !current.trim().is_empty() {
        parts.push(current);
    }
    parts
}

/// Export form data to CSV by extracting /T and /V from widget annotations
pub fn export_form_data_csv(pdf_path: &str, csv_path: &str) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let fields = extract_form_fields(pdf_path)?;
    if fields.is_empty() {
        fs::write(csv_path, "")?;
        return Ok(());
    }

    let mut csv = String::new();
    let keys: Vec<&String> = fields.keys().collect();
    csv.push_str(
        &keys
            .iter()
            .map(|k| format!("\"{}\"", k))
            .collect::<Vec<_>>()
            .join(","),
    );
    csv.push('\n');
    csv.push_str(
        &keys
            .iter()
            .map(|k| {
                let v = fields.get(*k).map(|s| s.as_str()).unwrap_or("");
                format!("\"{}\"", v.replace('"', "\"\""))
            })
            .collect::<Vec<_>>()
            .join(","),
    );
    csv.push('\n');
    fs::write(csv_path, csv)?;

    Ok(())
}

/// Export form data to JSON
pub fn export_form_data_json(pdf_path: &str, json_path: &str) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let fields = extract_form_fields(pdf_path)?;
    let mut json = String::from("{\n");
    let entries: Vec<String> = fields
        .iter()
        .map(|(k, v)| {
            let ek = k.replace('\\', "\\\\").replace('"', "\\\"");
            let ev = v.replace('\\', "\\\\").replace('"', "\\\"");
            format!("  \"{}\": \"{}\"", ek, ev)
        })
        .collect();
    json.push_str(&entries.join(",\n"));
    json.push_str("\n}\n");
    fs::write(json_path, json)?;

    Ok(())
}

fn extract_form_fields(pdf_path: &str) -> Result<HashMap<String, String>> {
    let data = fs::read(pdf_path)?;
    let content = String::from_utf8_lossy(&data);
    let mut fields = HashMap::new();

    let mut search_pos = 0;
    while let Some(t_pos) = content[search_pos..].find("/T (") {
        let abs_t_pos = search_pos + t_pos;
        let after_t = &content[abs_t_pos + 4..];
        if let Some(close) = find_matching_paren(&format!("({}", after_t)) {
            let field_name = after_t[..close - 1].to_string();
            let region_start = abs_t_pos.saturating_sub(200);
            let region_end = (abs_t_pos + 500).min(content.len());
            let region = &content[region_start..region_end];

            if let Some(v_pos) = region.find("/V (") {
                let after_v = &region[v_pos + 4..];
                if let Some(v_close) = find_matching_paren(&format!("({}", after_v)) {
                    let value = after_v[..v_close - 1].to_string();
                    fields.insert(field_name, value);
                }
            } else {
                fields.insert(field_name, String::new());
            }
        }
        search_pos = abs_t_pos + 4;
    }

    Ok(fields)
}

/// XFA form data binding
#[derive(Debug, Clone)]
pub struct XfaDataBinding {
    pub field_name: String,
    pub data_path: String,
    pub binding_type: XfaBindingType,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XfaBindingType {
    Normal,
    Global,
    None,
}

/// Create XFA form with data binding
///
/// XFA (XML Forms Architecture) is deprecated in PDF 2.0. This function
/// returns an unsupported error as XFA requires a full XML processing pipeline.
pub fn create_xfa_form(
    pdf_path: &str,
    _xfa_template: &str,
    _bindings: Vec<XfaDataBinding>,
) -> Result<()> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    Err(EnhancedError::Unsupported(
        "XFA form creation is not supported (XFA is deprecated in PDF 2.0)".to_string(),
    ))
}

/// Validate form data by checking required fields have values
pub fn validate_form_data(pdf_path: &str) -> Result<Vec<ValidationError>> {
    if !Path::new(pdf_path).exists() {
        return Err(EnhancedError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("PDF file not found: {}", pdf_path),
        )));
    }

    let data = fs::read(pdf_path)?;
    let content = String::from_utf8_lossy(&data);
    let mut errors = Vec::new();

    if !content.contains("/AcroForm") {
        return Ok(errors);
    }

    let fields = extract_form_fields(pdf_path)?;
    let mut search_pos = 0;
    while let Some(ff_pos) = content[search_pos..].find("/Ff ") {
        let abs_pos = search_pos + ff_pos;
        let after_ff = &content[abs_pos + 4..];
        let flags_str: String = after_ff
            .chars()
            .take_while(|c| c.is_ascii_digit())
            .collect();
        if let Ok(flags) = flags_str.parse::<u32>() {
            let is_required = (flags & (1 << 1)) != 0;
            if is_required {
                let region_start = abs_pos.saturating_sub(200);
                let region = &content[region_start..abs_pos];
                if let Some(t_pos) = region.rfind("/T (") {
                    let after_t = &region[t_pos + 4..];
                    if let Some(close) = after_t.find(')') {
                        let field_name = after_t[..close].to_string();
                        let value = fields.get(&field_name).map(|s| s.as_str()).unwrap_or("");
                        if value.is_empty() {
                            errors.push(ValidationError {
                                field_name: field_name.clone(),
                                error_type: "required".to_string(),
                                message: format!("Required field '{}' is empty", field_name),
                            });
                        }
                    }
                }
            }
        }
        search_pos = abs_pos + 4;
    }

    Ok(errors)
}

/// Form validation error
#[derive(Debug, Clone)]
pub struct ValidationError {
    pub field_name: String,
    pub error_type: String,
    pub message: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_form_field_creation() {
        let field = FormField {
            name: "email".to_string(),
            field_type: FieldType::Email,
            page: 0,
            rect: (100.0, 100.0, 300.0, 120.0),
            value: None,
            default_value: None,
            required: true,
            readonly: false,
            validation: Some(FieldValidation {
                rule_type: ValidationType::Email,
                pattern: None,
                min: None,
                max: None,
                custom_script: None,
            }),
            calculation: None,
        };

        assert_eq!(field.name, "email");
        assert_eq!(field.field_type, FieldType::Email);
        assert!(field.required);
    }

    #[test]
    fn test_xfa_data_binding() {
        let binding = XfaDataBinding {
            field_name: "customer.name".to_string(),
            data_path: "/data/customer/name".to_string(),
            binding_type: XfaBindingType::Normal,
        };

        assert_eq!(binding.binding_type, XfaBindingType::Normal);
    }
}
