//! `index.md` — the content catalog the agent reads first on every query
//! (plan §4). Rebuilt deterministically from page frontmatter; no LLM needed.

use crate::util;
use crate::wiki::frontmatter;
use anyhow::Result;
use std::path::Path;

/// Files the wiki owns specially; never listed as content pages.
pub const SPECIAL: &[&str] = &["HEPHAES.md", "index.md", "log.md"];

#[derive(Debug, Clone)]
pub struct PageInfo {
    /// Path relative to the wiki dir, e.g. `modules/auth.md`.
    pub rel: String,
    pub title: String,
    pub kind: String,
    pub summary: String,
    pub sources: Vec<String>,
    pub source_commit: Option<String>,
}

/// Enumerate all content pages under the wiki dir.
pub fn list_pages(wiki_dir: &Path) -> Vec<PageInfo> {
    let mut out = Vec::new();
    collect(wiki_dir, wiki_dir, &mut out);
    out.sort_by(|a, b| a.rel.cmp(&b.rel));
    out
}

fn collect(base: &Path, dir: &Path, out: &mut Vec<PageInfo>) {
    let entries = match std::fs::read_dir(dir) {
        Ok(e) => e,
        Err(_) => return,
    };
    for entry in entries.flatten() {
        let path = entry.path();
        let name = entry.file_name().to_string_lossy().to_string();
        if name.starts_with('.') {
            continue;
        }
        if path.is_dir() {
            collect(base, &path, out);
            continue;
        }
        if !name.ends_with(".md") || SPECIAL.contains(&name.as_str()) {
            continue;
        }
        let rel = path
            .strip_prefix(base)
            .unwrap_or(&path)
            .to_string_lossy()
            .replace('\\', "/");
        let content = std::fs::read_to_string(&path).unwrap_or_default();
        let (fm, body) = frontmatter::parse(&content);
        let stem = name.trim_end_matches(".md");
        let title = fm.title.clone().unwrap_or_else(|| humanize(stem));
        let kind = fm.kind.clone().unwrap_or_else(|| kind_from_path(&rel));
        let summary = fm
            .summary
            .clone()
            .unwrap_or_else(|| util::first_sentence(&body));
        out.push(PageInfo {
            rel,
            title,
            kind,
            summary,
            sources: fm.sources,
            source_commit: fm.source_commit,
        });
    }
}

fn kind_from_path(rel: &str) -> String {
    let dir = rel.split('/').next().unwrap_or("");
    match dir {
        "modules" => "module",
        "concepts" => "concept",
        "architecture" => "architecture",
        "flows" => "flow",
        "decisions" => "decision",
        "answers" => "answer",
        _ if rel == "overview.md" => "overview",
        _ if rel == "glossary.md" => "glossary",
        _ => "page",
    }
    .to_string()
}

fn humanize(stem: &str) -> String {
    stem.replace(['-', '_'], " ")
        .split_whitespace()
        .map(|w| {
            let mut c = w.chars();
            match c.next() {
                Some(f) => f.to_ascii_uppercase().to_string() + c.as_str(),
                None => String::new(),
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

const SECTIONS: &[(&str, &[&str])] = &[
    ("Overview", &["overview"]),
    ("Architecture", &["architecture"]),
    ("Modules", &["module"]),
    ("Concepts", &["concept"]),
    ("Flows", &["flow"]),
    ("Decisions", &["decision"]),
    ("Glossary", &["glossary"]),
    ("Answers", &["answer"]),
];

/// Render the index.md content from the current set of pages.
pub fn render(wiki_dir: &Path) -> String {
    let pages = list_pages(wiki_dir);
    let mut out = String::new();
    out.push_str("# Index\n\n");
    out.push_str(&format!(
        "_Catalog of {} wiki page(s). Maintained by hephaes._\n",
        pages.len()
    ));

    let mut listed = vec![false; pages.len()];

    for (heading, kinds) in SECTIONS {
        let group: Vec<usize> = pages
            .iter()
            .enumerate()
            .filter(|(_, p)| kinds.contains(&p.kind.as_str()))
            .map(|(i, _)| i)
            .collect();
        if group.is_empty() {
            continue;
        }
        out.push_str(&format!("\n## {heading}\n\n"));
        for i in group {
            listed[i] = true;
            out.push_str(&line_for(&pages[i]));
        }
    }

    // Anything that didn't match a known section.
    let rest: Vec<usize> = (0..pages.len()).filter(|i| !listed[*i]).collect();
    if !rest.is_empty() {
        out.push_str("\n## Other\n\n");
        for i in rest {
            out.push_str(&line_for(&pages[i]));
        }
    }
    out
}

fn line_for(p: &PageInfo) -> String {
    if p.summary.is_empty() {
        format!("- [{}]({})\n", p.title, p.rel)
    } else {
        format!("- [{}]({}) — {}\n", p.title, p.rel, p.summary)
    }
}

/// Rebuild and write `index.md`.
pub fn rebuild(wiki_dir: &Path) -> Result<()> {
    let content = render(wiki_dir);
    util::write_text(&wiki_dir.join("index.md"), &content)
}
