//! Skills: reusable instruction packs in `SKILL.md` files, compatible with
//! the Claude Code / Codex format (`---` frontmatter with name/description).
//!
//! Search order (later wins on name conflicts):
//!   ~/.lucid-train/skills/*/SKILL.md
//!   ~/.claude/skills/*/SKILL.md         (Claude Code compatibility)
//!   <cwd>/.lucid/skills/*/SKILL.md
//!   <cwd>/.claude/skills/*/SKILL.md     (project-level, Claude Code compat)

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub struct Skill {
    pub name: String,
    pub description: String,
    pub path: PathBuf,
}

pub fn discover(cwd: &Path) -> Vec<Skill> {
    let mut found: BTreeMap<String, Skill> = BTreeMap::new();
    let mut roots: Vec<PathBuf> = Vec::new();
    if let Some(home) = dirs::home_dir() {
        roots.push(home.join(".lucid-train/skills"));
        roots.push(home.join(".claude/skills"));
    }
    roots.push(crate::config::config_dir().join("skills"));
    roots.push(cwd.join(".lucid/skills"));
    roots.push(cwd.join(".claude/skills"));

    for root in roots {
        let Ok(entries) = std::fs::read_dir(&root) else { continue };
        for entry in entries.flatten() {
            let skill_md = entry.path().join("SKILL.md");
            if !skill_md.is_file() {
                continue;
            }
            if let Some(skill) = parse_skill(&skill_md) {
                found.insert(skill.name.clone(), skill);
            }
        }
    }
    found.into_values().collect()
}

fn parse_skill(path: &Path) -> Option<Skill> {
    let content = std::fs::read_to_string(path).ok()?;
    let (name, description) = parse_frontmatter(&content);
    let name = name.unwrap_or_else(|| {
        path.parent()
            .and_then(|p| p.file_name())
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_else(|| "unnamed".into())
    });
    Some(Skill {
        name,
        description: description.unwrap_or_default(),
        path: path.to_path_buf(),
    })
}

/// Extract `name:` and `description:` from a `---` YAML frontmatter block.
pub fn parse_frontmatter(content: &str) -> (Option<String>, Option<String>) {
    let mut lines = content.lines();
    if lines.next().map(str::trim) != Some("---") {
        return (None, None);
    }
    let mut name = None;
    let mut description = None;
    for line in lines {
        let t = line.trim();
        if t == "---" {
            break;
        }
        if let Some(v) = t.strip_prefix("name:") {
            name = Some(v.trim().trim_matches(['"', '\'']).to_string());
        } else if let Some(v) = t.strip_prefix("description:") {
            description = Some(v.trim().trim_matches(['"', '\'']).to_string());
        }
    }
    (name, description)
}

/// System-prompt fragment listing available skills (empty string if none).
pub fn skills_prompt(skills: &[Skill]) -> String {
    if skills.is_empty() {
        return String::new();
    }
    let mut s = String::from(
        "\n\n## Skills\n\nReusable skill instructions are available. When a task matches a skill's description, FIRST read its SKILL.md with the shell tool (`cat <path>`), then follow it:\n",
    );
    for skill in skills {
        s.push_str(&format!(
            "- **{}** — {} ({})\n",
            skill.name,
            skill.description,
            skill.path.display()
        ));
    }
    s
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn frontmatter_parses() {
        let md = "---\nname: pdf-tools\ndescription: Work with PDF files\n---\n# body\n";
        let (n, d) = parse_frontmatter(md);
        assert_eq!(n.as_deref(), Some("pdf-tools"));
        assert_eq!(d.as_deref(), Some("Work with PDF files"));
    }

    #[test]
    fn no_frontmatter_is_none() {
        assert_eq!(parse_frontmatter("# just a doc"), (None, None));
    }

    #[test]
    fn discovers_from_dir() {
        let tmp = tempfile_dir();
        let dir = tmp.join(".lucid/skills/my-skill");
        std::fs::create_dir_all(&dir).unwrap();
        std::fs::write(
            dir.join("SKILL.md"),
            "---\nname: my-skill\ndescription: does things\n---\nsteps...",
        )
        .unwrap();
        let skills = discover(&tmp);
        assert!(skills.iter().any(|s| s.name == "my-skill" && s.description == "does things"));
        let prompt = skills_prompt(&skills);
        assert!(prompt.contains("my-skill"));
        std::fs::remove_dir_all(&tmp).ok();
    }

    fn tempfile_dir() -> PathBuf {
        let p = std::env::temp_dir().join(format!("lucid-skill-test-{}", std::process::id()));
        std::fs::create_dir_all(&p).unwrap();
        p
    }
}
