/**
 * .agents/skills/ directory management
 *
 * Purpose: discover, validate, and load skills from repo-local
 * `.agents/skills/` and user-level skill directories.
 *
 * Consumer: skill loader, auto-skill creation, and model context assembly.
 */
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const SKILL_FILENAME = "SKILL.md";

export { SKILL_FILENAME };

const USER_SKILL_DIR = join(homedir(), ".agents", "skills");

export { USER_SKILL_DIR };

const BUNDLED_SKILL_DIR = join(
	dirname(fileURLToPath(import.meta.url)),
	"..",
	"..",
	"..",
	"skills",
);

export { BUNDLED_SKILL_DIR };

const WORKFLOW_SKILL_DIR = join(
	dirname(fileURLToPath(import.meta.url)),
	"..",
	"..",
	"..",
	"workflow-skills",
);

export { WORKFLOW_SKILL_DIR };

/**
 * Find all skill directories under a base path.
 */
export function discoverSkillDirs(basePath) {
	const skillRoot = join(basePath, ".agents", "skills");
	if (!existsSync(skillRoot)) return [];

	const dirs = [];
	for (const entry of readdirSync(skillRoot)) {
		const full = join(skillRoot, entry);
		if (statSync(full).isDirectory()) {
			const skillFile = join(full, SKILL_FILENAME);
			if (existsSync(skillFile)) {
				dirs.push({ name: entry, path: full, skillFile });
			}
		}
	}
	return dirs;
}

/**
 * Discover skills from all sources: project, user, built-in, and hidden workflow.
 *
 * Shadow protection: bundled skills with locked:true silently block
 * project/user skills with the same name. Hidden workflow skills are defaults:
 * project/user `.agents/skills/<name>/` can override them for repo-local
 * learning, but they are never user-visible unless the override says so.
 */
export function discoverAllSkills(projectPath, options = {}) {
	const bundledSources = [];
	const workflowSources = [];

	// Bundled SF skills (user-facing, shown in /skills catalog)
	if (options.includeBundled && existsSync(BUNDLED_SKILL_DIR)) {
		const bundledSkills = discoverSkillDirsInRoot(BUNDLED_SKILL_DIR);
		for (const s of bundledSkills) {
			bundledSources.push({ ...s, source: "bundled" });
		}
	}

	if (options.includeWorkflow && existsSync(WORKFLOW_SKILL_DIR)) {
		const workflowSkills = discoverSkillDirsInRoot(WORKFLOW_SKILL_DIR);
		for (const s of workflowSkills) {
			workflowSources.push({ ...s, source: "workflow" });
		}
	}

	// Build the set of locked names before appending lower-priority sources.
	// Bundled skills are locked when their frontmatter declares `locked: true`.
	const lockedNames = buildLockedNameSet(bundledSources);

	const sources = [];
	const seenNames = new Set();
	function addSkill(skill) {
		if (seenNames.has(skill.name)) return;
		sources.push(skill);
		seenNames.add(skill.name);
	}

	// Locked bundled skills are product-owned and must also win all name conflicts.
	for (const s of bundledSources) {
		if (lockedNames.has(s.name)) addSkill(s);
	}

	// Project skills — shadowed if name is locked
	if (projectPath) {
		const projectSkills = discoverSkillDirs(projectPath);
		for (const s of projectSkills) {
			if (!lockedNames.has(s.name)) {
				addSkill({ ...s, source: "project" });
			}
		}
	}

	// User skills — shadowed if name is locked
	if (existsSync(USER_SKILL_DIR)) {
		const userSkills = discoverSkillDirs(USER_SKILL_DIR);
		for (const s of userSkills) {
			// User skills have a different root structure
			s.path = s.path.replace(/\.agents\/skills$/, "");
			if (!lockedNames.has(s.name)) {
				addSkill({ ...s, source: "user" });
			}
		}
	}

	// Non-locked bundled skills are defaults and lose to project/user overlays.
	for (const s of bundledSources) addSkill(s);

	// Hidden workflow skills are product defaults and lose to project/user
	// overlays with the same name.
	for (const s of workflowSources) addSkill(s);

	return sources;
}

/**
 * Build the set of skill names that are locked and cannot be overridden.
 *
 * Bundled skills are locked when their SKILL.md frontmatter contains
 * `locked: true`.
 */
function buildLockedNameSet(prioritySources) {
	const locked = new Set();
	for (const { name, source, path } of prioritySources) {
		if (source === "bundled") {
			const raw = readRawFrontmatter(path);
			if (raw && /^\s*locked\s*:\s*true\s*$/m.test(raw)) {
				locked.add(name);
			}
		}
	}
	return locked;
}

/**
 * Read only the YAML frontmatter block from a SKILL.md without full parsing.
 *
 * Returns the raw YAML string between --- delimiters, or null.
 */
function readRawFrontmatter(skillDir) {
	const path = join(skillDir, SKILL_FILENAME);
	if (!existsSync(path)) return null;
	try {
		const content = readFileSync(path, "utf-8");
		const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
		return match ? match[1] : null;
	} catch {
		return null;
	}
}

function discoverSkillDirsInRoot(skillRoot) {
	if (!existsSync(skillRoot)) return [];

	const dirs = [];
	for (const entry of readdirSync(skillRoot)) {
		const full = join(skillRoot, entry);
		if (statSync(full).isDirectory()) {
			const skillFile = join(full, SKILL_FILENAME);
			if (existsSync(skillFile)) {
				dirs.push({ name: entry, path: full, skillFile });
			}
		}
	}
	return dirs;
}

/**
 * Read the raw content of a skill file.
 */
export function readSkillFile(skillDir) {
	const path = join(skillDir, SKILL_FILENAME);
	if (!existsSync(path)) return null;
	try {
		return readFileSync(path, "utf-8");
	} catch {
		return null;
	}
}
