//! Template loader and renderer.

use crate::config::Config;
use crate::error::{Result, TemplateRenderError};
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use tera::Tera;

/// Loads and renders Tera templates for code generation.
pub struct TemplateLoader {
    path: PathBuf,
    tera: Tera,
}

/// Template category with its templates.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TemplateCategory {
    pub name: String,
    pub description: String,
    pub templates: Vec<String>,
}

impl TemplateLoader {
    /// Load template library from configuration.
    pub fn new(config: &Config) -> Result<Self> {
        let path = config.templates_dir();

        let glob_pattern = path.join("**/*.tera").to_string_lossy().to_string();
        let mut tera = Tera::new(&glob_pattern).map_err(|e| TemplateRenderError {
            template_name: glob_pattern.clone(),
            reason: format!("Failed to initialize Tera engine: {}", e),
        })?;

        tera.autoescape_on(vec![]);

        Ok(TemplateLoader { path, tera })
    }

    /// Get the templates directory path.
    pub fn templates_dir(&self) -> &std::path::PathBuf {
        &self.path
    }

    /// List all available template categories and their templates.
    pub fn list_templates(&self) -> Result<Vec<TemplateCategory>> {
        let templates_dir = &self.path;
        let mut categories = Vec::new();

        if !templates_dir.exists() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("Templates directory not found: {}", templates_dir.display()),
            )
            .into());
        }

        let category_names = vec![
            "defaults",
            "domain",
            "fragments",
            "schemas",
            "scripts",
            "validators",
        ];
        let category_descriptions = BTreeMap::from([
            ("defaults", "Default value templates for fields"),
            ("domain", "Domain model and schema generation"),
            ("fragments", "Reusable code fragments"),
            ("schemas", "Schema validation and definition"),
            ("scripts", "Infrastructure and deployment scripts"),
            ("validators", "Field validation templates"),
        ]);

        for category_name in category_names {
            let category_dir = templates_dir.join(category_name);
            if !category_dir.exists() {
                continue;
            }

            let mut templates = Vec::new();
            for entry in fs::read_dir(&category_dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_file() {
                    if let Some(filename) = path.file_name() {
                        if let Some(name) = filename.to_str() {
                            templates.push(name.to_string());
                        }
                    }
                }
            }

            templates.sort();

            let description = category_descriptions
                .get(category_name)
                .unwrap_or(&"")
                .to_string();

            categories.push(TemplateCategory {
                name: category_name.to_string(),
                description,
                templates,
            });
        }

        Ok(categories)
    }

    /// Render a template with given context.
    pub fn render(&self, template_name: &str, context: &tera::Context) -> Result<String> {
        self.tera.render(template_name, context).map_err(|e| {
            TemplateRenderError {
                template_name: template_name.to_string(),
                reason: e.to_string(),
            }
            .into()
        })
    }
}
