import { existsSync, readFileSync, readdirSync } from "node:fs";
import path from "node:path";
import { blockConfigSchema } from "@prata.ma/anvil-schema";
import { createHash } from "node:crypto";
import { readFile, readdir, stat } from "node:fs/promises";
import { parse } from "yaml";
//#region src/recipe.ts
function parseBlockConfig(raw) {
	const validation = validateBlockConfig(parse(raw));
	if (!validation.valid) throw new Error(formatValidationError("Invalid block config", validation.issues));
	return validation.config;
}
function readBlockConfig(filePath) {
	const config = parseBlockConfig(readFileSync(filePath, "utf8"));
	const issues = validateBlockConfigAgainstBlockRoot(config, path.dirname(filePath));
	if (issues.length > 0) throw new Error(formatValidationError("Invalid block config", issues));
	return config;
}
function validateBlockConfig(value) {
	const parsed = blockConfigSchema.safeParse(value);
	if (!parsed.success) return {
		valid: false,
		issues: parsed.error.issues.map((issue) => ({
			path: issue.path.length > 0 ? `$.${issue.path.join(".")}` : "$",
			message: issue.message
		}))
	};
	return {
		valid: true,
		config: parsed.data
	};
}
function dedupe(values) {
	return [...new Set(values)];
}
const blockItemKinds = [
	"agents",
	"commands",
	"plugins",
	"references",
	"rules",
	"skills"
];
function validateBlockConfigAgainstBlockRoot(config, blockRoot) {
	const issues = [];
	const declaredUpstreams = new Set(Object.keys(config.upstreams ?? {}));
	for (const kind of blockItemKinds) {
		const inventory = config[kind];
		if (!inventory) continue;
		const declaredNames = new Set(Object.keys(inventory));
		const actualNames = new Set(collectBlockItemNames(blockRoot, kind));
		for (const [itemName, item] of Object.entries(inventory)) if (actualNames.has(itemName)) validateBlockItemUpstreamReferences(declaredUpstreams, kind, itemName, item, blockRoot, issues);
		else issues.push({
			path: `$.${kind}.${itemName}`,
			message: `declared ${kind.slice(0, -1)} item not found in content: ${itemName}`
		});
		for (const itemName of actualNames) {
			if (declaredNames.has(itemName)) continue;
			issues.push({
				path: `$.${kind}.${itemName}`,
				message: `undeclared ${kind.slice(0, -1)} item found in content: ${itemName}`
			});
		}
	}
	return issues;
}
function validateBlockItemUpstreamReferences(declaredUpstreams, kind, itemName, item, blockRoot, issues) {
	const refs = getBlockItemUpstreamRefs(item);
	for (const upstreamName of Object.keys(refs)) {
		if (declaredUpstreams.has(upstreamName)) continue;
		issues.push({
			path: `$.${kind}.${itemName}.upstreams.${upstreamName}`,
			message: `upstream reference not declared for ${kind.slice(0, -1)} item ${itemName}: ${upstreamName}`
		});
	}
	if (Object.keys(refs).length === 0) return;
	const folderShaped = isFolderShapedBlockItem(blockRoot, kind, itemName);
	for (const [upstreamName, target] of Object.entries(refs)) {
		if (!isSafeRelativeItemTarget(target)) {
			issues.push({
				path: `$.${kind}.${itemName}.upstreams.${upstreamName}`,
				message: `upstream target must be a safe relative path for ${kind.slice(0, -1)} item ${itemName}: ${target}`
			});
			continue;
		}
		if (folderShaped || target === ".") continue;
		issues.push({
			path: `$.${kind}.${itemName}.upstreams.${upstreamName}`,
			message: `file-shaped ${kind.slice(0, -1)} item ${itemName} only supports upstream target "."`
		});
	}
}
function getBlockItemUpstreamRefs(item) {
	if (item.upstream) return { [item.upstream]: "." };
	return item.upstreams ?? {};
}
function isSafeRelativeItemTarget(target) {
	if (target.length === 0 || path.isAbsolute(target)) return false;
	const normalized = path.posix.normalize(target.split("\\").join("/"));
	return normalized === target && normalized !== ".." && !normalized.startsWith("../");
}
function isFolderShapedBlockItem(blockRoot, kind, itemName) {
	const contentRoot = path.join(blockRoot, "content");
	switch (kind) {
		case "skills": return true;
		case "plugins": return existsSync(path.join(contentRoot, "plugins", itemName));
		case "references": return existsSync(path.join(contentRoot, "references", itemName, "content.md"));
		default: return false;
	}
}
function collectBlockItemNames(blockRoot, kind) {
	const contentRoot = path.join(blockRoot, "content");
	switch (kind) {
		case "agents": return listMarkdownBasenames(path.join(contentRoot, "agents"));
		case "commands": return dedupe([...listMarkdownBasenames(path.join(contentRoot, "commands")), ...listSkillCommandBasenames(path.join(contentRoot, "skills"))]);
		case "plugins": return listPluginNames(path.join(contentRoot, "plugins"));
		case "references": return listReferenceNames(path.join(contentRoot, "references"));
		case "rules": return listMarkdownBasenames(path.join(contentRoot, "rules"));
		case "skills": return listDirectoryNames(path.join(contentRoot, "skills"));
		default: return [];
	}
}
function listDirectoryNames(directoryPath) {
	if (!existsSync(directoryPath)) return [];
	return readdirSync(directoryPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
}
function listMarkdownBasenames(directoryPath) {
	if (!existsSync(directoryPath)) return [];
	return readdirSync(directoryPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path.basename(entry.name, ".md")).sort((left, right) => left.localeCompare(right));
}
function listPluginNames(directoryPath) {
	if (!existsSync(directoryPath)) return [];
	const names = /* @__PURE__ */ new Set();
	for (const entry of readdirSync(directoryPath, { withFileTypes: true })) {
		if (entry.isDirectory()) {
			names.add(entry.name);
			continue;
		}
		if (entry.isFile() && (entry.name.endsWith(".js") || entry.name.endsWith(".ts"))) names.add(path.basename(entry.name, path.extname(entry.name)));
	}
	return [...names].sort((left, right) => left.localeCompare(right));
}
function listReferenceNames(directoryPath) {
	if (!existsSync(directoryPath)) return [];
	const names = /* @__PURE__ */ new Set();
	for (const entry of readdirSync(directoryPath, { withFileTypes: true })) {
		if (entry.isFile() && entry.name.endsWith(".md")) {
			names.add(path.basename(entry.name, ".md"));
			continue;
		}
		if (entry.isDirectory() && existsSync(path.join(directoryPath, entry.name, "content.md"))) names.add(entry.name);
	}
	return [...names].sort((left, right) => left.localeCompare(right));
}
function listSkillCommandBasenames(skillsDirectoryPath) {
	if (!existsSync(skillsDirectoryPath)) return [];
	const commandNames = /* @__PURE__ */ new Set();
	for (const skillName of listDirectoryNames(skillsDirectoryPath)) for (const commandName of listMarkdownBasenames(path.join(skillsDirectoryPath, skillName, "commands"))) commandNames.add(commandName);
	return [...commandNames].sort((left, right) => left.localeCompare(right));
}
function formatValidationError(prefix, issues) {
	return `${prefix}: ${issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`;
}
//#endregion
//#region src/lib/metrics.ts
function createBlockLockMetrics(content) {
	const chars = content.length;
	const newlineCount = (content.match(/\n/g) ?? []).length;
	return {
		chars,
		lines: content.length === 0 ? 0 : content.endsWith("\n") ? newlineCount : newlineCount + 1,
		tokens: Math.ceil(chars / 4)
	};
}
function createEmptyBlockLockMetrics() {
	return {
		chars: 0,
		lines: 0,
		tokens: 0
	};
}
function sumBlockLockMetrics(values) {
	return values.reduce((summary, value) => {
		if (!value) return summary;
		summary.chars += value.chars;
		summary.lines += value.lines;
		summary.tokens += value.tokens;
		return summary;
	}, createEmptyBlockLockMetrics());
}
async function collectPathSnapshot(targetPath) {
	if ((await stat(targetPath)).isFile()) {
		const content = await readFile(targetPath);
		return {
			...createBlockLockMetrics(content.toString("utf8")),
			hash: createHash("sha256").update(content).digest("hex")
		};
	}
	const files = [];
	await collectFiles(targetPath, targetPath, files);
	files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
	const hash = createHash("sha256");
	const metrics = createEmptyBlockLockMetrics();
	for (const file of files) {
		hash.update(file.relativePath);
		hash.update(file.content);
		const fileMetrics = createBlockLockMetrics(file.content.toString("utf8"));
		metrics.chars += fileMetrics.chars;
		metrics.lines += fileMetrics.lines;
		metrics.tokens += fileMetrics.tokens;
	}
	return {
		...metrics,
		hash: hash.digest("hex")
	};
}
async function collectFiles(baseDir, currentDir, results) {
	const entries = await readdir(currentDir, { withFileTypes: true });
	await Promise.all(entries.map(async (entry) => {
		const fullPath = path.join(currentDir, entry.name);
		if (entry.isDirectory()) {
			if (entry.name === ".git" || entry.name === "node_modules") return;
			await collectFiles(baseDir, fullPath, results);
			return;
		}
		if (!entry.isFile()) return;
		results.push({
			content: await readFile(fullPath),
			relativePath: path.relative(baseDir, fullPath).split("\\").join("/")
		});
	}));
}
//#endregion
export { parseBlockConfig as a, sumBlockLockMetrics as i, createBlockLockMetrics as n, readBlockConfig as o, createEmptyBlockLockMetrics as r, validateBlockConfig as s, collectPathSnapshot as t };

//# sourceMappingURL=metrics-DMk3mFyO.mjs.map