//! 本机 Claude 技能扫描（skills / commands / plugins 三类）

use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

use serde::Serialize;

/// 技能元信息（前端 `/` 联想 + 后端注入用）
#[derive(Debug, Clone, Serialize)]
pub struct SkillInfo {
    pub name: String,
    pub description: String,
    pub argument_hint: Option<String>,
    /// skill | command | plugin
    pub source: String,
    /// SKILL.md 绝对路径（注入时读全文）
    pub path: String,
}

/// ~/.claude 目录（跨平台：USERPROFILE / HOME）
fn claude_home() -> Option<PathBuf> {
    std::env::var_os("USERPROFILE")
        .or_else(|| std::env::var_os("HOME"))
        .map(PathBuf::from)
        .map(|h| h.join(".claude"))
}

/// 剥离 YAML 标量值两侧的引号（`"..."` / `'...'`），简易 frontmatter 解析用
fn unquote(s: &str) -> &str {
    s.strip_prefix('"')
        .and_then(|x| x.strip_suffix('"'))
        .or_else(|| s.strip_prefix('\'').and_then(|x| x.strip_suffix('\'')))
        .unwrap_or(s)
}

/// 解析 markdown frontmatter 的 name / description / argument-hint / user_invocable
/// （简易，按行匹配，容错缩进与 CRLF；仅扫描 frontmatter 区段）
fn parse_frontmatter(md: &str) -> Option<(String, String, Option<String>, bool)> {
    let mut lines = md.lines();
    if lines.next()?.trim() != "---" {
        return None;
    }
    let mut name = None;
    let mut desc = None;
    let mut hint = None;
    let mut invocable = true;
    for line in lines {
        if line.trim() == "---" {
            break;
        }
        let l = line.trim_start();
        if let Some(v) = l.strip_prefix("name:") {
            name = Some(unquote(v.trim()).to_string());
        } else if let Some(v) = l.strip_prefix("description:") {
            desc = Some(unquote(v.trim()).to_string());
        } else if let Some(v) = l.strip_prefix("argument-hint:") {
            hint = Some(unquote(v.trim()).to_string());
        } else if let Some(v) = l.strip_prefix("user_invocable:") {
            invocable = v.trim() != "false";
        }
    }
    name.map(|n| (n, desc.unwrap_or_default(), hint, invocable))
}

/// 解析单个 SKILL.md / command md 为 SkillInfo（排除 user_invocable: false）
fn parse_skill_file(path: &Path, source: &str) -> Option<SkillInfo> {
    let md = fs::read_to_string(path).ok()?;
    let (name, description, argument_hint, invocable) = parse_frontmatter(&md).unwrap_or_else(|| {
        // 无 frontmatter（部分 commands）：用文件名兜底，默认可调用
        let stem = path
            .file_stem()
            .map(|s| s.to_string_lossy().to_string())
            .unwrap_or_default();
        (stem, String::new(), None, true)
    });
    if !invocable {
        return None;
    }
    Some(SkillInfo {
        name,
        description,
        argument_hint,
        source: source.to_string(),
        path: path.to_string_lossy().to_string(),
    })
}

/// 递归收集目录下所有 SKILL.md（用于 plugins/marketplaces 多层嵌套）
fn collect_skill_files(dir: &Path, out: &mut Vec<PathBuf>) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let p = entry.path();
            if p.is_dir() {
                // 跳过依赖/版本目录，避免递归爆炸
                let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
                if name == "node_modules" || name == ".git" {
                    continue;
                }
                collect_skill_files(&p, out);
            } else if p.file_name().and_then(|n| n.to_str()) == Some("SKILL.md") {
                out.push(p);
            }
        }
    }
}

/// 扫描三类来源，按 name 去重（skills 优先 > commands > plugins）
fn scan_skills() -> Vec<SkillInfo> {
    let home = match claude_home() {
        Some(h) => h,
        None => return Vec::new(),
    };
    let mut skills = Vec::new();
    let mut seen: HashSet<String> = HashSet::new();

    // 1. ~/.claude/skills/*/SKILL.md
    if let Ok(entries) = fs::read_dir(home.join("skills")) {
        for entry in entries.flatten() {
            if let Some(info) = parse_skill_file(&entry.path().join("SKILL.md"), "skill") {
                if seen.insert(info.name.clone()) {
                    skills.push(info);
                }
            }
        }
    }

    // 2. ~/.claude/commands/*.md
    if let Ok(entries) = fs::read_dir(home.join("commands")) {
        for entry in entries.flatten() {
            let p = entry.path();
            if p.extension().and_then(|e| e.to_str()) == Some("md") {
                if let Some(info) = parse_skill_file(&p, "command") {
                    if seen.insert(info.name.clone()) {
                        skills.push(info);
                    }
                }
            }
        }
    }

    // 3. ~/.claude/plugins/marketplaces/**/skills/*/SKILL.md（递归；cache 不在此路径下）
    let mut files = Vec::new();
    collect_skill_files(&home.join("plugins").join("marketplaces"), &mut files);
    for f in files {
        if let Some(info) = parse_skill_file(&f, "plugin") {
            if seen.insert(info.name.clone()) {
                skills.push(info);
            }
        }
    }

    skills
}

/// 技能扫描结果缓存（进程内；新增/改动技能需重启生效）
pub(crate) fn skills_cached() -> &'static Vec<SkillInfo> {
    static SKILLS: OnceLock<Vec<SkillInfo>> = OnceLock::new();
    SKILLS.get_or_init(scan_skills)
}

/// 按 name 读取技能全文（注入 system prompt）；扫描走缓存，仅读单个 SKILL.md
pub(crate) fn read_skill_content(name: &str) -> Option<String> {
    skills_cached()
        .iter()
        .find(|s| s.name == name)
        .and_then(|s| fs::read_to_string(&s.path).ok())
}
