/**
 * Agent discovery and configuration
 */
import * as fs from "node:fs";
import * as path from "node:path";
import { getAgentDir, parseFrontmatter } from "@singularity-forge/coding-agent";
import { parse as parseYaml } from "yaml";
import { validatePromptParts } from "./prompt-parts.js";

const PROJECT_AGENT_DIR_CANDIDATES = [".sf", ".pi"];

export function validateAgentDefinition(definition) {
	const errors = [];
	if (typeof definition?.name !== "string" || !definition.name.trim()) {
		errors.push("name must be a non-empty string");
	}
	if (
		typeof definition?.description !== "string" ||
		!definition.description.trim()
	) {
		errors.push("description must be a non-empty string");
	}
	if (
		typeof definition?.systemPrompt !== "string" ||
		!definition.systemPrompt.trim()
	) {
		errors.push("prompt must be a non-empty string");
	}
	errors.push(...validatePromptParts(definition?.promptParts));
	return errors;
}

export function parseConflictsWith(value) {
	if (typeof value !== "string") return undefined;
	const conflicts = value
		.split(",")
		.map((s) => s.trim())
		.filter(Boolean);
	return conflicts.length > 0 ? conflicts : undefined;
}
function parseAgentTools(value) {
	if (typeof value === "string") {
		const tools = value
			.split(",")
			.map((tool) => tool.trim())
			.filter(Boolean);
		if (tools.includes("*")) return undefined;
		return tools.length > 0 ? tools : undefined;
	}
	if (Array.isArray(value)) {
		const tools = value
			.flatMap((tool) => (typeof tool === "string" ? tool.split(",") : []))
			.map((tool) => tool.trim())
			.filter(Boolean);
		if (tools.includes("*")) return undefined;
		return tools.length > 0 ? tools : undefined;
	}
	return undefined;
}
function parseAgentModel(value) {
	return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function parseAgentVisibility(value) {
	return value === "internal" ? "internal" : "public";
}
function isAgentFileName(name) {
	return (
		name.endsWith(".md") ||
		name.endsWith(".agent.yaml") ||
		name.endsWith(".agent.yml")
	);
}
function parseMarkdownAgent(content) {
	const { frontmatter, body } = parseFrontmatter(content);
	return {
		name: frontmatter.name,
		displayName: frontmatter.displayName,
		description: frontmatter.description,
		tools: frontmatter.tools,
		model: frontmatter.model,
		visibility: frontmatter.visibility,
		conflictsWith: frontmatter.conflicts_with,
		systemPrompt: body,
	};
}
function parseYamlAgent(content) {
	const doc = parseYaml(content) ?? {};
	return {
		name: doc.name,
		displayName: doc.displayName,
		description: doc.description,
		tools: doc.tools,
		model: doc.model,
		visibility: doc.visibility,
		conflictsWith: doc.conflicts_with ?? doc.conflictsWith,
		systemPrompt: doc.prompt,
		promptParts: doc.promptParts,
		sidekick: doc.sidekick,
	};
}
function loadAgentsFromDir(dir, source) {
	const agents = [];
	if (!fs.existsSync(dir)) {
		return agents;
	}
	let entries;
	try {
		entries = fs.readdirSync(dir, { withFileTypes: true });
	} catch {
		return agents;
	}
	for (const entry of entries) {
		if (!isAgentFileName(entry.name)) continue;
		if (!entry.isFile() && !entry.isSymbolicLink()) continue;
		const filePath = path.join(dir, entry.name);
		let content;
		try {
			content = fs.readFileSync(filePath, "utf-8");
		} catch {
			continue;
		}
		let definition;
		try {
			definition = entry.name.endsWith(".md")
				? parseMarkdownAgent(content)
				: parseYamlAgent(content);
		} catch {
			continue;
		}
		const validationErrors = validateAgentDefinition(definition);
		if (validationErrors.length > 0) {
			if (source === "builtin") {
				throw new Error(
					`Invalid built-in agent ${filePath}: ${validationErrors.join("; ")}`,
				);
			}
			continue;
		}
		const tools = parseAgentTools(definition.tools);
		const conflictsWith = parseConflictsWith(definition.conflictsWith);
		agents.push({
			name: definition.name,
			displayName: definition.displayName,
			description: definition.description,
			tools: tools && tools.length > 0 ? tools : undefined,
			model: parseAgentModel(definition.model),
			visibility: parseAgentVisibility(definition.visibility),
			conflictsWith,
			promptParts: definition.promptParts,
			sidekick: definition.sidekick,
			systemPrompt: definition.systemPrompt,
			source,
			filePath,
		});
	}
	return agents;
}
function isDirectory(p) {
	try {
		return fs.statSync(p).isDirectory();
	} catch {
		return false;
	}
}
function findNearestProjectAgentsDir(cwd) {
	let currentDir = cwd;
	while (true) {
		// Prefer the documented project-local location while preserving support
		// for older workarounds that placed agents under .pi/agents.
		for (const configDir of PROJECT_AGENT_DIR_CANDIDATES) {
			const candidate = path.join(currentDir, configDir, "agents");
			if (isDirectory(candidate)) return candidate;
		}
		const parentDir = path.dirname(currentDir);
		if (parentDir === currentDir) return null;
		currentDir = parentDir;
	}
}
/**
 * Resolve the built-in agents directory shipped with SF.
 *
 * Built-in agents live in src/resources/extensions/sf/agents/ (source) →
 * dist/resources/extensions/sf/agents/ (build) → ~/.sf/agent/extensions/sf/
 * agents/ (installed). At runtime this module sits at .../subagent/agents.js;
 * the sibling agents/ directory is resolved via import.meta.dirname.
 *
 * Returns an empty list if the directory doesn't exist (older builds without
 * built-ins, or an unusual install path).
 */
function loadBuiltinAgents() {
	const moduleDir = import.meta.dirname;
	if (!moduleDir) return [];
	const builtinDir = path.join(moduleDir, "..", "agents");
	return loadAgentsFromDir(builtinDir, "builtin");
}

export function discoverAgents(cwd, scope) {
	const userDir = path.join(getAgentDir(), "agents");
	const projectAgentsDir = findNearestProjectAgentsDir(cwd);
	// Built-in agents ship with SF and have lowest precedence — user/project
	// definitions with the same name override them. Used for review-code +
	// triage-decider as the foundation for SF's self-driven triage pipeline.
	const builtinAgents = scope === "project" ? [] : loadBuiltinAgents();
	const userAgents =
		scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
	const projectAgents =
		scope === "user" || !projectAgentsDir
			? []
			: loadAgentsFromDir(projectAgentsDir, "project");
	const agentMap = new Map();
	if (scope === "both") {
		for (const agent of builtinAgents) agentMap.set(agent.name, agent);
		for (const agent of userAgents) agentMap.set(agent.name, agent);
		for (const agent of projectAgents) agentMap.set(agent.name, agent);
	} else if (scope === "user") {
		for (const agent of builtinAgents) agentMap.set(agent.name, agent);
		for (const agent of userAgents) agentMap.set(agent.name, agent);
	} else {
		for (const agent of projectAgents) agentMap.set(agent.name, agent);
	}
	return { agents: Array.from(agentMap.values()), projectAgentsDir };
}
export function formatAgentList(agents, maxItems) {
	if (agents.length === 0) return { text: "none", remaining: 0 };
	const listed = agents.slice(0, maxItems);
	const remaining = agents.length - listed.length;
	return {
		text: listed
			.map((a) => `${a.name} (${a.source}): ${a.description}`)
			.join("; "),
		remaining,
	};
}
