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
}
