/**
 * Skill Eval Harness — foundation for grading auto-created skills.
 *
 * Purpose: provide deterministic eval cases for skills so auto-generated
 * capabilities can be tested before being committed. Follows the
 * skill-optimizer pattern: cases + graders + workspaces.
 *
 * Consumer: /skills --eval, auto-skill creation acceptance gate.
 */
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";

const GRADER_TIMEOUT_MS = 30_000;

/**
 * Create an eval case directory for a skill.
 *
 * @param {string} skillPath — path to skill directory
 * @param {string} caseName — unique case identifier
 * @param {object} caseDef — { task: string, grader: string, hidden?: object }
 */
export function createEvalCase(skillPath, caseName, caseDef) {
	const evalDir = join(skillPath, "evals", caseName);
	mkdirSync(evalDir, { recursive: true });
	mkdirSync(join(evalDir, "work"), { recursive: true });

	writeFileSync(join(evalDir, "task.md"), caseDef.task, "utf-8");
	writeFileSync(join(evalDir, "grader.js"), caseDef.grader, "utf-8");
	if (caseDef.hidden) {
		mkdirSync(join(evalDir, "hidden"), { recursive: true });
		writeFileSync(
			join(evalDir, "hidden", "reference.json"),
			JSON.stringify(caseDef.hidden, null, 2),
			"utf-8",
		);
	}
	return { path: evalDir, caseName };
}

/**
 * Run a grader against a skill's work directory with timeout and sandbox.
 *
 * @param {string} evalDir — path to eval case directory
 * @param {object} ctx — { ui } for notifications
 * @returns {object} { passed: boolean, score: number, details: string[] }
 */
export async function runGrader(evalDir, _ctx) {
	const graderPath = join(evalDir, "grader.js");
	const workDir = join(evalDir, "work");
	if (!existsSync(graderPath)) {
		return { passed: false, score: 0, details: ["Grader not found"] };
	}

	try {
		const { grade } = await import(pathToFileURL(graderPath).href);
		if (typeof grade !== "function") {
			return {
				passed: false,
				score: 0,
				details: ["Grader must export a grade() function"],
			};
		}

		// Timeout wrapper to prevent infinite hangs
		const result = await Promise.race([
			grade(workDir),
			new Promise((_, reject) =>
				setTimeout(
					() =>
						reject(new Error(`Grader timed out after ${GRADER_TIMEOUT_MS}ms`)),
					GRADER_TIMEOUT_MS,
				),
			),
		]);

		return {
			passed: Boolean(result.passed),
			score: Number(result.score ?? (result.passed ? 1 : 0)),
			details: Array.isArray(result.details)
				? result.details
				: [String(result.details ?? "")],
		};
	} catch (err) {
		return {
			passed: false,
			score: 0,
			details: [`Grader error: ${err.message}`],
		};
	}
}

/**
 * Run all eval cases for a skill.
 *
 * @param {string} skillPath — path to skill directory
 * @param {object} ctx — { ui } for notifications
 * @returns {object} { skillName, totalCases, passedCases, totalScore, cases: [] }
 */
export async function runSkillEvals(skillPath, ctx) {
	const evalDir = join(skillPath, "evals");
	if (!existsSync(evalDir)) {
		return {
			skillName: skillPath,
			totalCases: 0,
			passedCases: 0,
			totalScore: 0,
			cases: [],
		};
	}

	const { readdirSync } = await import("node:fs");
	const cases = [];
	let passedCount = 0;
	let totalScore = 0;

	for (const entry of readdirSync(evalDir)) {
		const caseDir = join(evalDir, entry);
		const taskPath = join(caseDir, "task.md");
		if (!existsSync(taskPath)) continue;

		const result = await runGrader(caseDir, ctx);
		cases.push({
			caseName: entry,
			passed: result.passed,
			score: result.score,
			details: result.details,
		});
		if (result.passed) passedCount++;
		totalScore += result.score;
	}

	return {
		skillName: skillPath,
		totalCases: cases.length,
		passedCases: passedCount,
		totalScore: cases.length > 0 ? totalScore / cases.length : 0,
		cases,
	};
}

/**
 * Generate a default eval case for a skill based on its type.
 *
 * @param {object} skill — skill record from buildSkillRecord()
 * @returns {object} caseDef for createEvalCase()
 */
export function generateDefaultEvalCase(skill) {
	const task = `Test the ${skill.name} skill.

Instructions:
1. Read the skill definition at SKILL.md
2. Apply the skill to a realistic task matching its description
3. Verify the output matches expected behavior

Expected: The skill produces correct, safe output without side effects beyond its declared scope.
`;

	const grader = `/**
 * Default grader for ${skill.name}.
 * Checks that work directory contains expected artifacts.
 */
export async function grade(workDir) {
	const { existsSync } = await import("node:fs");
	const { join } = await import("node:path");

	const checks = [];
	let score = 0;

	// Example: check for expected output file
	if (existsSync(join(workDir, "result.txt"))) {
		checks.push("✓ result.txt exists");
		score += 0.5;
	} else {
		checks.push("✗ result.txt missing");
	}

	// Example: check for no unexpected files
	const unexpected = existsSync(join(workDir, "unexpected.txt"));
	if (!unexpected) {
		checks.push("✓ no unexpected artifacts");
		score += 0.5;
	} else {
		checks.push("✗ unexpected artifact found");
	}

	return {
		passed: score >= 0.8,
		score,
		details: checks,
	};
}
`;

	return { task, grader };
}
