/**
 * Skill-related preferences: resolution, discovery, and formatting.
 *
 * Contains all logic for resolving skill references from preferences
 * to absolute filesystem paths, plus skill discovery and staleness config.
 */
import { existsSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { isAbsolute, join } from "node:path";
import { statSync } from "node:fs";
import { validatePreferences } from "./preferences-validation.js";
import { loadEffectiveNerfPreferences } from "./preferences.js";
/**
 * Known skill directories, in priority order.
 * Global skills (~/.agents/skills/) take precedence over project skills.
 * Legacy ~/.nerf/agent/skills/ is included as a fallback for pre-migration installs.
 */
export function getSkillSearchDirs(cwd) {
    const dirs = [
        { dir: join(homedir(), ".agents", "skills"), method: "user-skill" },
        { dir: join(cwd, ".agents", "skills"), method: "project-skill" },
    ];
    // Legacy fallback — read skills from old NERF directory only if migration hasn't completed
    const legacyDir = join(homedir(), ".nerf", "agent", "skills");
    if (existsSync(legacyDir) && !existsSync(join(legacyDir, ".migrated-to-agents"))) {
        dirs.push({ dir: legacyDir, method: "user-skill" });
    }
    return dirs;
}
/**
 * Resolve a single skill reference to an absolute path.
 *
 * Resolution order:
 * 1. Absolute path to a file -> check existsSync
 * 2. Absolute path to a directory -> check for SKILL.md inside
 * 3. Bare name -> scan known skill directories for <name>/SKILL.md
 */
export function resolveSkillReference(ref, cwd) {
    const trimmed = ref.trim();
    // Expand tilde
    const expanded = trimmed.startsWith("~/")
        ? join(homedir(), trimmed.slice(2))
        : trimmed;
    // Absolute path
    if (isAbsolute(expanded)) {
        // Direct file reference
        if (existsSync(expanded)) {
            // Check if it's a directory -- look for SKILL.md inside
            try {
                const stat = statSync(expanded);
                if (stat.isDirectory()) {
                    const skillFile = join(expanded, "SKILL.md");
                    if (existsSync(skillFile)) {
                        return { original: ref, resolvedPath: skillFile, method: "absolute-dir" };
                    }
                    return { original: ref, resolvedPath: null, method: "unresolved" };
                }
            }
            catch { /* fall through */ }
            return { original: ref, resolvedPath: expanded, method: "absolute-path" };
        }
        // Maybe it's a directory path without SKILL.md suffix
        const withSkillMd = join(expanded, "SKILL.md");
        if (existsSync(withSkillMd)) {
            return { original: ref, resolvedPath: withSkillMd, method: "absolute-dir" };
        }
        return { original: ref, resolvedPath: null, method: "unresolved" };
    }
    // Bare name -- scan known skill directories
    for (const { dir, method } of getSkillSearchDirs(cwd)) {
        if (!existsSync(dir))
            continue;
        try {
            const entries = readdirSync(dir, { withFileTypes: true });
            for (const entry of entries) {
                if (!entry.isDirectory() && !entry.isSymbolicLink())
                    continue;
                if (entry.name === expanded) {
                    const skillFile = join(dir, entry.name, "SKILL.md");
                    if (existsSync(skillFile)) {
                        return { original: ref, resolvedPath: skillFile, method };
                    }
                }
            }
        }
        catch { /* directory not readable -- skip */ }
    }
    return { original: ref, resolvedPath: null, method: "unresolved" };
}
/**
 * Resolve all skill references in a preferences object.
 * Caches resolution per reference string to avoid redundant filesystem scans.
 */
export function resolveAllSkillReferences(preferences, cwd) {
    const validated = validatePreferences(preferences).preferences;
    preferences = validated;
    const resolutions = new Map();
    const warnings = [];
    function resolve(ref) {
        const existing = resolutions.get(ref);
        if (existing)
            return existing;
        const result = resolveSkillReference(ref, cwd);
        resolutions.set(ref, result);
        if (result.method === "unresolved") {
            warnings.push(ref);
        }
        return result;
    }
    // Resolve all skill lists
    for (const skill of preferences.always_use_skills ?? [])
        resolve(skill);
    for (const skill of preferences.prefer_skills ?? [])
        resolve(skill);
    for (const skill of preferences.avoid_skills ?? [])
        resolve(skill);
    // Resolve skill rules
    for (const rule of preferences.skill_rules ?? []) {
        for (const skill of rule.use ?? [])
            resolve(skill);
        for (const skill of rule.prefer ?? [])
            resolve(skill);
        for (const skill of rule.avoid ?? [])
            resolve(skill);
    }
    return { resolutions, warnings };
}
/**
 * Format a skill reference for the system prompt.
 * If resolved, shows the path so the agent knows exactly where to read.
 * If unresolved, marks it clearly.
 */
export function formatSkillRef(ref, resolutions) {
    const resolution = resolutions.get(ref);
    if (!resolution || resolution.method === "unresolved") {
        return `${ref} (⚠ not found — check skill name or path)`;
    }
    // For absolute paths where SKILL.md is just appended, don't clutter the output
    if (resolution.method === "absolute-path" || resolution.method === "absolute-dir") {
        return ref;
    }
    // For bare names resolved from skill directories, show the resolved path
    return `${ref} → \`${resolution.resolvedPath}\``;
}
/**
 * Resolve the skill discovery mode from effective preferences.
 * Defaults to "suggest" -- skills are identified during research but not installed automatically.
 */
export function resolveSkillDiscoveryMode() {
    const prefs = loadEffectiveNerfPreferences();
    return prefs?.preferences.skill_discovery ?? "suggest";
}
/**
 * Resolve the skill staleness threshold in days.
 * Returns 0 if disabled, default 60 if not configured.
 */
export function resolveSkillStalenessDays() {
    const prefs = loadEffectiveNerfPreferences();
    return prefs?.preferences.skill_staleness_days ?? 60;
}
