use super::util::{current_dir, is_iso_date, today_iso_date};
use koala_adr::{
    list_table, new_adr, supersede as adr_supersede, validate as adr_validate, NewArgs,
    Registry as AdrRegistry,
};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

pub fn new(
    root: Option<PathBuf>,
    title: String,
    date: Option<String>,
    tags: Vec<String>,
) -> ExitCode {
    let root = root.unwrap_or_else(current_dir);
    let date = match date {
        Some(d) if is_iso_date(&d) => d,
        Some(d) => {
            eprintln!("error: --date must be YYYY-MM-DD; got `{d}`");
            return ExitCode::from(2);
        }
        None => match today_iso_date() {
            Some(d) => d,
            None => {
                eprintln!("error: cannot determine today's date; pass --date YYYY-MM-DD");
                return ExitCode::from(1);
            }
        },
    };
    match new_adr(
        &root,
        &NewArgs {
            title: &title,
            date: &date,
            tags,
        },
    ) {
        Ok(r) => {
            println!("Created ADR-{:04}", r.id);
            println!("  {}", r.path.display());
            println!("Edit the body, then `koala-core adr validate` before commit.");
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("error: {e}");
            ExitCode::from(1)
        }
    }
}

pub fn list(root: Option<PathBuf>) -> ExitCode {
    let root = root.unwrap_or_else(current_dir);
    let reg = match AdrRegistry::load(&root) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("error: {e}");
            return ExitCode::from(1);
        }
    };
    let refs = inbound_refs(&root);
    print!("{}", list_table(&reg, &refs));
    ExitCode::SUCCESS
}

pub fn supersede(root: Option<PathBuf>, old: u32, new: u32) -> ExitCode {
    let root = root.unwrap_or_else(current_dir);
    match adr_supersede(&root, old, new) {
        Ok(r) => {
            println!("ADR-{old:04} → ADR-{new:04} (superseded)");
            println!("  updated {}", r.old_path.display());
            println!("  updated {}", r.new_path.display());
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("error: {e}");
            ExitCode::from(1)
        }
    }
}

pub fn validate(root: Option<PathBuf>) -> ExitCode {
    let root = root.unwrap_or_else(current_dir);
    let reg = match AdrRegistry::load(&root) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("error: {e}");
            return ExitCode::from(1);
        }
    };
    let issues = adr_validate(&reg);
    if issues.is_empty() {
        println!("✓ {n} ADR(s) — graph clean.", n = reg.len());
        ExitCode::SUCCESS
    } else {
        println!("✗ {} ADR issue(s):", issues.len());
        for i in &issues {
            println!("  • {i}");
        }
        ExitCode::from(1)
    }
}

fn inbound_refs(repo_root: &Path) -> HashMap<u32, usize> {
    let re = match regex::Regex::new(r"ADR-(\d{4})") {
        Ok(r) => r,
        Err(_) => return HashMap::new(),
    };
    let mut counts: HashMap<u32, usize> = HashMap::new();
    let dir = repo_root.join("wiki");
    let walker = walkdir::WalkDir::new(&dir);
    for entry in walker.into_iter().filter_map(Result::ok) {
        if !entry.file_type().is_file() {
            continue;
        }
        let path = entry.path();
        if path.extension().and_then(|s| s.to_str()) != Some("md") {
            continue;
        }
        let in_decisions = path.components().any(|c| c.as_os_str() == "decisions");
        let Ok(text) = std::fs::read_to_string(path) else {
            continue;
        };
        for cap in re.captures_iter(&text) {
            let Ok(id) = cap[1].parse::<u32>() else {
                continue;
            };
            if in_decisions {
                if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
                    if name.starts_with(&format!("{id:04}-")) {
                        continue;
                    }
                }
            }
            *counts.entry(id).or_insert(0) += 1;
        }
    }
    counts
}
