//! AcroForm and form field support inspired by qpdf's QPDFAcroFormDocumentHelper

use std::collections::HashMap;

use super::error::Result;
use crate::pdf::object::{Array, Dict, Name, ObjRef, Object, PdfString};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldType {
    Text,
    Button,
    Choice,
    Signature,
    Unknown,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonType {
    Pushbutton,
    Checkbox,
    RadioButton,
}

pub struct FormField<'a> {
    field: &'a Dict,
    field_ref: ObjRef,
}

impl<'a> FormField<'a> {
    pub fn new(field: &'a Dict, field_ref: ObjRef) -> Self {
        Self { field, field_ref }
    }

    pub fn field_type(&self) -> FieldType {
        match self.field.get(&Name::new("FT")).and_then(Object::as_name) {
            Some(n) => match n.as_str() {
                "Tx" => FieldType::Text,
                "Btn" => FieldType::Button,
                "Ch" => FieldType::Choice,
                "Sig" => FieldType::Signature,
                _ => FieldType::Unknown,
            },
            None => FieldType::Unknown,
        }
    }

    pub fn fully_qualified_name(&self, objects: &HashMap<ObjRef, Object>) -> String {
        let mut parts = vec![];
        let mut current = Some(self.field_ref);
        while let Some(ref_obj) = current {
            let obj = match objects.get(&ref_obj) {
                Some(Object::Dict(d)) => d,
                _ => break,
            };
            if let Some(Object::String(s)) = obj.get(&Name::new("T")) {
                if let Ok(name) = std::str::from_utf8(s.as_bytes()) {
                    parts.push(name.to_string());
                }
            }
            current = obj.get(&Name::new("Parent")).and_then(Object::as_obj_ref);
        }
        parts.reverse();
        parts.join(".")
    }

    pub fn partial_name(&self) -> Option<&str> {
        self.field
            .get(&Name::new("T"))
            .and_then(Object::as_string)
            .and_then(|s| s.as_str())
    }

    pub fn alternative_name(&self) -> Option<&str> {
        self.field
            .get(&Name::new("TU"))
            .and_then(Object::as_string)
            .and_then(|s| s.as_str())
    }

    pub fn value(&self) -> Option<&Object> {
        self.field.get(&Name::new("V"))
    }

    pub fn value_as_string(&self) -> Option<String> {
        self.field.get(&Name::new("V")).and_then(|o| match o {
            Object::String(s) => s.as_str().map(String::from),
            Object::Name(n) => Some(n.as_str().to_string()),
            _ => None,
        })
    }

    pub fn default_value(&self) -> Option<&Object> {
        self.field.get(&Name::new("DV"))
    }

    pub fn flags(&self) -> u32 {
        self.field
            .get(&Name::new("Ff"))
            .and_then(Object::as_int)
            .map(|i| i as u32)
            .unwrap_or(0)
    }

    pub fn is_read_only(&self) -> bool {
        (self.flags() & (1 << 0)) != 0
    }

    pub fn is_required(&self) -> bool {
        (self.flags() & (1 << 1)) != 0
    }

    pub fn is_text(&self) -> bool {
        self.field_type() == FieldType::Text
    }

    pub fn is_checkbox(&self) -> bool {
        self.field_type() == FieldType::Button
            && (self.flags() & (1 << 15)) == 0
            && (self.flags() & (1 << 16)) != 0
    }

    pub fn is_radio_button(&self) -> bool {
        self.field_type() == FieldType::Button && (self.flags() & (1 << 15)) != 0
    }

    pub fn is_pushbutton(&self) -> bool {
        self.field_type() == FieldType::Button
            && (self.flags() & (1 << 16)) == 0
            && (self.flags() & (1 << 15)) == 0
    }

    pub fn is_choice(&self) -> bool {
        self.field_type() == FieldType::Choice
    }

    pub fn choices(&self) -> Vec<String> {
        self.field
            .get(&Name::new("Opt"))
            .and_then(Object::as_array)
            .map(|arr| {
                arr.iter()
                    .filter_map(|o| match o {
                        Object::String(s) => s.as_str().map(String::from),
                        Object::Array(a) => a
                            .first()
                            .and_then(|o| o.as_string().and_then(|s| s.as_str().map(String::from))),
                        _ => None,
                    })
                    .collect()
            })
            .unwrap_or_default()
    }

    pub fn default_appearance(&self) -> Option<&str> {
        self.field
            .get(&Name::new("DA"))
            .and_then(Object::as_string)
            .and_then(|s| s.as_str())
    }

    pub fn quadding(&self) -> i32 {
        self.field
            .get(&Name::new("Q"))
            .and_then(Object::as_int)
            .map(|i| i as i32)
            .unwrap_or(0)
    }
}

pub struct AcroFormHelper<'a> {
    acroform: &'a Dict,
}

impl<'a> AcroFormHelper<'a> {
    pub fn new(acroform: &'a Dict) -> Self {
        Self { acroform }
    }

    pub fn has_acroform(root: &Dict) -> bool {
        root.get(&Name::new("AcroForm")).is_some()
    }

    pub fn get_fields(&self) -> Vec<&Object> {
        self.acroform
            .get(&Name::new("Fields"))
            .and_then(Object::as_array)
            .map(|arr| arr.iter().collect())
            .unwrap_or_default()
    }

    pub fn needs_appearances(&self) -> bool {
        self.acroform
            .get(&Name::new("NeedAppearances"))
            .and_then(Object::as_bool)
            .unwrap_or(false)
    }

    pub fn default_resources(&self) -> Option<&Object> {
        self.acroform.get(&Name::new("DR"))
    }

    pub fn default_appearance(&self) -> Option<&str> {
        self.acroform
            .get(&Name::new("DA"))
            .and_then(Object::as_string)
            .and_then(|s| s.as_str())
    }
}

pub struct AcroFormHelperMut<'a> {
    acroform: &'a mut Dict,
}

impl<'a> AcroFormHelperMut<'a> {
    pub fn new(acroform: &'a mut Dict) -> Self {
        Self { acroform }
    }

    pub fn set_needs_appearances(&mut self, needs: bool) {
        self.acroform
            .insert(Name::new("NeedAppearances"), Object::Bool(needs));
    }

    pub fn add_field(&mut self, field_ref: ObjRef) {
        let key = Name::new("Fields");
        let existing = self.acroform.get(&key).cloned();
        match existing {
            Some(Object::Array(mut arr)) => {
                arr.push(Object::Ref(field_ref));
                self.acroform.insert(key, Object::Array(arr));
            }
            Some(obj) => {
                self.acroform
                    .insert(key, Object::Array(vec![obj, Object::Ref(field_ref)]));
            }
            None => {
                self.acroform
                    .insert(key, Object::Array(vec![Object::Ref(field_ref)]));
            }
        }
    }

    pub fn remove_field(&mut self, field_ref: ObjRef) -> bool {
        let key = Name::new("Fields");
        let arr = match self.acroform.get(&key).and_then(Object::as_array) {
            Some(a) => a.clone(),
            None => return false,
        };
        let original_len = arr.len();
        let new_arr: Array = arr
            .into_iter()
            .filter(|o| !matches!(o, Object::Ref(r) if *r == field_ref))
            .collect();
        let removed = new_arr.len() < original_len;
        self.acroform.insert(key, Object::Array(new_arr));
        removed
    }

    pub fn disable_digital_signatures(&mut self) {
        self.acroform.remove(&Name::new("SigFlags"));
    }
}

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

    fn make_text_field() -> Dict {
        let mut dict = HashMap::new();
        dict.insert(Name::new("FT"), Object::Name(Name::new("Tx")));
        dict.insert(
            Name::new("T"),
            Object::String(PdfString::new(b"field1".to_vec())),
        );
        dict.insert(
            Name::new("V"),
            Object::String(PdfString::new(b"value".to_vec())),
        );
        dict.insert(Name::new("Ff"), Object::Int(1)); // ReadOnly
        dict
    }

    fn make_checkbox_field() -> Dict {
        let mut dict = HashMap::new();
        dict.insert(Name::new("FT"), Object::Name(Name::new("Btn")));
        dict.insert(Name::new("Ff"), Object::Int(1 << 16)); // Checkbox
        dict
    }

    #[test]
    fn test_field_type_detection() {
        let text_field = make_text_field();
        let field = FormField::new(&text_field, ObjRef::new(1, 0));
        assert_eq!(field.field_type(), FieldType::Text);
        assert!(field.is_text());

        let cb_field = make_checkbox_field();
        let field = FormField::new(&cb_field, ObjRef::new(2, 0));
        assert_eq!(field.field_type(), FieldType::Button);
        assert!(field.is_checkbox());
    }

    #[test]
    fn test_partial_name() {
        let dict = make_text_field();
        let field = FormField::new(&dict, ObjRef::new(1, 0));
        assert_eq!(field.partial_name(), Some("field1"));
    }

    #[test]
    fn test_value_as_string() {
        let dict = make_text_field();
        let field = FormField::new(&dict, ObjRef::new(1, 0));
        assert_eq!(field.value_as_string(), Some("value".to_string()));
    }

    #[test]
    fn test_flags() {
        let dict = make_text_field();
        let field = FormField::new(&dict, ObjRef::new(1, 0));
        assert!(field.is_read_only());
    }

    #[test]
    fn test_acroform_helper() {
        let mut acroform = HashMap::new();
        acroform.insert(Name::new("Fields"), Object::Array(vec![]));
        acroform.insert(Name::new("NeedAppearances"), Object::Bool(true));

        let helper = AcroFormHelper::new(&acroform);
        assert!(helper.needs_appearances());
        assert_eq!(helper.get_fields().len(), 0);
    }

    #[test]
    fn test_acroform_mut() {
        let mut acroform = HashMap::new();
        acroform.insert(Name::new("Fields"), Object::Array(vec![]));
        acroform.insert(Name::new("SigFlags"), Object::Int(3));

        let mut helper = AcroFormHelperMut::new(&mut acroform);
        helper.set_needs_appearances(true);
        helper.add_field(ObjRef::new(5, 0));
        helper.disable_digital_signatures();

        let helper = AcroFormHelper::new(&acroform);
        assert_eq!(helper.get_fields().len(), 1);
        assert!(!acroform.contains_key(&Name::new("SigFlags")));
    }
}
