//! Fragment generator: produces TypeDialog form fragments from domain features.

use crate::error::Result;
use crate::models::{FieldType, ProjectSpec};
use std::path::Path;

/// Generates TypeDialog form fragments from domain features.
pub struct FragmentGenerator;

impl FragmentGenerator {
    /// Generate form fragments for all domain features.
    pub fn generate(spec: &ProjectSpec, output_dir: impl AsRef<Path>) -> Result<()> {
        let output_dir = output_dir.as_ref();
        tracing::info!("Generating form fragments for project: {}", spec.name);

        // Ensure fragments directory exists
        let fragments_dir = output_dir.join("fragments");
        std::fs::create_dir_all(&fragments_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create fragments directory: {}",
                e
            ))
        })?;

        // Generate fragments for each feature
        for feature in &spec.domain_features {
            Self::generate_feature_fragment(spec, feature, &fragments_dir)?;
        }

        // Generate README
        Self::generate_fragments_readme(spec, &fragments_dir)?;

        tracing::info!("Successfully generated form fragments");
        Ok(())
    }

    /// Generate a fragment file for a single feature.
    fn generate_feature_fragment(
        spec: &ProjectSpec,
        feature: &crate::models::DomainFeature,
        fragments_dir: &Path,
    ) -> Result<()> {
        let mut fragment_content = String::new();

        fragment_content.push_str(&format!(
            "# Form fragment for {} feature\n# Auto-generated for project: {}\n\n",
            feature.name, spec.name
        ));

        fragment_content.push_str(&format!("[section.{}]\n", feature.name));

        if let Some(desc) = &feature.description {
            fragment_content.push_str(&format!("description = \"{}\"\n", desc));
        }

        fragment_content.push('\n');

        // Generate field definitions for this feature
        for field in &feature.fields {
            fragment_content.push_str(&Self::generate_field_definition(
                feature,
                field,
                spec,
                fragments_dir,
            )?);
        }

        // Write fragment file
        let fragment_file = fragments_dir.join(format!("{}-section.toml", feature.name));
        std::fs::write(&fragment_file, fragment_content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write fragment file: {}",
                e
            ))
        })?;

        tracing::debug!("Generated fragment for feature: {}", feature.name);
        Ok(())
    }

    /// Generate TOML definition for a single field.
    fn generate_field_definition(
        feature: &crate::models::DomainFeature,
        field: &crate::models::ConfigField,
        spec: &ProjectSpec,
        fragments_dir: &Path,
    ) -> Result<String> {
        let mut field_def = String::new();

        field_def.push_str(&format!("[[section.{}.fields]]\n", feature.name));
        field_def.push_str(&format!("name = \"{}\"\n", field.name));
        field_def.push_str(&format!("prompt = \"{}\"\n", field.prompt));
        field_def.push_str(&format!(
            "type = \"{}\"\n",
            Self::field_type_to_form_type(&field.field_type)
        ));

        // Add nickel_path for hierarchical Nickel output
        let nickel_path = format!("{}.{}", feature.name, field.name);
        field_def.push_str(&format!("nickel_path = \"{}\"\n", nickel_path));

        if let Some(help) = &field.help {
            field_def.push_str(&format!("help = \"{}\"\n", help));
        }

        if let Some(placeholder) = &field.placeholder {
            field_def.push_str(&format!("placeholder = \"{}\"\n", placeholder));
        }

        if !field.required {
            field_def.push_str("required = false\n");
        }

        if field.sensitive {
            field_def.push_str("sensitive = true\n");
            if let Some(backend) = &field.encryption_backend {
                field_def.push_str(&format!("encryption_backend = \"{}\"\n", backend));
            }
        }

        if !field.options.is_empty() {
            field_def.push_str("options = [\n");
            for option in &field.options {
                field_def.push_str(&format!("  \"{}\",\n", option));
            }
            field_def.push_str("]\n");
        }

        // Use constraint interpolation for min/max from constraints.toml
        if field.min.is_some() || field.max.is_some() {
            if field.min.is_some() {
                // Use interpolation if constraint might exist, otherwise hardcode
                field_def.push_str(&format!(
                    "min = \"${{constraint.{}.{}.min}}\"\n",
                    feature.name, field.name
                ));
            }
            if field.max.is_some() {
                field_def.push_str(&format!(
                    "max = \"${{constraint.{}.{}.max}}\"\n",
                    feature.name, field.name
                ));
            }
        }

        // Handle repeating groups - add constraint interpolation for array bounds
        if matches!(field.field_type, FieldType::RepeatingGroup) {
            // Check if this field has array constraints in spec
            let constraint_path = format!("{}.{}", feature.name, field.name);
            if let Some(constraint) = spec.constraints.iter().find(|c| c.path == constraint_path) {
                if constraint.min_items.is_some() {
                    field_def.push_str(&format!(
                        "min_items = \"${{constraint.{}.min_items}}\"\n",
                        constraint_path
                    ));
                }
                if constraint.max_items.is_some() {
                    field_def.push_str(&format!(
                        "max_items = \"${{constraint.{}.max_items}}\"\n",
                        constraint_path
                    ));
                }
                if constraint.unique {
                    field_def.push_str("unique = true\n");
                    if let Some(unique_key) = &constraint.unique_key {
                        field_def.push_str(&format!("unique_key = \"{}\"\n", unique_key));
                    }
                }
            }

            // Reference item fragment if it exists
            field_def.push_str(&format!(
                "item_fragment = \"fragments/{}-item.toml\"\n",
                field.name
            ));

            // Generate item fragment file for this repeating group
            Self::generate_item_fragment(feature, field, fragments_dir)?;
        }

        field_def.push('\n');
        Ok(field_def)
    }

    /// Generate item fragment for repeating group fields.
    fn generate_item_fragment(
        feature: &crate::models::DomainFeature,
        parent_field: &crate::models::ConfigField,
        fragments_dir: &Path,
    ) -> Result<()> {
        // For now, generate a minimal item fragment
        // In a real implementation, this would extract nested fields from the repeating group
        let mut item_content = String::new();

        item_content.push_str(&format!(
            "# Item fragment for {} repeating group\n# Auto-generated for feature: {}\n\n",
            parent_field.name, feature.name
        ));

        item_content.push_str(&format!("[item.{}]\n", parent_field.name));
        item_content.push_str(&format!(
            "description = \"Item definition for {}\"\n\n",
            parent_field.prompt
        ));

        // If the parent field has nested structure, we would generate fields here
        // For now, we indicate this is a placeholder for future enhancement
        item_content.push_str("# Fields for this item would be defined here\n");
        item_content.push_str("# Example:\n");
        item_content.push_str("# [[item.fields]]\n");
        item_content.push_str("# name = \"field_name\"\n");
        item_content.push_str("# type = \"text\"\n");

        let item_file = fragments_dir.join(format!("{}-item.toml", parent_field.name));
        std::fs::write(&item_file, item_content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write item fragment: {}",
                e
            ))
        })?;

        Ok(())
    }

    /// Generate README.md for fragments directory.
    fn generate_fragments_readme(spec: &ProjectSpec, fragments_dir: &Path) -> Result<()> {
        let mut content = String::new();

        content.push_str(&format!("# Form Fragments - {}\n\n", spec.name));

        content.push_str(
            "This directory contains TypeDialog form fragments for configuration collection.\n\n\
             ## Overview\n\n\
             Fragments are modular TOML files that define interactive forms for each domain feature. \
             They use TypeDialog's declarative syntax to create CLI, TUI, or Web forms.\n\n\
             ## Files\n\n",
        );

        for feature in &spec.domain_features {
            content.push_str(&format!("### `{}-section.toml`\n\n", feature.name));
            if let Some(desc) = &feature.description {
                content.push_str(&format!("{}\\n\n", desc));
            }

            content.push_str(&format!("**Fields:** {}\\n\n", feature.fields.len()));

            // List repeating groups if any
            let repeating_fields: Vec<_> = feature
                .fields
                .iter()
                .filter(|f| matches!(f.field_type, FieldType::RepeatingGroup))
                .collect();

            if !repeating_fields.is_empty() {
                content.push_str("**Repeating Groups:**\\n");
                for field in repeating_fields {
                    content.push_str(&format!("- `{}` - {}\\n", field.name, field.prompt));
                }
                content.push('\n');
            }
        }

        content.push_str(
            "## Features\n\n\
             ### Hierarchical Nickel Paths\n\n\
             Fields include `nickel_path` attribute for structured output:\n\n\
             ```toml\n\
             [[section.tracker.fields]]\n\
             name = \"bind_address\"\n\
             nickel_path = \"tracker.bind_address\"\n\
             ```\n\n\
             This generates Nickel output like:\n\n\
             ```nickel\n\
             {\n\
               tracker.bind_address = \"0.0.0.0:6969\",\n\
             }\n\
             ```\n\n\
             ### Constraint Interpolation\n\n\
             Min/max values reference centralized constraints:\n\n\
             ```toml\n\
             min = \"${constraint.tracker.port.min}\"\n\
             max = \"${constraint.tracker.port.max}\"\n\
             ```\n\n\
             This pulls values from `constraints.toml` at runtime, ensuring single source of truth.\n\n\
             ### Repeating Groups\n\n\
             Array fields with item fragments:\n\n\
             ```toml\n\
             type = \"repeating_group\"\n\
             min_items = \"${constraint.tracker.udp_trackers.min_items}\"\n\
             max_items = \"${constraint.tracker.udp_trackers.max_items}\"\n\
             unique = true\n\
             unique_key = \"bind_address\"\n\
             item_fragment = \"fragments/udp_trackers-item.toml\"\n\
             ```\n\n\
             Item fragments define the structure of each array element.\n\n\
             ## Usage\n\n\
             ### CLI Backend\n\n\
             ```bash\n\
             typedialog --form fragments/tracker-section.toml --output config.json\n\
             ```\n\n\
             ### TUI Backend\n\n\
             ```bash\n\
             typedialog-tui --form fragments/tracker-section.toml\n\
             ```\n\n\
             ### Web Backend\n\n\
             ```bash\n\
             typedialog-web --fragments-dir fragments/ --port 8080\n\
             ```\n\n\
             ### Composing Multiple Fragments\n\n\
             Use a master form that includes fragments:\n\n\
             ```toml\n\
             # config-form.toml\n\
             [form]\n\
             name = \"complete-config\"\n\
             \n\
             [[includes]]\n\
             path = \"fragments/tracker-section.toml\"\n\
             \n\
             [[includes]]\n\
             path = \"fragments/database-section.toml\"\n\
             condition = \"${config.enable_database}\"\n\
             ```\n\n\
             ## Integration with Nickel\n\n\
             Fragment output feeds into Nickel roundtrip workflow:\n\n\
             1. **TypeDialog execution** - User fills form → JSON output\n\
             2. **json-to-nickel** - Convert JSON to Nickel syntax\n\
             3. **Validation** - Apply schemas and validators\n\
             4. **Export** - Generate final config files\n\n\
             See `docs/nickel-roundtrip.md` for complete workflow.\n\n",
        );

        let readme_file = fragments_dir.join("README.md");
        std::fs::write(&readme_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write fragments README: {}",
                e
            ))
        })?;

        Ok(())
    }

    /// Map ProjectSpec field types to TypeDialog form field types.
    fn field_type_to_form_type(field_type: &FieldType) -> &'static str {
        match field_type {
            FieldType::Text => "text",
            FieldType::Number => "number",
            FieldType::Password => "password",
            FieldType::Confirm => "confirm",
            FieldType::Select => "select",
            FieldType::MultiSelect => "multi_select",
            FieldType::Editor => "editor",
            FieldType::Date => "date",
            FieldType::RepeatingGroup => "repeating_group",
        }
    }
}
