//! `hephaes lint` — health-check the wiki (plan §9). Deterministic checks
//! (broken [[links]], orphans, missing sources, drift) run offline; `--deep`
//! adds an LLM pass for contradictions/stale claims.

use super::{base_overrides, ensure_config, repo_root, verbosity};
use crate::agent::{prompts, AgentTask};
use crate::config::{Paths, DEFAULT_WIKI_DIR};
use crate::progress_term::TerminalProgress;
use crate::repo::diff;
use crate::wiki::index;
use crate::{providers, util};
use anyhow::{bail, Result};
use owo_colors::OwoColorize;
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;

pub async fn run(global: &crate::cli::GlobalArgs, args: crate::cli::LintArgs) -> Result<()> {
    let root = repo_root(None)?;
    let paths = Paths::new(&root);
    let ov = base_overrides(global);

    let wiki_dir = ov.wiki_dir.clone().unwrap_or_else(|| {
        crate::config::resolve(&paths, &ov)
            .ok()
            .flatten()
            .map(|c| c.wiki_dir)
            .unwrap_or_else(|| DEFAULT_WIKI_DIR.to_string())
    });
    let wiki = if Path::new(&wiki_dir).is_absolute() {
        std::path::PathBuf::from(&wiki_dir)
    } else {
        root.join(&wiki_dir)
    };
    if !wiki.exists() {
        bail!("no wiki found at {wiki_dir} — run `hephaes init` first");
    }

    let pages = index::list_pages(&wiki);
    println!("{} {} page(s)", "linting".bold(), pages.len());

    // Build a name→rel resolution table for [[wikilinks]].
    let mut name_to_rel: BTreeMap<String, String> = BTreeMap::new();
    for p in &pages {
        let stem = p.rel.trim_end_matches(".md");
        let base = stem.rsplit('/').next().unwrap_or(stem);
        for key in [p.rel.as_str(), stem, base, p.title.as_str()] {
            name_to_rel
                .entry(key.to_lowercase())
                .or_insert_with(|| p.rel.clone());
        }
    }

    let link_re = regex::Regex::new(r"\[\[([^\]]+)\]\]").unwrap();
    let mut broken: Vec<String> = Vec::new();
    let mut referenced: BTreeSet<String> = BTreeSet::new();

    for p in &pages {
        let content = std::fs::read_to_string(wiki.join(&p.rel)).unwrap_or_default();
        for cap in link_re.captures_iter(&content) {
            let target = cap[1].split('|').next().unwrap_or("").trim().to_lowercase();
            let key = target.trim_start_matches("./").to_string();
            let resolved = name_to_rel
                .get(&key)
                .or_else(|| name_to_rel.get(&format!("{key}.md")));
            match resolved {
                Some(rel) => {
                    referenced.insert(rel.clone());
                }
                None => broken.push(format!("{} → [[{}]]", p.rel, cap[1].trim())),
            }
        }
    }

    let orphans: Vec<&str> = pages
        .iter()
        .filter(|p| p.rel != "overview.md" && p.kind != "answer" && !referenced.contains(&p.rel))
        .map(|p| p.rel.as_str())
        .collect();

    let missing_sources: Vec<&str> = pages
        .iter()
        .filter(|p| p.sources.is_empty() && p.kind != "overview" && p.kind != "glossary")
        .map(|p| p.rel.as_str())
        .collect();

    let head = diff::head_commit(&root);
    let drift: Vec<&str> = match &head {
        Some(h) => pages
            .iter()
            .filter(|p| p.source_commit.as_deref().map(|c| c != h).unwrap_or(false))
            .map(|p| p.rel.as_str())
            .collect(),
        None => Vec::new(),
    };

    report(
        "broken links",
        &broken.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
        true,
    );
    report("orphan pages (no inbound [[links]])", &orphans, false);
    report("pages missing `sources:`", &missing_sources, false);
    report("pages drifted from current commit", &drift, false);

    if args.deep {
        let cfg = ensure_config(&paths, &ov, true).await?;
        let provider = providers::build(&cfg, &paths, cfg.model_for(crate::config::Op::Ask))?;
        let project = root
            .file_name()
            .and_then(|s| s.to_str())
            .unwrap_or("project")
            .to_string();
        let task = AgentTask {
            system: prompts::system_prompt(&project),
            instruction: prompts::lint_instruction(),
            context: String::new(),
            repo_map: String::new(),
            repo_root: root.clone(),
            wiki_dir: wiki.clone(),
            model: cfg.model_for(crate::config::Op::Ask),
            read_only: true,
            max_iters: 40,
        };
        let reporter = TerminalProgress::new(verbosity(global), util::is_interactive());
        let outcome = provider.run_agent(&task, &reporter).await?;
        reporter.finish();
        println!(
            "\n{}\n{}",
            "deep findings:".bold(),
            outcome.final_text.trim()
        );
    }

    if broken.is_empty() {
        println!("\n{} no broken links.", "✓".green());
        Ok(())
    } else {
        bail!("{} broken link(s) found", broken.len())
    }
}

fn report(label: &str, items: &[&str], is_error: bool) {
    if items.is_empty() {
        println!("  {} {}: none", "·".dimmed(), label);
        return;
    }
    let head = format!("{}: {}", label, items.len());
    if is_error {
        println!("  {} {}", "✗".red(), head.red());
    } else {
        println!("  {} {}", "!".yellow(), head.yellow());
    }
    for it in items.iter().take(40) {
        println!("      - {}", util::truncate_chars(it, 100));
    }
}
