//! TOML Form Generator
//!
//! Converts Nickel schema intermediate representation (NickelSchemaIR)
//! into typedialog FormDefinition TOML format.
//!
//! Handles type mapping, metadata extraction, flatten/unflatten operations,
//! semantic grouping, and conditional expression inference from contracts.

use super::contracts::ContractAnalyzer;
use super::schema_ir::{NickelFieldIR, NickelSchemaIR, NickelType};
use crate::error::Result;
use crate::form_parser::{DisplayItem, FieldDefinition, FieldType, FormDefinition, SelectOption};
use std::collections::HashMap;

/// Generator for converting Nickel schemas to typedialog TOML forms
pub struct TomlGenerator;

impl TomlGenerator {
    /// Convert a Nickel schema IR to a typedialog FormDefinition
    ///
    /// # Arguments
    ///
    /// * `schema` - The Nickel schema intermediate representation
    /// * `flatten_records` - Whether to flatten nested records into flat field names
    /// * `use_groups` - Whether to use semantic grouping for form organization
    ///
    /// # Returns
    ///
    /// FormDefinition ready to be serialized to TOML
    pub fn generate(
        schema: &NickelSchemaIR,
        flatten_records: bool,
        use_groups: bool,
    ) -> Result<FormDefinition> {
        let mut fields = Vec::new();
        let mut items = Vec::new();
        let mut group_order: HashMap<String, usize> = HashMap::new();
        let mut current_order = 0;

        // First pass: collect all groups
        if use_groups {
            for field in &schema.fields {
                if let Some(group) = &field.group {
                    group_order.entry(group.clone()).or_insert_with(|| {
                        let order = current_order;
                        current_order += 1;
                        order
                    });
                }
            }
        }

        // Generate display items for groups (headers)
        let mut item_order = 0;
        if use_groups {
            for group in &schema
                .fields
                .iter()
                .filter_map(|f| f.group.as_ref())
                .collect::<std::collections::HashSet<_>>()
            {
                items.push(DisplayItem {
                    name: format!("{}_header", group),
                    item_type: "section".to_string(),
                    title: Some(format_group_title(group)),
                    border_top: Some(true),
                    group: Some(group.to_string()),
                    order: item_order,
                    content: None,
                    template: None,
                    border_bottom: None,
                    margin_left: None,
                    border_margin_left: None,
                    content_margin_left: None,
                    align: None,
                    when: None,
                    includes: None,
                    border_top_char: None,
                    border_top_len: None,
                    border_top_l: None,
                    border_top_r: None,
                    border_bottom_char: None,
                    border_bottom_len: None,
                    border_bottom_l: None,
                    border_bottom_r: None,
                    i18n: None,
                });
                item_order += 1;
            }
        }

        // Second pass: generate fields
        let mut field_order = item_order + 100; // Offset to allow items to display first
        for field in &schema.fields {
            let form_field =
                Self::field_ir_to_definition(field, flatten_records, field_order, schema)?;
            fields.push(form_field);
            field_order += 1;
        }

        Ok(FormDefinition {
            name: schema.name.clone(),
            description: schema.description.clone(),
            fields,
            items,
            elements: Vec::new(),
            locale: None,
            template: None,
            output_template: None,
            i18n_prefix: None,
            display_mode: Default::default(),
        })
    }

    /// Generate forms with fragments
    ///
    /// Creates multiple FormDefinition objects: one main form with includes for each fragment,
    /// and separate forms for each fragment containing only its fields.
    ///
    /// # Returns
    ///
    /// HashMap with "main" key for main form, and fragment names for fragment forms
    pub fn generate_with_fragments(
        schema: &NickelSchemaIR,
        flatten_records: bool,
        _use_groups: bool,
    ) -> Result<HashMap<String, FormDefinition>> {
        let mut result = HashMap::new();

        // Get all fragments and ungrouped fields
        let fragments = schema.fragments();
        let ungrouped_fields = schema.fields_without_fragment();

        // Generate main form with includes
        let mut main_items = Vec::new();
        let mut main_fields = Vec::new();
        let mut item_order = 0;
        let mut field_order = 100;

        // Add ungrouped fields to main form
        if !ungrouped_fields.is_empty() {
            for field in ungrouped_fields {
                // Check if this is an array-of-records field
                if field.is_array_of_records {
                    // Generate fragment for array element
                    let fragment_name = format!("{}_item", field.flat_name);

                    if let Some(element_fields) = &field.array_element_fields {
                        let fragment_form = Self::create_fragment_from_fields(
                            &fragment_name,
                            element_fields,
                            flatten_records,
                            schema,
                        )?;

                        result.insert(fragment_name.clone(), fragment_form);

                        // Generate RepeatingGroup field in main form
                        let repeating_field =
                            Self::create_repeating_group_field(field, &fragment_name, field_order)?;

                        main_fields.push(repeating_field);
                        field_order += 1;
                    }
                } else {
                    // Normal field
                    let form_field =
                        Self::field_ir_to_definition(field, flatten_records, field_order, schema)?;
                    main_fields.push(form_field);
                    field_order += 1;
                }
            }
        }

        // Add includes for each fragment
        for fragment in &fragments {
            item_order += 1;
            main_items.push(DisplayItem {
                name: format!("{}_group", fragment),
                item_type: "group".to_string(),
                title: Some(format_group_title(fragment)),
                includes: Some(vec![format!("fragments/{}.toml", fragment)]),
                group: Some(fragment.clone()),
                order: item_order,
                content: None,
                template: None,
                border_top: None,
                border_bottom: None,
                margin_left: None,
                border_margin_left: None,
                content_margin_left: None,
                align: None,
                when: None,
                border_top_char: None,
                border_top_len: None,
                border_top_l: None,
                border_top_r: None,
                border_bottom_char: None,
                border_bottom_len: None,
                border_bottom_l: None,
                border_bottom_r: None,
                i18n: None,
            });
        }

        // Create main form
        let main_form = FormDefinition {
            name: format!("{}_main", schema.name),
            description: schema.description.clone(),
            fields: main_fields,
            items: main_items,
            elements: Vec::new(),
            locale: None,
            template: None,
            output_template: None,
            i18n_prefix: None,
            display_mode: Default::default(),
        };

        result.insert("main".to_string(), main_form);

        // Generate forms for each fragment
        for fragment in &fragments {
            let fragment_fields = schema.fields_by_fragment(fragment);

            let mut fields = Vec::new();

            for (field_order, field) in fragment_fields.into_iter().enumerate() {
                let form_field =
                    Self::field_ir_to_definition(field, flatten_records, field_order, schema)?;
                fields.push(form_field);
            }

            let fragment_form = FormDefinition {
                name: format!("{}_fragment", fragment),
                description: Some(format!("Fragment: {}", fragment)),
                fields,
                items: Vec::new(),
                elements: Vec::new(),
                locale: None,
                template: None,
                output_template: None,
                i18n_prefix: None,
                display_mode: Default::default(),
            };

            result.insert(fragment.clone(), fragment_form);
        }

        Ok(result)
    }

    /// Convert a single NickelFieldIR to a FieldDefinition
    fn field_ir_to_definition(
        field: &NickelFieldIR,
        _flatten_records: bool,
        order: usize,
        schema: &NickelSchemaIR,
    ) -> Result<FieldDefinition> {
        let (field_type, custom_type) = Self::nickel_type_to_field_type(&field.nickel_type)?;

        let prompt = field
            .doc
            .clone()
            .unwrap_or_else(|| format_prompt_from_path(&field.flat_name));

        let default = field.default.as_ref().map(|v| match v {
            serde_json::Value::String(s) => s.clone(),
            serde_json::Value::Number(n) => n.to_string(),
            serde_json::Value::Bool(b) => b.to_string(),
            serde_json::Value::Null => String::new(),
            other => other.to_string(),
        });

        let options = match &field.nickel_type {
            NickelType::Array(_) => {
                // Try to extract enum options from array element type or doc
                Self::extract_enum_options(field)
            }
            _ => Vec::new(),
        };

        // Determine if field is required
        let required = if field.optional {
            Some(false)
        } else {
            Some(true)
        };

        // Infer conditional expression from contracts
        let when_condition = ContractAnalyzer::infer_condition(field, schema);

        // Map encryption metadata to field encryption settings
        let (sensitive, encryption_backend, encryption_config) =
            if let Some(enc_meta) = &field.encryption_metadata {
                let sensitive = Some(enc_meta.sensitive);
                let encryption_backend = enc_meta.backend.clone();
                let encryption_config = enc_meta.key.clone().map(|key| {
                    let mut config = std::collections::HashMap::new();
                    config.insert("key".to_string(), key);
                    config
                });
                (sensitive, encryption_backend, encryption_config)
            } else {
                (None, None, None)
            };

        Ok(FieldDefinition {
            // Use alias if present (semantic name), otherwise use flat_name
            name: field
                .alias
                .clone()
                .unwrap_or_else(|| field.flat_name.clone()),
            field_type,
            prompt,
            default,
            placeholder: None,
            options,
            options_from: None,
            required,
            file_extension: None,
            prefix_text: None,
            page_size: None,
            vim_mode: None,
            display_mode: None,
            searchable: None,
            min_selected: None,
            max_selected: None,
            custom_type,
            min_date: None,
            max_date: None,
            week_start: None,
            order,
            when: when_condition,
            when_false: Default::default(),
            i18n: None,
            group: field.group.clone(),
            nickel_contract: field.contract.clone(),
            nickel_path: Some(field.path.clone()),
            nickel_doc: field.doc.clone(),
            nickel_alias: field.alias.clone(),
            fragment: None,
            min_items: None,
            max_items: None,
            default_items: None,
            unique: None,
            unique_key: None,
            sensitive,
            encryption_backend,
            encryption_config,
        })
    }

    /// Map a Nickel type to typedialog field type
    fn nickel_type_to_field_type(nickel_type: &NickelType) -> Result<(FieldType, Option<String>)> {
        match nickel_type {
            NickelType::String => Ok((FieldType::Text, None)),
            NickelType::Number => Ok((FieldType::Custom, Some("f64".to_string()))),
            NickelType::Bool => Ok((FieldType::Confirm, None)),
            NickelType::Array(elem_type) => {
                // Check if this is an array of records (repeating group)
                if matches!(elem_type.as_ref(), NickelType::Record(_)) {
                    // Array of records -> use RepeatingGroup
                    Ok((FieldType::RepeatingGroup, None))
                } else {
                    // Simple arrays -> use Editor with JSON for now
                    // (could be enhanced to MultiSelect if options are detected)
                    Ok((FieldType::Editor, Some("json".to_string())))
                }
            }
            NickelType::Record(_) => {
                // Records are handled by nested field generation
                Ok((FieldType::Text, None))
            }
            NickelType::Custom(type_name) => {
                // Unknown types map to custom with type name
                Ok((FieldType::Custom, Some(type_name.clone())))
            }
        }
    }

    /// Extract enum options from field documentation or array structure
    /// Returns Vec of SelectOption (with value only, no labels)
    fn extract_enum_options(field: &NickelFieldIR) -> Vec<SelectOption> {
        // Check if doc contains "Options: X, Y, Z" pattern
        if let Some(doc) = &field.doc {
            if let Some(start) = doc.find("Options:") {
                let options_str = &doc[start + 8..]; // Skip "Options:"
                let options: Vec<SelectOption> = options_str
                    .split(',')
                    .map(|s| SelectOption {
                        value: s.trim().to_string(),
                        label: None,
                    })
                    .filter(|opt| !opt.value.is_empty())
                    .collect();
                if !options.is_empty() {
                    return options;
                }
            }
        }

        // For now, don't try to extract from array structure unless we have more info
        Vec::new()
    }

    /// Create a FormDefinition fragment from array element fields
    fn create_fragment_from_fields(
        name: &str,
        element_fields: &[NickelFieldIR],
        flatten_records: bool,
        schema: &NickelSchemaIR,
    ) -> Result<FormDefinition> {
        let mut fields = Vec::new();

        // Generate FieldDefinition for each element field
        for (order, elem_field) in element_fields.iter().enumerate() {
            let field_def =
                Self::field_ir_to_definition(elem_field, flatten_records, order, schema)?;
            fields.push(field_def);
        }

        Ok(FormDefinition {
            name: name.to_string(),
            description: Some(format!("Array element definition for {}", name)),
            fields,
            items: Vec::new(),
            elements: Vec::new(),
            locale: None,
            template: None,
            output_template: None,
            i18n_prefix: None,
            display_mode: Default::default(),
        })
    }

    /// Create a RepeatingGroup FieldDefinition pointing to a fragment
    fn create_repeating_group_field(
        field: &NickelFieldIR,
        fragment_name: &str,
        order: usize,
    ) -> Result<FieldDefinition> {
        let prompt = field
            .doc
            .as_ref()
            .map(|d| d.lines().next().unwrap_or("").to_string())
            .unwrap_or_else(|| {
                field
                    .alias
                    .clone()
                    .unwrap_or_else(|| field.flat_name.clone())
            });

        // Map encryption metadata to field encryption settings
        let (sensitive, encryption_backend, encryption_config) =
            if let Some(enc_meta) = &field.encryption_metadata {
                let sensitive = Some(enc_meta.sensitive);
                let encryption_backend = enc_meta.backend.clone();
                let encryption_config = enc_meta.key.clone().map(|key| {
                    let mut config = std::collections::HashMap::new();
                    config.insert("key".to_string(), key);
                    config
                });
                (sensitive, encryption_backend, encryption_config)
            } else {
                (None, None, None)
            };

        Ok(FieldDefinition {
            name: field
                .alias
                .clone()
                .unwrap_or_else(|| field.flat_name.clone()),
            field_type: FieldType::RepeatingGroup,
            prompt,
            default: None,
            placeholder: None,
            options: Vec::new(),
            options_from: None,
            required: Some(!field.optional),
            file_extension: None,
            prefix_text: None,
            page_size: None,
            vim_mode: None,
            display_mode: None,
            searchable: None,
            min_selected: None,
            max_selected: None,
            custom_type: None,
            min_date: None,
            max_date: None,
            week_start: None,
            order,
            when: None,
            when_false: Default::default(),
            i18n: None,
            group: field.group.clone(),
            nickel_contract: field.contract.clone(),
            nickel_path: Some(field.path.clone()),
            nickel_doc: field.doc.clone(),
            nickel_alias: field.alias.clone(),
            fragment: Some(format!("fragments/{}.toml", fragment_name)),
            min_items: if field.optional { Some(0) } else { Some(1) },
            max_items: Some(10), // Default limit
            default_items: Some(if field.optional { 0 } else { 1 }),
            unique: None,
            unique_key: None,
            sensitive,
            encryption_backend,
            encryption_config,
        })
    }
}

/// Format a group title from group name
fn format_group_title(group: &str) -> String {
    // Convert snake_case or kebab-case to Title Case
    group
        .split(['_', '-'])
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                None => String::new(),
                Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

/// Format a prompt from field name
fn format_prompt_from_path(flat_name: &str) -> String {
    // Convert snake_case to Title Case
    flat_name
        .split('_')
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                None => String::new(),
                Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

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

    #[test]
    fn test_generate_simple_schema() {
        let schema = NickelSchemaIR {
            name: "test_schema".to_string(),
            description: Some("A test schema".to_string()),
            fields: vec![
                NickelFieldIR {
                    path: vec!["name".to_string()],
                    flat_name: "name".to_string(),
                    alias: None,
                    nickel_type: NickelType::String,
                    doc: Some("User full name".to_string()),
                    default: Some(json!("Alice")),
                    optional: false,
                    contract: Some("String | std.string.NonEmpty".to_string()),
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec!["age".to_string()],
                    flat_name: "age".to_string(),
                    alias: None,
                    nickel_type: NickelType::Number,
                    doc: Some("User age".to_string()),
                    default: None,
                    optional: true,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
            ],
        };

        let form = TomlGenerator::generate(&schema, false, false).unwrap();
        assert_eq!(form.name, "test_schema");
        assert_eq!(form.fields.len(), 2);

        // Check first field
        assert_eq!(form.fields[0].name, "name");
        assert_eq!(form.fields[0].field_type, FieldType::Text);
        assert_eq!(form.fields[0].required, Some(true));
        assert_eq!(
            form.fields[0].nickel_contract,
            Some("String | std.string.NonEmpty".to_string())
        );

        // Check second field
        assert_eq!(form.fields[1].name, "age");
        assert_eq!(form.fields[1].field_type, FieldType::Custom);
        assert_eq!(form.fields[1].custom_type, Some("f64".to_string()));
        assert_eq!(form.fields[1].required, Some(false));
    }

    #[test]
    fn test_generate_with_groups() {
        let schema = NickelSchemaIR {
            name: "grouped_schema".to_string(),
            description: None,
            fields: vec![
                NickelFieldIR {
                    path: vec!["user_name".to_string()],
                    flat_name: "user_name".to_string(),
                    alias: None,
                    nickel_type: NickelType::String,
                    doc: Some("User name".to_string()),
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: Some("user".to_string()),
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec!["settings_theme".to_string()],
                    flat_name: "settings_theme".to_string(),
                    alias: None,
                    nickel_type: NickelType::String,
                    doc: Some("Theme preference".to_string()),
                    default: Some(json!("dark")),
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: Some("settings".to_string()),
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
            ],
        };

        let form = TomlGenerator::generate(&schema, false, true).unwrap();

        // Should have display items for groups
        assert!(!form.items.is_empty());

        // Check fields are grouped
        assert_eq!(form.fields[0].group, Some("user".to_string()));
        assert_eq!(form.fields[1].group, Some("settings".to_string()));
    }

    #[test]
    fn test_nickel_type_to_field_type() {
        let (field_type, custom_type) =
            TomlGenerator::nickel_type_to_field_type(&NickelType::String).unwrap();
        assert_eq!(field_type, FieldType::Text);
        assert_eq!(custom_type, None);

        let (field_type, custom_type) =
            TomlGenerator::nickel_type_to_field_type(&NickelType::Number).unwrap();
        assert_eq!(field_type, FieldType::Custom);
        assert_eq!(custom_type, Some("f64".to_string()));

        let (field_type, custom_type) =
            TomlGenerator::nickel_type_to_field_type(&NickelType::Bool).unwrap();
        assert_eq!(field_type, FieldType::Confirm);
        assert_eq!(custom_type, None);
    }

    #[test]
    fn test_format_group_title() {
        assert_eq!(format_group_title("user"), "User");
        assert_eq!(format_group_title("user_settings"), "User Settings");
        assert_eq!(format_group_title("api-config"), "Api Config");
    }

    #[test]
    fn test_format_prompt_from_path() {
        assert_eq!(format_prompt_from_path("name"), "Name");
        assert_eq!(format_prompt_from_path("user_name"), "User Name");
        assert_eq!(format_prompt_from_path("first_name"), "First Name");
    }

    #[test]
    fn test_extract_enum_options() {
        let field = NickelFieldIR {
            path: vec!["status".to_string()],
            flat_name: "status".to_string(),
            alias: None,
            nickel_type: NickelType::Array(Box::new(NickelType::String)),
            doc: Some("Status. Options: pending, active, completed".to_string()),
            default: None,
            optional: false,
            contract: None,
            contract_call: None,
            group: None,
            fragment_marker: None,
            is_array_of_records: false,
            array_element_fields: None,
            encryption_metadata: None,
        };

        let options = TomlGenerator::extract_enum_options(&field);
        assert_eq!(options.len(), 3);
        assert_eq!(options[0].value, "pending");
        assert_eq!(options[1].value, "active");
        assert_eq!(options[2].value, "completed");
        // Labels should be None for options extracted from doc strings
        assert_eq!(options[0].label, None);
        assert_eq!(options[1].label, None);
        assert_eq!(options[2].label, None);
    }

    #[test]
    fn test_default_value_conversion() {
        let field = NickelFieldIR {
            path: vec!["count".to_string()],
            flat_name: "count".to_string(),
            alias: None,
            nickel_type: NickelType::Number,
            doc: None,
            default: Some(json!(42)),
            optional: false,
            contract: None,
            contract_call: None,
            group: None,
            fragment_marker: None,
            is_array_of_records: false,
            array_element_fields: None,
            encryption_metadata: None,
        };

        let schema = NickelSchemaIR {
            name: "test".to_string(),
            description: None,
            fields: vec![field.clone()],
        };

        let form_field = TomlGenerator::field_ir_to_definition(&field, false, 0, &schema).unwrap();
        assert_eq!(form_field.default, Some("42".to_string()));
    }

    #[test]
    fn test_array_of_records_detection_and_fragment_generation() {
        // Create a field with Array(Record(...)) type
        let tracker_field = NickelFieldIR {
            path: vec!["bind_address".to_string()],
            flat_name: "bind_address".to_string(),
            alias: None,
            nickel_type: NickelType::String,
            doc: Some("Bind Address".to_string()),
            default: Some(json!("0.0.0.0:6969")),
            optional: false,
            contract: None,
            contract_call: None,
            group: None,
            fragment_marker: None,
            is_array_of_records: false,
            array_element_fields: None,
            encryption_metadata: None,
        };

        let udp_trackers_field = NickelFieldIR {
            path: vec!["udp_trackers".to_string()],
            flat_name: "udp_trackers".to_string(),
            alias: Some("trackers".to_string()),
            nickel_type: NickelType::Array(Box::new(NickelType::Record(vec![
                tracker_field.clone()
            ]))),
            doc: Some("UDP Tracker Listeners".to_string()),
            default: None,
            optional: true,
            contract: None,
            contract_call: None,
            group: None,
            fragment_marker: None,
            is_array_of_records: true,
            array_element_fields: Some(vec![tracker_field.clone()]),
            encryption_metadata: None,
        };

        let schema = NickelSchemaIR {
            name: "tracker_config".to_string(),
            description: Some("Torrust Tracker Configuration".to_string()),
            fields: vec![udp_trackers_field.clone()],
        };

        // Test fragment generation
        let forms = TomlGenerator::generate_with_fragments(&schema, true, false).unwrap();

        // Should have main form + fragment form
        assert!(forms.contains_key("main"));
        assert!(
            forms.contains_key("udp_trackers_item"),
            "Should generate fragment for array element"
        );

        // Check main form has RepeatingGroup field
        let main_form = forms.get("main").unwrap();
        assert_eq!(main_form.fields.len(), 1);
        assert_eq!(main_form.fields[0].field_type, FieldType::RepeatingGroup);
        assert_eq!(main_form.fields[0].name, "trackers"); // Uses alias
        assert_eq!(
            main_form.fields[0].fragment,
            Some("fragments/udp_trackers_item.toml".to_string())
        );
        assert_eq!(main_form.fields[0].min_items, Some(0)); // Optional
        assert_eq!(main_form.fields[0].max_items, Some(10));
        assert_eq!(main_form.fields[0].default_items, Some(0));

        // Check fragment form has element fields
        let fragment_form = forms.get("udp_trackers_item").unwrap();
        assert_eq!(fragment_form.fields.len(), 1);
        assert_eq!(fragment_form.fields[0].name, "bind_address");
        assert_eq!(fragment_form.fields[0].field_type, FieldType::Text);
    }

    #[test]
    fn test_nickel_type_array_of_records_maps_to_repeating_group() {
        // Test that Array(Record(...)) maps to RepeatingGroup
        let record_type = NickelType::Record(vec![]);
        let array_of_records = NickelType::Array(Box::new(record_type));

        let (field_type, custom_type) =
            TomlGenerator::nickel_type_to_field_type(&array_of_records).unwrap();
        assert_eq!(field_type, FieldType::RepeatingGroup);
        assert_eq!(custom_type, None);

        // Test that simple arrays still map to Editor
        let simple_array = NickelType::Array(Box::new(NickelType::String));
        let (field_type, custom_type) =
            TomlGenerator::nickel_type_to_field_type(&simple_array).unwrap();
        assert_eq!(field_type, FieldType::Editor);
        assert_eq!(custom_type, Some("json".to_string()));
    }
}
