use std::fs;
use std::path::Path;
use std::process;
use crate::lang::{LangDef, LANGUAGES, find_lang};

pub fn detect_languages(dir: &Path) -> Vec<&'static LangDef> {
    let mut detected = Vec::new();

    // 1. Marker files (précis, prioritaire)
    let markers: &[(&str, &str)] = &[
        ("Cargo.toml",       "rs"),
        ("go.mod",           "go"),
        ("pom.xml",          "java"),
        ("build.gradle",     "java"),
        ("requirements.txt", "py"),
        ("setup.py",         "py"),
        ("pyproject.toml",   "py"),
    ];

    for (marker, lang) in markers {
        if dir.join(marker).exists() {
            if let Some(l) = find_lang(lang) {
                if !detected.iter().any(|d: &&LangDef| d.name == l.name) {
                    detected.push(l);
                }
            }
        }
    }

    // package.json: check tsconfig.json to distinguish ts vs js
    if dir.join("package.json").exists() {
        let lang = if dir.join("tsconfig.json").exists() { "ts" } else { "js" };
        if let Some(l) = find_lang(lang) {
            if !detected.iter().any(|d: &&LangDef| d.name == l.name) {
                detected.push(l);
            }
        }
    }

    // 2. Fallback: scan les fichiers à la racine du dossier
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.filter_map(|e| e.ok()) {
            let path = entry.path();
            if !path.is_file() { continue; }
            let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
            for lang in LANGUAGES {
                if lang.extensions.contains(&ext)
                    && !detected.iter().any(|d: &&LangDef| d.name == lang.name)
                {
                    detected.push(lang);
                }
            }
        }
    }

    detected
}

// Écrit le Dumbo.toml à la racine avec une section par projet.
// sections : [("root", ["rs"]), ("frontend", ["ts"]), ...]
pub fn write_dumbo_toml(root: &Path, sections: &[(String, Vec<&str>)]) {
    let toml_path = root.join("Dumbo.toml");
    let mut content = String::from("# Dumbo.toml — generated by dumbo init\n");
    for (section, langs) in sections {
        content.push_str(&format!("\n[{}]\nlang = \"{}\"\n", section, langs.join(",")));
    }
    fs::write(&toml_path, content).expect("Failed to write Dumbo.toml");
}

pub fn write_dumboignore(dir: &Path, detected: &[&LangDef]) {
    let ignore_path = dir.join(".dumboignore");

    let mut content = String::from(
        "# .dumboignore — add directories or files to ignore, one per line.\n# Lines starting with # are comments.\n",
    );

    for lang in detected {
        content.push_str(&format!("\n# {}\n", lang.name));
        for dir_name in lang.ignored_dirs {
            content.push_str(&format!("{}\n", dir_name));
        }
    }

    let mut seen = std::collections::HashSet::new();
    let deduped: Vec<&str> = content
        .lines()
        .filter(|line| {
            let trimmed = line.trim();
            if trimmed.is_empty() || trimmed.starts_with('#') {
                true
            } else {
                seen.insert(*line)
            }
        })
        .collect();

    fs::write(&ignore_path, deduped.join("\n") + "\n")
        .expect("Failed to write .dumboignore");
}

fn read_existing_sections(dir: &Path) -> Vec<String> {
    let Some(toml::Value::Table(table)) = crate::run::load_dumbo_config(dir) else { return vec![] };
    table.keys().cloned().collect()
}

fn read_all_sections_with_lang(dir: &Path) -> Vec<(String, String)> {
    let Some(toml::Value::Table(table)) = crate::run::load_dumbo_config(dir) else { return vec![] };
    table.iter()
        .filter_map(|(k, v)| {
            let lang = v.get("lang")?.as_str().unwrap_or("").to_string();
            Some((k.clone(), lang))
        })
        .collect()
}

pub fn cmd_list(dir: &Path) {
    let toml_path = dir.join("Dumbo.toml");
    if !toml_path.exists() {
        eprintln!("Error: no Dumbo.toml found. Run `dumbo init` first.");
        process::exit(1);
    }

    let sections = read_all_sections_with_lang(dir);
    if sections.is_empty() {
        println!("Dumbo.toml has no sections.");
        return;
    }

    println!("Sections in Dumbo.toml:");
    let width = sections.iter().map(|(s, _)| s.len()).max().unwrap_or(0);
    for (section, lang) in &sections {
        let lang_display = if lang.is_empty() { "(none)".to_string() } else { lang.clone() };
        println!("  [{:<width$}]  lang: {}", section, lang_display, width = width);
    }
}

pub fn cmd_update(dir: &Path) {
    let toml_path = dir.join("Dumbo.toml");
    if !toml_path.exists() {
        eprintln!("Error: no Dumbo.toml found. Run `dumbo init` first.");
        process::exit(1);
    }

    let existing = read_existing_sections(dir);
    let mut new_sections: Vec<(String, Vec<&str>)> = Vec::new();

    // Vérifie les sous-dossiers
    let Ok(entries) = fs::read_dir(dir) else {
        eprintln!("Error: cannot read directory '{}'.", dir.to_str().unwrap_or("."));
        process::exit(1);
    };

    let mut subdirs: Vec<_> = entries
        .filter_map(|e| e.ok())
        .filter(|e| e.path().is_dir())
        .collect();
    subdirs.sort_by_key(|e| e.file_name());

    for entry in subdirs {
        let subdir = entry.path();
        let name = subdir.file_name().and_then(|n| n.to_str()).unwrap_or("?").to_string();
        if existing.contains(&name) {
            println!("  {} : already in Dumbo.toml, skipping", name);
            continue;
        }
        let detected = detect_languages(&subdir);
        if detected.is_empty() { continue; }
        let langs: Vec<&str> = detected.iter().map(|l| l.name).collect();
        println!("  {} : detected {} → adding section", name, langs.join(", "));
        if !subdir.join(".dumboignore").exists() {
            write_dumboignore(&subdir, &detected);
        }
        new_sections.push((name, langs));
    }

    if new_sections.is_empty() {
        println!("Nothing to update — Dumbo.toml is already up to date.");
        return;
    }

    // Appende les nouvelles sections au fichier existant (préserve les édits manuels)
    let mut content = fs::read_to_string(&toml_path).expect("Failed to read Dumbo.toml");
    for (section, langs) in &new_sections {
        content.push_str(&format!("\n[{}]\nlang = \"{}\"\n", section, langs.join(",")));
    }
    fs::write(&toml_path, content).expect("Failed to write Dumbo.toml");

    let added: Vec<&str> = new_sections.iter().map(|(s, _)| s.as_str()).collect();
    println!("Updated Dumbo.toml — added sections: [{}]", added.join(", "));
}

fn update_gitignore(dir: &Path) {
    if !dir.join(".git").exists() { return; }

    let gitignore_path = dir.join(".gitignore");
    let entry = "dumbo_*.md";

    let existing = fs::read_to_string(&gitignore_path).unwrap_or_default();
    if existing.lines().any(|l| l.trim() == entry) {
        return;
    }

    let content = if existing.is_empty() || existing.ends_with('\n') {
        format!("{}{}\n", existing, entry)
    } else {
        format!("{}\n{}\n", existing, entry)
    };

    fs::write(&gitignore_path, content).expect("Failed to update .gitignore");
    println!("Updated .gitignore — added {}", entry);
}

pub fn cmd_init(dir: &Path, recursive: bool) {
    let toml_path = dir.join("Dumbo.toml");
    if toml_path.exists() {
        eprintln!("Error: Dumbo.toml already exists in '{}'.", dir.to_str().unwrap_or("."));
        process::exit(1);
    }

    let mut sections: Vec<(String, Vec<&str>)> = Vec::new();

    // Section [root]
    let root_detected = detect_languages(dir);
    if !root_detected.is_empty() {
        let langs: Vec<&str> = root_detected.iter().map(|l| l.name).collect();
        sections.push(("root".to_string(), langs));
        write_dumboignore(dir, &root_detected);
    }

    if recursive {
        let Ok(entries) = fs::read_dir(dir) else {
            eprintln!("Error: cannot read directory '{}'.", dir.to_str().unwrap_or("."));
            process::exit(1);
        };

        let mut subdirs: Vec<_> = entries
            .filter_map(|e| e.ok())
            .filter(|e| e.path().is_dir())
            .collect();
        subdirs.sort_by_key(|e| e.file_name());

        for entry in subdirs {
            let subdir = entry.path();
            let detected = detect_languages(&subdir);
            if detected.is_empty() { continue; }
            let name = subdir.file_name().and_then(|n| n.to_str()).unwrap_or("?").to_string();
            let langs: Vec<&str> = detected.iter().map(|l| l.name).collect();
            println!("  {} : detected {}", name, langs.join(", "));
            write_dumboignore(&subdir, &detected);
            sections.push((name, langs));
        }
    }

    if sections.is_empty() {
        println!("No language detected. Creating blank Dumbo.toml.");
        sections.push(("root".to_string(), vec![]));
    }

    write_dumbo_toml(dir, &sections);
    update_gitignore(dir);

    let section_names: Vec<&str> = sections.iter().map(|(s, _)| s.as_str()).collect();
    println!("Created Dumbo.toml with sections: [{}]", section_names.join(", "));
}
