//! Tera template engine setup and utilities

use super::DocumentationContext;
use std::collections::HashMap;
use tera::{Context, Tera, Value};
use thiserror::Error;

/// Template engine errors
#[derive(Error, Debug)]
pub enum TemplateError {
    #[error("Template not found: {template}")]
    NotFound { template: String },

    #[error("Template rendering failed: {source}")]
    RenderFailed {
        #[from]
        source: tera::Error,
    },

    #[error("Template loading failed: {message}")]
    LoadFailed { message: String },

    #[error("I18n template not found for language: {language}")]
    I18nNotFound { language: String },
}

/// Template engine wrapper with i18n support
pub struct DocumentationTemplateEngine {
    /// Main Tera engine instance
    tera: Tera,
    /// Available languages
    _languages: Vec<String>,
    /// Default language for fallbacks
    _default_language: String,
}

impl DocumentationTemplateEngine {
    /// Create new template engine with external templates
    /// Returns None if templates are not found (graceful degradation)
    pub fn new() -> Option<Self> {
        let templates_path =
            std::env::var("SITE_TEMPLATES_PATH").unwrap_or_else(|_| "site/templates".to_string());

        let build_tools_templates_dir = std::path::Path::new(&templates_path).join("build-tools");
        let build_tools_templates_pattern = format!("{}/build-tools/**/*", templates_path);

        // Check if templates directory exists
        if !build_tools_templates_dir.exists() {
            eprintln!("🚨 BIG WARNING: Site-info templates directory not found!");
            eprintln!("   Expected: {}", build_tools_templates_dir.display());
            eprintln!("   SITE_TEMPLATES_PATH: {}", templates_path);
            eprintln!("   📝 No documentation will be generated until templates are available.");
            eprintln!("   ℹ️  Create templates or run setup to initialize the template system.");
            return None;
        }

        // Check for required template files
        let required_templates = [
            "documentation/base.tera",
            "documentation/summary.tera",
            "documentation/reference.tera",
            "partials/route_table.tera",
            "partials/source_link.tera",
        ];

        let mut missing_templates = Vec::new();
        for template in &required_templates {
            let template_path = build_tools_templates_dir.join(template);
            if !template_path.exists() {
                missing_templates.push(template);
            }
        }

        if !missing_templates.is_empty() {
            eprintln!("🚨 BIG WARNING: Required build-tools templates are missing!");
            eprintln!("   Missing templates:");
            for template in &missing_templates {
                eprintln!("   - {}/{}", build_tools_templates_dir.display(), template);
            }
            eprintln!("   📝 No documentation will be generated until all required templates are available.");
            eprintln!("   ℹ️  Run template setup or restore missing template files.");
            return None;
        }

        // Attempt to load templates
        let mut tera = match Tera::new(&build_tools_templates_pattern) {
            Ok(tera) => tera,
            Err(e) => {
                eprintln!("🚨 BIG WARNING: Failed to load build-tools templates!");
                eprintln!("   Template pattern: {}", build_tools_templates_pattern);
                eprintln!("   Error: {}", e);
                eprintln!(
                    "   📝 No documentation will be generated due to template loading errors."
                );
                eprintln!("   ℹ️  Check template syntax and file permissions.");
                return None;
            }
        };

        // Register custom filters and functions (ignore errors in graceful mode)
        if let Err(e) = register_custom_filters(&mut tera) {
            eprintln!("🚨 BIG WARNING: Failed to register template filters!");
            eprintln!("   Error: {}", e);
            eprintln!(
                "   📝 No documentation will be generated due to filter registration errors."
            );
            return None;
        }

        if let Err(e) = register_custom_functions(&mut tera) {
            eprintln!("🚨 BIG WARNING: Failed to register template functions!");
            eprintln!("   Error: {}", e);
            eprintln!(
                "   📝 No documentation will be generated due to function registration errors."
            );
            return None;
        }

        Some(Self {
            tera,
            _languages: vec!["en".to_string(), "es".to_string()],
            _default_language: "en".to_string(),
        })
    }

    /// Render documentation using template
    /// Returns None if rendering fails (graceful degradation)
    pub fn render_documentation(
        &self,
        template_name: &str,
        context: &DocumentationContext,
    ) -> Option<String> {
        // Try language-specific template first
        let lang_template = format!("{}/{}.tera", context.i18n.language, template_name);
        let default_template = format!("documentation/{}.tera", template_name);

        let template_to_use = if self
            .tera
            .get_template_names()
            .any(|name| name == lang_template)
        {
            &lang_template
        } else {
            &default_template
        };

        let mut tera_context = Context::new();
        tera_context.insert("doc", context);
        tera_context.insert("theme", &context.theme);
        tera_context.insert("i18n", &context.i18n);
        tera_context.insert("metadata", &context.metadata);
        tera_context.insert("summary", &context.summary);

        match self.tera.render(template_to_use, &tera_context) {
            Ok(rendered) => Some(rendered),
            Err(e) => {
                eprintln!("🚨 BIG WARNING: Template rendering failed!");
                eprintln!("   Template: {}", template_to_use);
                eprintln!("   Error: {}", e);
                eprintln!("   📝 Skipping {} documentation generation.", template_name);
                eprintln!("   ℹ️  Check template syntax and context data.");
                None
            }
        }
    }

    /// Get available template names
    pub fn available_templates(&self) -> Vec<String> {
        self.tera
            .get_template_names()
            .map(|s| s.to_string())
            .collect()
    }

    /// Check if language-specific template exists
    pub fn has_language_template(&self, template_name: &str, language: &str) -> bool {
        let lang_template = format!("{}/{}.tera", language, template_name);
        self.tera
            .get_template_names()
            .any(|name| name == lang_template)
    }

    /// Add custom template from string
    pub fn add_template(&mut self, name: &str, template: &str) -> Result<(), TemplateError> {
        self.tera
            .add_raw_template(name, template)
            .map_err(|e| TemplateError::LoadFailed {
                message: format!("Failed to add template {}: {}", name, e),
            })
    }
}

/// Register custom Tera filters for documentation
fn register_custom_filters(tera: &mut Tera) -> Result<(), TemplateError> {
    // Filter for generating kebab-case anchors
    tera.register_filter(
        "anchor",
        |value: &Value, _: &HashMap<String, Value>| match value.as_str() {
            Some(s) => Ok(Value::String(generate_anchor(s))),
            None => Err(tera::Error::msg("anchor filter requires string input")),
        },
    );

    // Filter for formatting source links with item names
    tera.register_filter(
        "source_link",
        |value: &Value, args: &HashMap<String, Value>| match value.as_object() {
            Some(obj) => {
                let path = obj.get("path").and_then(|v| v.as_str()).unwrap_or("");
                let line = obj.get("line").and_then(|v| v.as_u64()).unwrap_or(0);
                let context = obj.get("context").and_then(|v| v.as_str()).unwrap_or("");
                let item_name = args
                    .get("item_name")
                    .and_then(|v| v.as_str())
                    .unwrap_or("source");

                Ok(Value::String(format!(
                    "[{} source]({}) line: {} context: **{}**",
                    item_name, path, line, context
                )))
            }
            None => Err(tera::Error::msg("source_link filter requires object input")),
        },
    );

    // Filter for pluralization
    tera.register_filter(
        "pluralize",
        |value: &Value, args: &HashMap<String, Value>| {
            let count = value.as_u64().unwrap_or(0);
            let singular = args
                .get("singular")
                .and_then(|v| v.as_str())
                .unwrap_or("item");
            let plural = args
                .get("plural")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string())
                .unwrap_or_else(|| format!("{}s", singular));

            if count == 1 {
                Ok(Value::String(format!("{} {}", count, singular)))
            } else {
                Ok(Value::String(format!("{} {}", count, plural)))
            }
        },
    );

    // Filter for method badge colors
    tera.register_filter(
        "method_color",
        |value: &Value, _: &HashMap<String, Value>| {
            let method = value.as_str().unwrap_or("").to_uppercase();
            let color = match method.as_str() {
                "GET" => "green",
                "POST" => "blue",
                "PUT" => "orange",
                "DELETE" => "red",
                "PATCH" => "purple",
                _ => "gray",
            };
            Ok(Value::String(color.to_string()))
        },
    );

    Ok(())
}

/// Register custom Tera functions for documentation
fn register_custom_functions(tera: &mut Tera) -> Result<(), TemplateError> {
    // Function to generate table of contents
    tera.register_function("toc", |args: &HashMap<String, Value>| {
        let empty_vec = vec![];
        let items = args
            .get("items")
            .and_then(|v| v.as_array())
            .unwrap_or(&empty_vec);
        let mut toc = String::from("## Table of Contents\n\n");

        for item in items {
            if let Some(obj) = item.as_object() {
                let title = obj.get("title").and_then(|v| v.as_str()).unwrap_or("");
                let anchor = obj.get("anchor").and_then(|v| v.as_str()).unwrap_or("");
                toc.push_str(&format!("- [{}](#{})\n", title, anchor));
            }
        }

        Ok(Value::String(toc))
    });

    // Function to format numbers with commas
    tera.register_function("format_number", |args: &HashMap<String, Value>| {
        let number = args.get("number").and_then(|v| v.as_u64()).unwrap_or(0);
        // Simple number formatting - add commas for thousands separators
        let num_str = number.to_string();
        let chars: Vec<char> = num_str.chars().collect();
        let mut formatted = String::new();

        for (i, c) in chars.iter().enumerate() {
            if i > 0 && (chars.len() - i) % 3 == 0 {
                formatted.push(',');
            }
            formatted.push(*c);
        }

        Ok(Value::String(formatted))
    });

    Ok(())
}

/// Generate anchor-friendly string
fn generate_anchor(input: &str) -> String {
    input
        .to_lowercase()
        .chars()
        .map(|c| if c.is_alphanumeric() { c } else { '-' })
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<_>>()
        .join("-")
}
