use std::collections::BTreeMap;

use crate::error::{usage, Result};
use crate::feature::Feature;
use crate::project::Project;
use crate::Ctx;

pub fn run(ctx: &Ctx) -> Result<()> {
    let prj = ctx.open()?;
    let count = regenerate(&prj)?;
    println!(
        "wrote {} and {count} view files",
        prj.fdir.join("INDEX.md").display()
    );
    Ok(())
}

/// Regenerate `features/INDEX.md` and `views/{by-tag,status}`. Returns the
/// number of view files written. Errors if any feature fails to parse.
pub fn regenerate(prj: &Project) -> Result<usize> {
    let (mut feats, errors) = prj.load();
    if !errors.is_empty() {
        return Err(usage("fix parse errors first (run verify)"));
    }
    feats.sort_by(|a, b| a.id().unwrap_or("").cmp(b.id().unwrap_or("")));

    // INDEX.md
    let mut lines = vec![
        "# Feature index".to_string(),
        String::new(),
        "Generated by opys — do not edit.".to_string(),
        String::new(),
    ];
    for f in &feats {
        lines.push(index_line(f));
    }
    std::fs::write(prj.fdir.join("INDEX.md"), lines.join("\n") + "\n")?;

    // views/
    let views = &prj.views_dir;
    for sub in ["by-tag", "status"] {
        let d = views.join(sub);
        std::fs::create_dir_all(&d)?;
        for entry in std::fs::read_dir(&d)?.flatten() {
            if entry.path().extension().is_some_and(|e| e == "md") {
                std::fs::remove_file(entry.path())?;
            }
        }
    }

    let mut buckets: BTreeMap<(&str, String), Vec<&Feature>> = BTreeMap::new();
    for f in &feats {
        for t in f.frontmatter.tags().unwrap_or_default() {
            buckets.entry(("by-tag", t)).or_default().push(f);
        }
        let status = f.status().unwrap_or("").to_string();
        buckets.entry(("status", status)).or_default().push(f);
    }
    for ((sub, key), fl) in &buckets {
        let mut out = vec![
            format!("# {sub}: {key}"),
            String::new(),
            "Generated by opys — do not edit.".to_string(),
            String::new(),
        ];
        for f in fl {
            out.push(format!(
                "- {} [{}] {}",
                f.id().unwrap_or(""),
                f.status().unwrap_or(""),
                f.title
            ));
        }
        std::fs::write(
            views.join(sub).join(format!("{key}.md")),
            out.join("\n") + "\n",
        )?;
    }

    let count = walkdir::WalkDir::new(views)
        .into_iter()
        .filter_map(|e| e.ok())
        .filter(|e| e.file_type().is_file() && e.path().extension().is_some_and(|x| x == "md"))
        .count();
    Ok(count)
}

fn index_line(f: &Feature) -> String {
    let tags = f.frontmatter.tags().unwrap_or_default().join(", ");
    format!(
        "- {} [{}] ({}) {}",
        f.id().unwrap_or(""),
        f.status().unwrap_or(""),
        tags,
        f.title
    )
}
