//! CODE_INDEX.md generation and file I/O.

use std::collections::HashSet;
use std::fs;
use std::path::Path;

use anyhow::{Context, Result};

use super::ModuleInfo;

/// Generate the CODE_INDEX.md content from parsed modules.
pub fn generate_code_index(modules: &[ModuleInfo], source_path: &Path) -> String {
    let mut out = String::new();

    let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
    let rev = get_git_rev(source_path).unwrap_or_else(|| "unknown".into());

    // Collect unique languages
    let languages: Vec<&str> = {
        let mut langs: HashSet<&str> = HashSet::new();
        for m in modules {
            if !m.functions.is_empty() || !m.types.is_empty() {
                langs.insert(&m.language);
            }
        }
        let mut v: Vec<&str> = langs.into_iter().collect();
        v.sort();
        v
    };

    out.push_str("# Code Structure Index\n\n");
    out.push_str("> Auto-generated by alcove. Do not edit manually.\n");
    out.push_str(&format!("> Last updated: {timestamp}\n"));
    out.push_str(&format!(
        "> Source: {} (rev: {rev})\n",
        source_path.display()
    ));
    if !languages.is_empty() {
        out.push_str(&format!("> Languages: {}\n", languages.join(", ")));
    }
    out.push('\n');

    for module in modules {
        if module.functions.is_empty() && module.types.is_empty() {
            continue;
        }

        out.push_str(&format!("## Module: {}\n\n", module.module_path));

        if !module.types.is_empty() {
            out.push_str("### Types\n");
            for t in &module.types {
                out.push_str(&format!("- `{t}`\n"));
            }
            out.push('\n');
        }

        if !module.functions.is_empty() {
            out.push_str("### Functions\n");
            for f in &module.functions {
                out.push_str(&format!("- `{f}`\n"));
            }
            out.push('\n');
        }
    }

    out
}

/// Write CODE_INDEX.md to the project's docs directory.
pub fn write_code_index(docs_root: &Path, project: &str, content: &str) -> Result<()> {
    let project_dir = docs_root.join(project);
    fs::create_dir_all(&project_dir).context("Failed to create project docs directory")?;

    let index_path = project_dir.join("CODE_INDEX.md");
    fs::write(&index_path, content)
        .with_context(|| format!("Failed to write {}", index_path.display()))?;

    Ok(())
}

fn get_git_rev(source_path: &Path) -> Option<String> {
    use std::process::Command;
    let output = Command::new("git")
        .args(["rev-parse", "--short", "HEAD"])
        .current_dir(source_path)
        .output()
        .ok()?;
    if output.status.success() {
        Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_code_index_includes_language_header() {
        let modules = vec![ModuleInfo {
            module_path: "server".to_string(),
            language: "Rust".to_string(),
            functions: vec!["pub fn search() -> Vec<Result>".to_string()],
            types: vec!["struct SearchIndex { entries: HashMap<String, String> }".to_string()],
            submodules: vec![],
            imports: vec![],
        }];
        let md = generate_code_index(&modules, Path::new("/test"));
        assert!(md.contains("## Module: server"));
        assert!(md.contains("### Functions"));
        assert!(md.contains("### Types"));
        assert!(md.contains("`pub fn search"));
        assert!(md.contains("`struct SearchIndex"));
        assert!(md.contains("Languages: Rust"));
    }

    #[test]
    fn test_generate_code_index_multi_language() {
        let modules = vec![
            ModuleInfo {
                module_path: "server".to_string(),
                language: "Rust".to_string(),
                functions: vec!["pub fn start()".to_string()],
                types: vec![],
                submodules: vec![],
                imports: vec![],
            },
            ModuleInfo {
                module_path: "utils".to_string(),
                language: "Python".to_string(),
                functions: vec!["def process()".to_string()],
                types: vec![],
                submodules: vec![],
                imports: vec![],
            },
        ];
        let md = generate_code_index(&modules, Path::new("/test"));
        assert!(md.contains("Languages: Python, Rust"));
    }
}
