/**
 * SF Skill Discovery
 *
 * Detects skills installed during autonomous mode by comparing the current
 * skills directory against a snapshot taken at autonomous mode start.
 *
 * New skills are injected into the system prompt via before_agent_start,
 * making them visible to all subsequent units without requiring a reload.
 */
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

/** Skills directory — Agent Skills ecosystem path. */
export const SKILLS_DIR = join(homedir(), ".agents", "skills");
/** Snapshot of skill names at autonomous mode start */
let baselineSkills = null;
/**
 * Snapshot the current skills directory. Call at autonomous mode start.
 */
export function snapshotSkills() {
	baselineSkills = new Set(listSkillDirs());
}
/**
 * Clear the snapshot. Call when autonomous mode stops.
 */
export function clearSkillSnapshot() {
	baselineSkills = null;
}
/**
 * Check if a snapshot is active (autonomous mode is running with discovery).
 */
export function hasSkillSnapshot() {
	return baselineSkills !== null;
}
/**
 * Detect skills installed since the snapshot was taken.
 * Returns skill metadata for any new skills found.
 */
export function detectNewSkills() {
	if (!baselineSkills) return [];
	const current = listSkillDirs();
	const newSkills = [];
	for (const dir of current) {
		if (baselineSkills.has(dir)) continue;
		// Check both skill directories for the SKILL.md file
		const skillMdPath = resolveSkillMdPath(dir);
		if (!skillMdPath) continue;
		const meta = parseSkillFrontmatter(skillMdPath);
		if (meta) {
			newSkills.push({
				name: meta.name || dir,
				description: meta.description || `Skill: ${dir}`,
				location: skillMdPath,
			});
		}
	}
	return newSkills;
}
/**
 * Format discovered skills as an XML block matching pi's <available_skills> format.
 * This can be appended to the system prompt so the LLM sees them naturally.
 */
export function formatSkillsXml(skills) {
	if (skills.length === 0) return "";
	const entries = skills
		.map(
			(s) => `  <skill>
    <name>${escapeXml(s.name)}</name>
    <description>${escapeXml(s.description)}</description>
    <location>${escapeXml(s.location)}</location>
  </skill>`,
		)
		.join("\n");
	return `\n<newly_discovered_skills>
The following skills were installed during this autonomous mode session.
Use the read tool to load a skill's file when the task matches its description.

${entries}
</newly_discovered_skills>`;
}
// ─── Internals ────────────────────────────────────────────────────────────────
function listSkillDirsFrom(dir) {
	if (!existsSync(dir)) return [];
	try {
		return readdirSync(dir, { withFileTypes: true })
			.filter((d) => d.isDirectory())
			.map((d) => d.name);
	} catch {
		return [];
	}
}
function listSkillDirs() {
	const names = new Set();
	for (const name of listSkillDirsFrom(SKILLS_DIR)) names.add(name);
	return [...names];
}
function parseSkillFrontmatter(path) {
	try {
		const content = readFileSync(path, "utf-8");
		// Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
		if (!content.startsWith("---\n")) return null;
		const endIdx = content.indexOf("\n---", 4);
		if (endIdx === -1) return null;
		const fm = content.slice(4, endIdx);
		const result = {};
		const nameMatch = fm.match(/^name:\s*(.+)$/m);
		if (nameMatch) result.name = nameMatch[1].trim();
		const descMatch = fm.match(/^description:\s*(.+)$/m);
		if (descMatch) result.description = descMatch[1].trim();
		return result;
	} catch {
		return null;
	}
}
function resolveSkillMdPath(skillName) {
	const candidate = join(SKILLS_DIR, skillName, "SKILL.md");
	if (existsSync(candidate)) return candidate;
	return null;
}
function escapeXml(text) {
	return text
		.replace(/&/g, "&amp;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;")
		.replace(/"/g, "&quot;");
}
