import { lintAwaits } from "./lint.js";
import { validateExamplePrompts, validateMetaI18n } from "./meta-locale.js";
import { parse } from "acorn";
//#region src/agent/workflow/parser.ts
/**
* Workflow script parser.
*
* Responsibilities:
*   1. Parse the script with acorn (latest ECMA, top-level await + return allowed).
*   2. Enforce determinism — reject `Date.now()`, `Math.random()`, `new Date()`,
*      `require`, `import`, dynamic eval. This keeps future resume/replay possible
*      and surfaces non-deterministic mistakes early.
*   3. Require the first statement to be `export const meta = <literal>`, validate
*      the literal shape, and strip that line from the body returned to the runtime.
*
* Returning a `{ meta, body }` pair means the runtime can `vm.Script(body)` without
* any further AST work.
*/
const NONDETERMINISM_ERROR = "Workflow scripts must be deterministic: Date.now()/Math.random()/new Date() are unavailable. Pass timestamps via args or stamp them after the workflow returns.";
function parseWorkflowScript(script) {
	let ast;
	try {
		ast = parse(script, {
			ecmaVersion: "latest",
			sourceType: "module",
			allowAwaitOutsideFunction: true,
			allowReturnOutsideFunction: true,
			ranges: false,
			locations: true
		});
	} catch (e) {
		const msg = e instanceof Error ? e.message : String(e);
		throw new Error(`Workflow script parse error: ${msg}`);
	}
	assertDeterministicAst(ast);
	assertNoDangerousImports(ast);
	lintAwaits(ast);
	const first = ast.body?.[0];
	if (first?.type !== "ExportNamedDeclaration") throw new Error("`export const meta = { name, description }` must be the first statement in the script");
	const declaration = first.declaration;
	if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") throw new Error("meta export must be `export const meta = ...`");
	if (declaration.declarations.length !== 1) throw new Error("meta export must declare only `meta`");
	const declarator = declaration.declarations[0];
	if (declarator.id?.type !== "Identifier" || declarator.id.name !== "meta") throw new Error("meta export must declare `meta`");
	if (!declarator.init) throw new Error("meta must have a literal value");
	const meta = evaluateLiteral(declarator.init, "meta");
	validateMeta(meta);
	return {
		meta,
		body: script.slice(0, first.start) + script.slice(first.end)
	};
}
function assertDeterministicAst(node) {
	if (isDateNowCall(node) || isMathRandomCall(node) || isNewDateExpression(node)) throw new Error(NONDETERMINISM_ERROR);
	for (const child of astChildren(node)) assertDeterministicAst(child);
}
function assertNoDangerousImports(node) {
	if (node.type === "ImportDeclaration" || node.type === "ImportExpression") throw new Error("Workflow scripts cannot use `import` — only the exposed globals (agent, parallel, pipeline, phase, log, args, cwd, budget) are available.");
	if (node.type === "CallExpression") {
		const callee = node.callee;
		if (callee?.type === "Identifier" && (callee.name === "require" || callee.name === "eval")) throw new Error(`Workflow scripts cannot call \`${callee.name}\`.`);
	}
	for (const child of astChildren(node)) assertNoDangerousImports(child);
}
function astChildren(node) {
	const children = [];
	for (const value of Object.values(node)) if (Array.isArray(value)) {
		for (const v of value) if (isAstNode(v)) children.push(v);
	} else if (isAstNode(value)) children.push(value);
	return children;
}
function isAstNode(value) {
	return !!value && typeof value === "object" && typeof value.type === "string";
}
function isDateNowCall(node) {
	return node.type === "CallExpression" && isMemberExpression(node.callee, "Date", "now");
}
function isMathRandomCall(node) {
	return node.type === "CallExpression" && isMemberExpression(node.callee, "Math", "random");
}
function isNewDateExpression(node) {
	return node.type === "NewExpression" && node.callee?.type === "Identifier" && node.callee.name === "Date";
}
function isMemberExpression(node, objectName, propertyName) {
	if (node?.type !== "MemberExpression" || node.object?.type !== "Identifier" || node.object.name !== objectName) return false;
	const prop = node.property;
	if (!node.computed && prop?.type === "Identifier") return prop.name === propertyName;
	if (prop?.type === "Literal" && typeof prop.value === "string") return prop.value === propertyName;
	return false;
}
function evaluateLiteral(node, path) {
	switch (node.type) {
		case "ObjectExpression": {
			const out = {};
			for (const prop of node.properties) {
				if (prop.type === "SpreadElement") throw new Error(`spread not allowed in ${path}`);
				if (prop.type !== "Property") throw new Error(`only plain properties allowed in ${path}`);
				if (prop.computed) throw new Error(`computed keys not allowed in ${path}`);
				if (prop.kind !== "init" || prop.method) throw new Error(`methods/accessors not allowed in ${path}`);
				const key = propertyKey(prop.key, path);
				if (key === "__proto__" || key === "constructor" || key === "prototype") throw new Error(`reserved key name not allowed in ${path}: ${key}`);
				out[key] = evaluateLiteral(prop.value, `${path}.${key}`);
			}
			return out;
		}
		case "ArrayExpression": return node.elements.map((element, index) => {
			if (!element) throw new Error(`sparse arrays not allowed in ${path}`);
			if (element.type === "SpreadElement") throw new Error(`spread not allowed in ${path}`);
			return evaluateLiteral(element, `${path}[${index}]`);
		});
		case "Literal": return node.value;
		case "TemplateLiteral":
			if (node.expressions.length > 0) throw new Error(`template interpolation not allowed in ${path}`);
			return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join("");
		case "UnaryExpression":
			if (node.operator === "-" && node.argument?.type === "Literal" && typeof node.argument.value === "number") return -node.argument.value;
			throw new Error(`only negative-number unary allowed in ${path}`);
		default: throw new Error(`non-literal node type in ${path}: ${node.type}`);
	}
}
function propertyKey(node, path) {
	if (node.type === "Identifier") return node.name;
	if (node.type === "Literal" && (typeof node.value === "string" || typeof node.value === "number")) return String(node.value);
	throw new Error(`unsupported key type in ${path}: ${node.type}`);
}
function validateMeta(meta) {
	if (!meta || typeof meta !== "object") throw new Error("meta must be an object");
	const value = meta;
	if (typeof value.name !== "string" || !value.name.trim()) throw new Error("meta.name must be a non-empty string");
	if (!/^[a-z][a-z0-9_-]*$/.test(value.name)) throw new Error(`meta.name must be lowercase snake_case (got "${value.name}"). Example: "audit_repo".`);
	if (typeof value.description !== "string" || !value.description.trim()) throw new Error("meta.description must be a non-empty string");
	if (value.whenToUse !== void 0 && typeof value.whenToUse !== "string") throw new Error("meta.whenToUse must be a string");
	if (value.phases !== void 0) {
		if (!Array.isArray(value.phases)) throw new Error("meta.phases must be an array");
		for (const phase of value.phases) if (!phase || typeof phase !== "object" || typeof phase.title !== "string") throw new Error("each meta phase must have a title string");
	}
	if (value.tags !== void 0) {
		if (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== "string" || !tag.trim())) throw new Error("meta.tags must be an array of non-empty strings");
	}
	if (value.estimatedAgents !== void 0) {
		const est = value.estimatedAgents;
		if (!est || typeof est !== "object") throw new Error("meta.estimatedAgents must be an object");
		if (typeof est.min !== "number" || typeof est.max !== "number" || !Number.isFinite(est.min) || !Number.isFinite(est.max)) throw new Error("meta.estimatedAgents.min and .max must be finite numbers");
		if (est.min < 1 || est.max < est.min) throw new Error("meta.estimatedAgents requires min >= 1 and max >= min");
	}
	validateExamplePrompts(value.examplePrompts, "meta.examplePrompts");
	validateMetaI18n(value.i18n);
}
//#endregion
export { parseWorkflowScript };

//# sourceMappingURL=parser.js.map