use crate::errors::KamError;
use crate::types::kam_toml::KamToml;

use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use tera::{Context, Tera};
use walkdir::WalkDir;

// Import assets
use crate::assets::tmpl::TmplAssets;
use crate::utils::{PrintOp, Utils};

/// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public struct 'TemplateCacheManager'. Replace with real documentation. (2025-12-30T22:23:21Z)

pub struct TemplateCacheManager;

impl TemplateCacheManager {
    // 获取模板缓存目录
    // 优先级：环境变量 > 全局配置 > 默认值
    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'get_cache_dir'. Replace with real documentation. (2025-12-30T22:23:21Z)
    ///
    /// # Errors
    /// Returns `KamError` if directory creation fails or kam home directory cannot be determined.
    pub fn get_cache_dir() -> Result<PathBuf, KamError> {
        // 1) 环境变量覆盖（最高优先级）
        //    允许临时或测试时覆盖，比如 KAM_TEMPLATE_CACHE_DIR=/tmp/kam_templates
        if let Ok(dir_str) = std::env::var("KAM_TEMPLATE_CACHE_DIR")
            && !dir_str.trim().is_empty()
        {
            let cache_dir = if dir_str.starts_with("~/") {
                let rel = dir_str.trim_start_matches("~/").to_string();
                dirs::home_dir().map_or_else(|| PathBuf::from(&dir_str), |home| home.join(&rel))
            } else {
                PathBuf::from(dir_str)
            };

            if !cache_dir.exists() {
                fs::create_dir_all(&cache_dir).map_err(KamError::Io)?;
            }
            return Ok(cache_dir);
        }

        // 2) 全局配置覆盖（KAM_HOME/config.toml 或默认 ~/.kam/config.toml）
        //    如果配置里有：
        //      [tmpl]
        //      cache_dir = "/some/path" (或 "~/somepath")
        //    就用那个作为模板缓存目录
        if let Ok(cfg_home) = crate::utils::kam_home_dir() {
            let cfg_path = cfg_home.join("config.toml");
            if cfg_path.exists()
                && let Ok(cfg_str) = fs::read_to_string(&cfg_path)
                && let Ok(cfg_v) = toml::from_str::<toml::Value>(&cfg_str)
                && let Some(tmpl_table) = cfg_v.get("tmpl")
                && let Some(cache_val) = tmpl_table.get("cache_dir").and_then(|v| v.as_str())
            {
                let cache_dir = if cache_val.starts_with("~/") {
                    // Keep tilde-expansion semantics relative to the user's HOME
                    dirs::home_dir().map_or_else(
                        || PathBuf::from(cache_val),
                        |home| home.join(cache_val.trim_start_matches("~/")),
                    )
                } else {
                    PathBuf::from(cache_val)
                };
                if !cache_dir.exists() {
                    fs::create_dir_all(&cache_dir).map_err(KamError::Io)?;
                }
                return Ok(cache_dir);
            }
        }

        // 3) 默认回退到 Kam home 的 templates 目录（KAM_HOME 默认为 $HOME/.kam）
        //    如果前面都没找到，就用这个
        let base = crate::utils::kam_home_dir()?;
        let cache_dir = base.join("templates");
        if !cache_dir.exists() {
            fs::create_dir_all(&cache_dir).map_err(KamError::Io)?;
        }
        Ok(cache_dir)
    }

    // 列出所有可用的模板（内置的 + 缓存的）
    // 用HashSet去重，避免重复
    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'list_templates'. Replace with real documentation. (2025-12-30T22:23:21Z)
    #[must_use]
    pub fn list_templates() -> Vec<String> {
        let mut templates = HashSet::new();

        // 1. 从assets里找内置模板
        for file in TmplAssets::iter() {
            let filename = file.as_ref();
            if filename.ends_with(".tar.gz") {
                if let Some(name) = filename.strip_suffix(".tar.gz") {
                    templates.insert(name.to_string());
                }
            } else if has_ext(filename, "zip")
                && let Some(name) = filename.strip_suffix(".zip")
            {
                templates.insert(name.to_string());
            }
        }

        // 2. 本地缓存的模板
        //    遍历缓存目录，找出所有模板文件
        if let Ok(cache_dir) = Self::get_cache_dir()
            && let Ok(entries) = fs::read_dir(cache_dir)
        {
            for entry in entries.flatten() {
                let path = entry.path();
                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                    // 处理.tar.gz的特殊情况，file_stem会返回"template.tar"
                    // 需要再去掉.tar后缀
                    let name = if path.to_string_lossy().ends_with(".tar.gz") {
                        stem.strip_suffix(".tar").unwrap_or(stem)
                    } else {
                        stem
                    };
                    templates.insert(name.to_string());
                }
            }
        }

        // 3. Project-local templates (e.g., tmpl/ and templates/ directories in the project)
        //    Support both directory-based templates and archive files such as .tar.gz, .tgz, .zip, .tar
        let project_local_dirs = crate::utils::PROJECT_TEMPLATE_DIRS;
        for dir in project_local_dirs {
            let dir_path = Path::new(dir);
            if dir_path.exists()
                && dir_path.is_dir()
                && let Ok(entries) = fs::read_dir(dir_path)
            {
                for entry in entries.flatten() {
                    let p = entry.path();
                    if p.is_dir() {
                        if let Some(name) = p.file_name().and_then(|s| s.to_str()) {
                            templates.insert(name.to_string());
                        }
                    } else if let Some(filename) = p.file_name().and_then(|s| s.to_str()) {
                        // Handle common archive extensions
                        if has_tar_gz_suffix(filename) {
                            if let Some(name) = filename.strip_suffix(".tar.gz") {
                                templates.insert(name.to_string());
                            }
                        } else if has_ext(filename, "tgz") {
                            if let Some(name) = filename.strip_suffix(".tgz") {
                                templates.insert(name.to_string());
                            }
                        } else if has_ext(filename, "zip") {
                            if let Some(name) = filename.strip_suffix(".zip") {
                                templates.insert(name.to_string());
                            }
                        } else if has_ext(filename, "tar")
                            && let Some(name) = filename.strip_suffix(".tar")
                        {
                            templates.insert(name.to_string());
                        }
                    }
                }
            }
        }

        let mut list: Vec<String> = templates.into_iter().collect();
        list.sort();
        list
    }

    /// Check if a template exists (built-in or cached)
    /// Ensure a template exists (either built-in or already installed in local cache).
    ///
    /// # Errors
    /// Returns `KamError` if template resolution/installation fails.
    pub fn ensure_template(template: &str) -> Result<(), KamError> {
        let list = Self::list_templates();
        if list.contains(&template.to_string()) {
            Ok(())
        } else {
            Err(KamError::TemplateNotFound(format!(
                "Template '{template}' not found in built-in assets or local cache"
            )))
        }
    }

    /// Get path to template archive/directory.
    ///
    /// # Errors
    /// Returns `KamError` if the cache directory cannot be accessed.
    pub fn resolve_template_path(template: &str) -> Result<Option<PathBuf>, KamError> {
        if let Some(local_path) = Self::resolve_project_template_path(template) {
            return Ok(Some(local_path));
        }

        let cache_dir = Self::get_cache_dir()?;

        // Check for directory
        let dir_path = cache_dir.join(template);
        if dir_path.exists() && dir_path.is_dir() {
            return Ok(Some(dir_path));
        }

        // Check for archives
        let extensions = [".tar.gz", ".tgz", ".zip"];
        for ext in extensions {
            let archive_path = cache_dir.join(format!("{template}{ext}"));
            if archive_path.exists() {
                return Ok(Some(archive_path));
            }
        }

        Ok(None)
    }

    fn resolve_project_template_path(template: &str) -> Option<PathBuf> {
        for dir in crate::utils::PROJECT_TEMPLATE_DIRS {
            let base = Path::new(dir);
            if !base.is_dir() {
                continue;
            }

            let dir_path = base.join(template);
            if dir_path.is_dir() {
                return Some(dir_path);
            }

            for ext in [".tar.gz", ".tgz", ".zip"] {
                let archive_path = base.join(format!("{template}{ext}"));
                if archive_path.is_file() {
                    return Some(archive_path);
                }
            }
        }

        None
    }

    /// List local cached templates
    ///
    /// # Errors
    /// Returns `KamError` if the cache directory cannot be accessed or read.
    pub fn list_local_templates() -> Result<Vec<String>, KamError> {
        let cache_dir = Self::get_cache_dir()?;
        let mut templates = Vec::new();
        if cache_dir.exists() {
            for entry in fs::read_dir(cache_dir).map_err(KamError::Io)? {
                let entry = entry.map_err(KamError::Io)?;
                let path = entry.path();
                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                    let name = if path.to_string_lossy().ends_with(".tar.gz") {
                        stem.strip_suffix(".tar").unwrap_or(stem)
                    } else {
                        stem
                    };
                    templates.push(name.to_string());
                }
            }
        }
        templates.sort();
        Ok(templates)
    }

    /// Install a template from a local path (directory or archive)
    ///
    /// # Errors
    /// Returns `KamError` if the source does not exist, template already exists, or copy/installation fails.
    pub fn install_template(name: &str, source: &Path) -> Result<(), KamError> {
        let cache_dir = Self::get_cache_dir()?;

        if source.is_dir() {
            let dest = cache_dir.join(name);
            if dest.exists() {
                return Err(KamError::CommandFailed(format!(
                    "Template '{name}' already exists in cache"
                )));
            }
            crate::utils::copy_dir_all(source, &dest).map_err(KamError::Io)?;
        } else if source.is_file() {
            let filename = source
                .file_name()
                .ok_or_else(|| KamError::InvalidDirectory("Invalid source filename".to_string()))?
                .to_string_lossy();

            let dest_name = if filename.ends_with(".tar.gz") {
                format!("{name}.tar.gz")
            } else if let Some(ext) = source.extension().and_then(|s| s.to_str()) {
                format!("{name}.{ext}")
            } else {
                name.to_string()
            };

            let dest = cache_dir.join(dest_name);
            if dest.exists() {
                return Err(KamError::CommandFailed(format!(
                    "Template '{name}' already exists in cache"
                )));
            }
            fs::copy(source, &dest).map_err(KamError::Io)?;
        } else {
            return Err(KamError::InvalidDirectory(format!(
                "Source '{}' does not exist",
                source.display()
            )));
        }
        Ok(())
    }

    /// Remove a template from cache
    ///
    /// # Errors
    /// Returns `KamError` if the template is not found in cache or removal fails.
    pub fn remove_template(name: &str) -> Result<(), KamError> {
        let cache_dir = Self::get_cache_dir()?;

        // Check for directory
        let dir_path = cache_dir.join(name);
        if dir_path.exists() && dir_path.is_dir() {
            fs::remove_dir_all(&dir_path).map_err(KamError::Io)?;
            return Ok(());
        }

        // Check for archives
        let extensions = [".tar.gz", ".tgz", ".zip"];
        for ext in extensions {
            let archive_path = cache_dir.join(format!("{name}{ext}"));
            if archive_path.exists() {
                fs::remove_file(&archive_path).map_err(KamError::Io)?;
                return Ok(());
            }
        }

        Err(KamError::TemplateNotFound(format!(
            "Template '{name}' not found in cache"
        )))
    }
}

/// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public struct 'TemplateVariableProcessor'. Replace with real documentation. (2025-12-30T22:23:21Z)

pub struct TemplateVariableProcessor;

impl TemplateVariableProcessor {
    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'flatten_kam_toml'. Replace with real documentation. (2025-12-30T22:23:21Z)
    #[must_use]
    pub fn flatten_kam_toml(kam_toml: &KamToml) -> HashMap<String, String> {
        let mut vars = HashMap::new();

        // Top-level shortcuts
        vars.insert("id".to_string(), kam_toml.prop.id.clone());
        vars.insert("name".to_string(), kam_toml.prop.get_name().to_string());
        // Alias for templates using `project_name` variable
        vars.insert(
            "project_name".to_string(),
            kam_toml.prop.get_name().to_string(),
        );
        vars.insert("version".to_string(), kam_toml.prop.version.clone());
        vars.insert(
            "versionCode".to_string(),
            kam_toml.prop.versionCode.to_string(),
        );
        vars.insert(
            "author".to_string(),
            kam_toml
                .prop
                .author
                .as_ref()
                .unwrap_or(&String::new())
                .clone(),
        );
        vars.insert(
            "description".to_string(),
            kam_toml.prop.get_description().to_string(),
        );

        if let Some(uj) = &kam_toml.prop.updateJson {
            vars.insert("update_json".to_string(), uj.clone());
        }

        // Prop section
        vars.insert("prop.id".to_string(), kam_toml.prop.id.clone());
        vars.insert(
            "prop.name".to_string(),
            kam_toml.prop.get_name().to_string(),
        );
        vars.insert("prop.version".to_string(), kam_toml.prop.version.clone());
        vars.insert(
            "prop.versionCode".to_string(),
            kam_toml.prop.versionCode.to_string(),
        );
        vars.insert(
            "prop.author".to_string(),
            kam_toml
                .prop
                .author
                .as_ref()
                .unwrap_or(&String::new())
                .clone(),
        );
        vars.insert(
            "prop.description".to_string(),
            kam_toml.prop.get_description().to_string(),
        );

        // mmrl.repo.* fields (helpful template variables and env vars)
        if let Some(mmrl) = &kam_toml.mmrl
            && let Some(repo) = &mmrl.repo
        {
            if let Some(repository) = &repo.repository {
                vars.insert("mmrl.repo.repository".to_string(), repository.clone());
            }
            if let Some(homepage) = &repo.homepage {
                vars.insert("mmrl.repo.homepage".to_string(), homepage.clone());
            }
            if let Some(readme) = &repo.readme {
                vars.insert("mmrl.repo.readme".to_string(), readme.clone());
            }
            if let Some(documentation) = &repo.documentation {
                vars.insert("mmrl.repo.documentation".to_string(), documentation.clone());
            }
            if let Some(issues) = &repo.issues {
                vars.insert("mmrl.repo.issues".to_string(), issues.clone());
            }
            if let Some(cover) = &repo.cover {
                vars.insert("mmrl.repo.cover".to_string(), cover.clone());
            }
        }

        // kam.build.*字段（target和hooks目录，可选）
        // 这些主要是给模板用的，让模板知道构建配置
        if let Some(build) = &kam_toml.kam.build {
            if let Some(source_dir) = &build.source_dir {
                vars.insert("kam.build.source_dir".to_string(), source_dir.clone());
            }
            if let Some(target_dir) = &build.target_dir {
                vars.insert("kam.build.target_dir".to_string(), target_dir.clone());
            }
            if let Some(output_file) = &build.output_file {
                vars.insert("kam.build.output_file".to_string(), output_file.clone());
            }
            if let Some(hooks_dir) = &build.hooks_dir {
                vars.insert("kam.build.hooks_dir".to_string(), hooks_dir.clone());
            }
        }

        // 暴露module_type（kam/template）给模板
        // 虽然可能不太常用，但留着总没错
        vars.insert(
            "kam.module_type".to_string(),
            format!("{:?}", kam_toml.kam.module_type).to_ascii_lowercase(),
        );

        vars
    }

    // 解析模板变量，格式是key=value
    // 如果没有=号，就当作key，value是空字符串
    // 这个函数很简单，但够用了
    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'parse_template_vars'. Replace with real documentation. (2025-12-30T22:23:21Z)
    ///
    /// # Errors
    /// This function currently always returns `Ok`, but the signature allows for future error handling.
    pub fn parse_template_vars(vars: &[String]) -> Result<HashMap<String, String>, KamError> {
        let mut map = HashMap::new();
        for var in vars {
            if let Some((k, v)) = var.split_once('=') {
                map.insert(k.to_string(), v.to_string());
            } else {
                // 没有=号，就当key用，value是空
                map.insert(var.clone(), String::new());
            }
        }
        Ok(map)
    }
}

/// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public struct 'TemplateCopier'. Replace with real documentation. (2025-12-30T22:23:21Z)

pub struct TemplateCopier;

impl TemplateCopier {
    // 向后兼容的包装函数，用rule-based copy但没有include/exclude规则
    // 主要是为了保持API兼容性，虽然可能有点多余
    /// Copy a template tree from `src` to `dst`, performing variable substitution.
    ///
    /// This is a small convenience wrapper that calls `copy_and_replace_with_rules`
    /// with no include/exclude filters.
    ///
    /// # Errors
    /// - `KamError::InvalidDirectory` when `src` does not exist or a path prefix operation fails.
    /// - `KamError::Io` for filesystem I/O errors (directory creation, file copy, file write).
    /// - `KamError::TemplateRenderError` when rendering a filename or the contents of a text
    ///   file with Tera fails (the error includes the source path and a short message).
    ///
    /// Note: rendering failures are treated as explicit errors (no silent fallback).
    pub fn copy_and_replace(
        src: &Path,
        dst: &Path,
        vars: &HashMap<String, String>,
        force: bool,
        template_id: &str,
    ) -> Result<(), KamError> {
        Self::copy_and_replace_with_rules(src, dst, vars, force, template_id, None, None)
    }

    // 核心复制函数，支持include和exclude规则
    // 这个函数有点长，但逻辑还算清晰
    /// Copy and render template files from `src` into `dst`, applying `vars` as the
    /// Tera context. Binary files are copied verbatim; textual files are rendered using
    /// Tera. `excludes` and `includes` control which relative paths are skipped or forced in.
    ///
    /// `template_id` is included in render-related error messages to help identify which
    /// template produced the failure.
    ///
    /// # Errors
    /// - `KamError::InvalidDirectory`: when `src` does not exist or `strip_prefix` fails.
    /// - `KamError::Io`: for filesystem-related errors (creating directories, copying, writing).
    /// - `KamError::TemplateRenderError`: when Tera fails to render a filename or file content.
    ///
    /// Important: previous behavior silently copied files when rendering failed. That implicit
    /// fallback has been removed: rendering errors are now explicit failures so callers can
    /// handle template problems deterministically.
    pub fn copy_and_replace_with_rules(
        src: &Path,
        dst: &Path,
        vars: &HashMap<String, String>,
        force: bool,
        template_id: &str,
        excludes: Option<&[String]>,
        includes: Option<&[String]>,
    ) -> Result<(), KamError> {
        if !src.exists() {
            return Err(KamError::InvalidDirectory(format!(
                "Source does not exist: {}",
                src.display()
            )));
        }

        // 构建Tera上下文，把扁平化的变量展开成嵌套结构
        // 这样模板里可以用prop.id这种点号路径访问
        let context_value = unflatten_vars(vars);
        let context = Context::from_serialize(&context_value).map_err(|e| {
            KamError::CommandFailed(format!("Failed to build template context: {e}"))
        })?;

        let mut tera = Tera::default();
        tera.set_escape_fn(ToString::to_string); // Disable auto-escaping for file content

        for entry in WalkDir::new(src) {
            let entry = entry.map_err(|e| KamError::Io(e.into()))?;
            let src_path = entry.path();

            if src_path == src {
                continue;
            }

            // Calculate relative path
            let rel_path = src_path
                .strip_prefix(src)
                .map_err(|e| KamError::InvalidDirectory(format!("strip_prefix failed: {e}")))?;

            // Replace variables in filename
            let rel_path_str = rel_path.to_string_lossy();
            let mut dest_rel_path_str = rel_path_str.to_string();

            // Simple string replacement for filenames
            for (k, v) in vars {
                let placeholder = format!("{{{{{k}}}}}"); // {{key}}
                dest_rel_path_str = dest_rel_path_str.replace(&placeholder, v);
            }

            // If the filename still looks like a template (contains '{{'), attempt to
            // render it with Tera. Unlike the previous behavior which silently fell
            // back to the original filename on render error, we now treat render
            // errors as explicit failures to avoid silent degradation.
            if dest_rel_path_str.contains("{{") {
                match tera.render_str(&dest_rel_path_str, &context) {
                    Ok(rendered) => dest_rel_path_str = rendered,
                    Err(e) => {
                        return Err(KamError::TemplateRenderError(format!(
                            "Failed to render filename '{dest_rel_path_str}': {e} (template_id: {template_id})"
                        )));
                    }
                }
            }

            let dst_path = dst.join(&dest_rel_path_str);

            // Evaluate include/exclude rules before touching the file
            let file_name_opt = src_path.file_name().and_then(|s| s.to_str());
            if should_skip(&dest_rel_path_str, file_name_opt, includes, excludes) {
                // Print a skip status for excluded files
                Utils::print_status(&dst_path, &dest_rel_path_str, PrintOp::Skip, force);
                continue;
            }

            if entry.file_type().is_dir() {
                // Create directory silently without printing
                fs::create_dir_all(&dst_path).map_err(KamError::Io)?;
            } else if entry.file_type().is_file() {
                // Ensure parent exists
                if let Some(parent) = dst_path.parent() {
                    fs::create_dir_all(parent).map_err(KamError::Io)?;
                }

                if dst_path.exists() && !force {
                    Utils::print_status(&dst_path, &dest_rel_path_str, PrintOp::Skip, force);
                    continue;
                }

                // Check if file exists before writing to determine correct status
                let file_existed = dst_path.exists();

                // 二进制文件和 explicitly raw payloads 直接复制，不做内容模板替换。
                // 文件名/路径中的变量已经在上面处理过。
                if is_binary(src_path) || !should_render_file(rel_path) {
                    fs::copy(src_path, &dst_path).map_err(KamError::Io)?;
                } else {
                    // 文本文件，尝试做模板替换
                    // 注意：渲染错误现在视为致命错误（不再静默回退为直接复制），以避免隐式降级或“看起来正确”的默认值。
                    // 如果将来需要兼容旧行为，请引入显式的容错参数（例如 `tolerate_render_errors`）。
                    let text = fs::read_to_string(src_path).map_err(KamError::Io)?;
                    let rendered = tera.render_str(&text, &context).map_err(|e| {
                        KamError::TemplateRenderError(format!(
                            "Failed to render template '{}': {} (template_id: {})",
                            src_path.display(),
                            e,
                            template_id
                        ))
                    })?;
                    fs::write(&dst_path, rendered).map_err(KamError::Io)?;
                }

                // Print appropriate status based on whether file existed before
                if file_existed && force {
                    Utils::print_status(&dst_path, &dest_rel_path_str, PrintOp::Update, force);
                } else if !file_existed {
                    Utils::print_status(
                        &dst_path,
                        &dest_rel_path_str,
                        PrintOp::Create { is_dir: false },
                        force,
                    );
                }
            }
        }
        Ok(())
    }
}

fn should_skip(
    rel_path: &str,
    file_name: Option<&str>,
    includes: Option<&[String]>,
    excludes: Option<&[String]>,
) -> bool {
    // If includes exist and any of them matches, do NOT skip (force include).
    if let Some(includes_vec) = includes {
        for inc in includes_vec {
            if crate::utils::pattern_matches(inc, rel_path, file_name) {
                return false;
            }
        }
    }

    // If excludes exist and any matches, we skip unless included above.
    if let Some(excludes_vec) = excludes {
        for exc in excludes_vec {
            if crate::utils::pattern_matches(exc, rel_path, file_name) {
                return true;
            }
        }
    }
    false
}

fn has_tar_gz_suffix(filename: &str) -> bool {
    filename.to_ascii_lowercase().ends_with(".tar.gz")
}

fn has_ext(filename: &str, expected: &str) -> bool {
    Path::new(filename)
        .extension()
        .and_then(|ext| ext.to_str())
        .is_some_and(|ext| ext.eq_ignore_ascii_case(expected))
}

fn should_render_file(rel_path: &Path) -> bool {
    if rel_path.components().any(|component| {
        matches!(
            component.as_os_str().to_str(),
            Some(".github" | "hooks" | "lib")
        )
    }) {
        return false;
    }

    rel_path
        .extension()
        .and_then(|ext| ext.to_str())
        .is_none_or(|ext| {
            matches!(
                ext.to_ascii_lowercase().as_str(),
                "md" | "toml"
                    | "json"
                    | "yml"
                    | "yaml"
                    | "txt"
                    | "prop"
                    | "rule"
                    | "sh"
                    | "tmpl"
                    | "ps1"
                    | "env"
                    | "example"
            )
        })
}

fn unflatten_vars(vars: &HashMap<String, String>) -> Value {
    let mut root = serde_json::Map::new();

    for (key, value) in vars {
        if key.trim().is_empty() {
            continue;
        }

        let parts: Vec<&str> = key.split('.').collect();
        let mut current = &mut root;

        for part in &parts[..parts.len() - 1] {
            current = current
                .entry(part.to_string())
                .or_insert_with(|| Value::Object(serde_json::Map::new()))
                .as_object_mut()
                .unwrap(); // This unwrap is safe because we just inserted an Object.
        }

        if let Some(leaf_part) = parts.last() {
            current.insert(leaf_part.to_string(), Value::String(value.clone()));
        }
    }
    Value::Object(root)
}

fn is_binary(path: &Path) -> bool {
    // Simple heuristic: read first few bytes and check for null byte
    if let Ok(mut file) = fs::File::open(path) {
        use std::io::Read;
        let mut buffer = [0; 1024];
        if let Ok(n) = file.read(&mut buffer) {
            for b in &buffer[..n] {
                if *b == 0 {
                    return true;
                }
            }
        }
    }
    // Check extension
    if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
        let binary_exts = [
            "png", "jpg", "jpeg", "gif", "ico", "zip", "tar", "gz", "so", "a", "o", "bin", "exe",
        ];
        if binary_exts.contains(&ext.to_lowercase().as_str()) {
            return true;
        }
    }
    false
}

/// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public struct 'TemplateManager'. Replace with real documentation. (2025-12-30T22:23:21Z)

pub struct TemplateManager;

impl TemplateManager {
    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'ensure_template'. Replace with real documentation. (2025-12-30T22:23:21Z)
    ///
    /// # Errors
    /// Returns `KamError` if the template is not found in built-in assets or local cache.
    pub fn ensure_template(template: &str) -> Result<(), KamError> {
        TemplateCacheManager::ensure_template(template)
    }

    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'list_builtin_templates'. Replace with real documentation. (2025-12-30T22:23:21Z)

    #[must_use]
    pub fn list_builtin_templates() -> Vec<String> {
        TemplateCacheManager::list_templates()
    }

    /// AUTO-GENERATED BY Kam/scripts/add_missing_docs.py: placeholder documentation for public function 'parse_template_vars'. Replace with real documentation. (2025-12-30T22:23:21Z)
    ///
    /// # Errors
    /// This function currently always returns `Ok`, but the signature allows for future error handling.
    pub fn parse_template_vars(vars: &[String]) -> Result<HashMap<String, String>, KamError> {
        TemplateVariableProcessor::parse_template_vars(vars)
    }

    /// Copy a template tree from `src` to `dst` performing variable substitution.
    ///
    /// Wrapper around `TemplateCopier::copy_and_replace`.
    ///
    /// # Errors
    /// - `KamError::InvalidDirectory` if `src` does not exist.
    /// - `KamError::Io` for filesystem errors.
    /// - `KamError::TemplateRenderError` when template rendering fails.
    pub fn copy_and_replace(
        src: &Path,
        dst: &Path,
        vars: &HashMap<String, String>,
        force: bool,
        template_id: &str,
    ) -> Result<(), KamError> {
        TemplateCopier::copy_and_replace(src, dst, vars, force, template_id)
    }

    /// Copy and render template files from `src` to `dst` with optional include/exclude rules.
    ///
    /// Wrapper around `TemplateCopier::copy_and_replace_with_rules`.
    ///
    /// # Errors
    /// - `KamError::InvalidDirectory` when `src` is invalid.
    /// - `KamError::Io` for filesystem failures.
    /// - `KamError::TemplateRenderError` for template rendering failures.
    pub fn copy_and_replace_with_rules(
        src: &Path,
        dst: &Path,
        vars: &HashMap<String, String>,
        force: bool,
        template_id: &str,
        excludes: Option<&[String]>,
        includes: Option<&[String]>,
    ) -> Result<(), KamError> {
        TemplateCopier::copy_and_replace_with_rules(
            src,
            dst,
            vars,
            force,
            template_id,
            excludes,
            includes,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::errors::KamError;
    use std::collections::HashMap;
    use std::fs;
    use std::fs::File;
    use std::io::Write;
    use tempfile::tempdir;

    #[test]
    fn test_template_render_error_in_file() {
        let src_dir = tempdir().unwrap();
        let dst_dir = tempdir().unwrap();
        let src_path = src_dir.path().join("bad.tmpl");

        // Write a deliberately malformed template (unclosed brace) to trigger parse/render error.
        fs::write(&src_path, "{{ unclosed").unwrap();

        let vars: HashMap<String, String> = HashMap::new();
        let res = TemplateCopier::copy_and_replace_with_rules(
            src_dir.path(),
            dst_dir.path(),
            &vars,
            true,
            "test_template",
            None,
            None,
        );
        match res {
            Err(KamError::TemplateRenderError(_)) => (),
            Err(e) => panic!("expected TemplateRenderError, got {e:?}"),
            Ok(()) => panic!("expected error, got Ok"),
        }
    }

    // Filename render tests can be brittle because Tera often treats
    // brace-containing filenames as plain text or renders undefined variables
    // to empty strings. We already cover rendering error behavior in
    // `test_template_render_error_in_file` (malformed template content), which
    // exercises the same TemplateRenderError path. If filename-specific tests
    // are required in the future, add deterministic cases that reliably produce
    // a parse/render error (for example, by creating an actual malformed
    // template file and asserting the error), rather than relying on filename
    // parsing semantics which may vary across Tera versions.

    #[test]
    fn test_is_binary_detects_null_byte() {
        let tmp = tempdir().unwrap();
        let fpath = tmp.path().join("binfile");
        let mut f = File::create(&fpath).unwrap();
        f.write_all(&[0u8, 1, 2, 3]).unwrap();
        assert!(is_binary(&fpath));
    }
}
