import { init_paths, resolveSkillsDir } from "../../config/paths.js";
import { parseFrontmatter } from "../../markdown/frontmatter.js";
import { loadSkillsLock } from "./hub-lock.js";
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join, resolve, sep } from "node:path";
import AdmZip from "adm-zip";
//#region src/agent/skills/managed-store.ts
/**
* Managed skills live under ~/.xopc/skills (see resolveSkillsDir).
* Install/update/delete via zip or folder operations; used by gateway API.
*/
init_paths();
const MAX_SKILL_ZIP_BYTES = 15 * 1024 * 1024;
const SKILL_ID_RE = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62})$/;
function isValidSkillId(id) {
	return SKILL_ID_RE.test(id);
}
/** True when `baseDir` is the global managed skills root or a direct child folder. */
function isUnderManagedSkillsDir(baseDir) {
	const b = resolve(baseDir);
	const r = resolve(resolveSkillsDir());
	return b === r || b.startsWith(r + sep);
}
function isSafeZipPath(name) {
	if (!name) return false;
	const normalized = name.replace(/\\/g, "/");
	if (normalized.includes("..")) return false;
	if (normalized.startsWith("/") || /^\w:/.test(normalized)) return false;
	for (const p of normalized.split("/")) if (p === "..") return false;
	return true;
}
/** macOS/Windows noise; not part of the skill tree. */
function isIgnorableZipEntry(name) {
	const n = name.replace(/\\/g, "/");
	if (n.startsWith("__MACOSX/")) return true;
	const segments = n.split("/").filter(Boolean);
	for (const s of segments) {
		if (s === ".DS_Store" || s === "Thumbs.db" || s === "desktop.ini") return true;
		if (s.startsWith("._")) return true;
	}
	return false;
}
function readSkillMdMeta(skillMdPath) {
	try {
		const { frontmatter } = parseFrontmatter(readFileSync(skillMdPath, "utf-8"));
		return {
			name: frontmatter.name || "",
			description: frontmatter.description?.trim() || ""
		};
	} catch {
		return {
			name: "",
			description: ""
		};
	}
}
function listManagedSkillDirs() {
	const root = resolveSkillsDir();
	mkdirSync(root, { recursive: true });
	const lock = loadSkillsLock();
	let dirNames;
	try {
		dirNames = readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
	} catch {
		return [];
	}
	const out = [];
	for (const id of dirNames) {
		if (!isValidSkillId(id)) continue;
		const skillMd = join(root, id, "SKILL.md");
		if (!existsSync(skillMd)) continue;
		const meta = readSkillMdMeta(skillMd);
		const hub = lock.entries[id];
		out.push({
			id,
			name: meta.name || id,
			description: meta.description || "",
			path: join(root, id),
			...hub ? { hub } : {}
		});
	}
	out.sort((a, b) => a.id.localeCompare(b.id));
	return out;
}
function deleteManagedSkill(skillId) {
	if (!isValidSkillId(skillId)) throw new Error("Invalid skill id");
	const root = resolveSkillsDir();
	const dir = resolve(join(root, skillId));
	const rootResolved = resolve(root);
	if (!dir.startsWith(rootResolved + sep) && dir !== rootResolved) throw new Error("Invalid path");
	if (!existsSync(join(dir, "SKILL.md"))) throw new Error("Skill not found");
	rmSync(dir, {
		recursive: true,
		force: true
	});
}
function inferStripPrefix(primary) {
	const parts = primary.split("/").filter(Boolean);
	if (parts.length === 1 && parts[0].toLowerCase() === "skill.md") return "";
	return primary.slice(0, -8);
}
/**
* Install or replace a skill from a zip buffer. Layout: either `SKILL.md` at archive root
* or a single top-level folder containing `SKILL.md` (optionally nested one level).
*/
function installSkillFromZip(buffer, options) {
	if (buffer.length > 15728640) throw new Error(`Zip exceeds maximum size (${MAX_SKILL_ZIP_BYTES} bytes)`);
	const zip = new AdmZip(buffer);
	const safeEntries = zip.getEntries().filter((e) => !e.isDirectory && e.entryName && !isIgnorableZipEntry(e.entryName)).filter((e) => isSafeZipPath(e.entryName));
	if (safeEntries.length === 0) throw new Error("Zip is empty or invalid");
	const names = safeEntries.map((e) => e.entryName.replace(/\\/g, "/"));
	const skillMdPaths = names.filter((n) => /(^|\/)SKILL\.md$/i.test(n));
	if (skillMdPaths.length === 0) throw new Error("Zip must contain at least one SKILL.md");
	const shallow = skillMdPaths.filter((p) => p.split("/").filter(Boolean).length <= 2);
	if (shallow.length === 0) throw new Error("SKILL.md is nested too deeply; use a zip with SKILL.md at archive root or one folder (e.g. my-skill/SKILL.md)");
	shallow.sort((a, b) => a.length - b.length);
	const stripPrefix = inferStripPrefix(shallow[0]);
	if (stripPrefix) {
		const prefixNorm = stripPrefix.replace(/\\/g, "/");
		const outside = names.filter((n) => !n.startsWith(prefixNorm) && !isIgnorableZipEntry(n));
		if (outside.length > 0) throw new Error(`Invalid zip: expected all skill files under "${prefixNorm.replace(/\/$/, "")}/", but found "${outside[0]}". Put SKILL.md and assets in one folder, or zip that folder only.`);
	}
	const root = resolveSkillsDir();
	mkdirSync(root, { recursive: true });
	let targetId = options.skillId?.trim();
	if (!targetId) {
		if (stripPrefix) targetId = stripPrefix.replace(/\/$/, "").split("/")[0] || "";
		if (!targetId) {
			const skillName = shallow.find((n) => /(^|\/)SKILL\.md$/i.test(n)) || "";
			const entry = zip.getEntry(skillName);
			if (!entry) throw new Error("SKILL.md missing");
			const { frontmatter } = parseFrontmatter(entry.getData().toString("utf-8"));
			targetId = String(frontmatter.name || "").trim();
		}
	}
	if (!targetId || !isValidSkillId(targetId)) throw new Error("Could not determine skill id: use a folder named with the skill id, pass skillId, or set name: in SKILL.md (letters, digits, ._-)");
	const destDir = join(root, targetId);
	const destResolved = resolve(destDir);
	const rootResolved = resolve(root);
	if (!destResolved.startsWith(rootResolved + sep) && destResolved !== rootResolved) throw new Error("Invalid destination");
	if (existsSync(destDir)) {
		if (!options.overwrite) throw new Error(`Skill "${targetId}" already exists. Pass overwrite to replace.`);
		rmSync(destDir, {
			recursive: true,
			force: true
		});
	}
	mkdirSync(destDir, { recursive: true });
	for (const e of safeEntries) {
		const norm = e.entryName.replace(/\\/g, "/");
		let rel;
		if (stripPrefix) {
			const prefixNorm = stripPrefix.replace(/\\/g, "/");
			if (!norm.startsWith(prefixNorm)) continue;
			rel = norm.slice(prefixNorm.length).replace(/^\//, "");
		} else rel = norm;
		if (!rel || rel.includes("..")) continue;
		const targetPath = join(destDir, rel);
		const resolvedTarget = resolve(targetPath);
		if (!resolvedTarget.startsWith(destResolved + sep) && resolvedTarget !== destResolved) continue;
		mkdirSync(dirname(targetPath), { recursive: true });
		writeFileSync(targetPath, e.getData());
	}
	if (!existsSync(join(destDir, "SKILL.md"))) {
		rmSync(destDir, {
			recursive: true,
			force: true
		});
		throw new Error("Extracted content is missing SKILL.md");
	}
	return {
		skillId: targetId,
		path: destDir
	};
}
//#endregion
export { MAX_SKILL_ZIP_BYTES, deleteManagedSkill, installSkillFromZip, isIgnorableZipEntry, isUnderManagedSkillsDir, isValidSkillId, listManagedSkillDirs };

//# sourceMappingURL=managed-store.js.map