import { CONTRADICTION_CODES } from "./plan-types.mjs";
//#region extensions/crypto/src/services/plan-validator.ts
const KNOWN_TOOLS = new Set([
	"defi_swap",
	"defi_price",
	"defi_balance",
	"transfer",
	"manage_orders",
	"analytics",
	"market_intel",
	"clawnch_info",
	"clawnch_launch",
	"clawnch_fees",
	"clawnchconnect",
	"crypto_workflow",
	"cost_basis",
	"permit2",
	"liquidity",
	"bridge",
	"block_explorer",
	"watch_activity",
	"herd_intelligence",
	"molten",
	"clawnx",
	"hummingbot",
	"wayfinder",
	"bankr_launch",
	"bankr_automate",
	"bankr_polymarket",
	"bankr_leverage"
]);
const WRITE_TOOLS = new Set([
	"defi_swap",
	"transfer",
	"clawnch_launch",
	"liquidity",
	"bridge",
	"permit2",
	"bankr_launch",
	"bankr_automate",
	"bankr_polymarket",
	"bankr_leverage"
]);
const TOKEN_ALIASES = {
	WETH: "ETH",
	weth: "ETH",
	eth: "ETH",
	USDC: "USD_STABLE",
	usdc: "USD_STABLE",
	USDT: "USD_STABLE",
	usdt: "USD_STABLE",
	DAI: "USD_STABLE",
	dai: "USD_STABLE"
};
const MAX_PLAN_DEPTH = 10;
const MAX_PLAN_NODES = 50;
const MAX_LOOP_ITERATIONS = 1e3;
const HIGH_VALUE_ETH_THRESHOLD = 1;
const MAX_TOTAL_GAS_ETH = .5;
var PlanValidator = class {
	issues = [];
	nodeIds = /* @__PURE__ */ new Set();
	toolsUsed = /* @__PURE__ */ new Set();
	chainsUsed = /* @__PURE__ */ new Set();
	executionOrder = [];
	validate(plan) {
		this.issues = [];
		this.nodeIds = /* @__PURE__ */ new Set();
		this.toolsUsed = /* @__PURE__ */ new Set();
		this.chainsUsed = /* @__PURE__ */ new Set();
		this.executionOrder = [];
		this.validateStructure(plan.root, 0);
		this.validateTiming(plan);
		this.validateFinancial(plan.root);
		this.validateTools(plan.root);
		this.validateSafety(plan.root);
		this.validateDependencies(plan.root);
		return {
			valid: !this.issues.some((i) => i.severity === "error"),
			issues: this.issues,
			estimatedGasEth: this.estimateGas(plan.root),
			estimatedDurationMs: this.estimateDuration(plan.root),
			toolsUsed: [...this.toolsUsed],
			chainsUsed: [...this.chainsUsed]
		};
	}
	validateStructure(node, depth) {
		if (depth > MAX_PLAN_DEPTH) {
			this.addIssue("error", node.id, "STRUCT_DEPTH", `Plan exceeds maximum nesting depth of ${MAX_PLAN_DEPTH}.`);
			return;
		}
		if (this.nodeIds.has(node.id)) this.addIssue("error", node.id, "STRUCT_DUP_ID", `Duplicate node ID: "${node.id}". Each step must have a unique ID.`);
		this.nodeIds.add(node.id);
		if (this.nodeIds.size > MAX_PLAN_NODES) {
			this.addIssue("error", node.id, "STRUCT_TOO_MANY", `Plan exceeds maximum of ${MAX_PLAN_NODES} nodes.`);
			return;
		}
		if (!node.label || node.label.trim().length === 0) this.addIssue("warning", node.id, "STRUCT_NO_LABEL", "Node has no label. Labels help users understand the plan.");
		switch (node.type) {
			case "action":
				this.executionOrder.push(node.id);
				break;
			case "sequence":
				for (const step of node.steps) this.validateStructure(step, depth + 1);
				break;
			case "parallel":
				for (const step of node.steps) this.validateStructure(step, depth + 1);
				break;
			case "if":
				this.validateCondition(node.condition, node.id);
				this.validateStructure(node.then, depth + 1);
				if (node.else) this.validateStructure(node.else, depth + 1);
				break;
			case "wait":
				if (!node.until && !node.durationMs && !node.untilTime) this.addIssue("error", node.id, "STRUCT_WAIT_EMPTY", "Wait node has no condition, duration, or target time. It would wait forever.");
				if (node.until && node.durationMs) this.addIssue("warning", node.id, "STRUCT_WAIT_AMBIGUOUS", "Wait has both a condition and a duration. The condition takes priority; duration is used as max wait.");
				if (node.until) this.validateCondition(node.until, node.id);
				break;
			case "loop":
				if (!node.maxIterations || node.maxIterations <= 0) this.addIssue("error", node.id, CONTRADICTION_CODES.INFINITE_LOOP, "Loop has no maxIterations or it is <= 0. This would loop forever.");
				else if (node.maxIterations > MAX_LOOP_ITERATIONS) this.addIssue("error", node.id, CONTRADICTION_CODES.INFINITE_LOOP, `Loop maxIterations (${node.maxIterations}) exceeds safety limit of ${MAX_LOOP_ITERATIONS}.`);
				if (!node.exitWhen) this.addIssue("warning", node.id, "LOOP_NO_EXIT", "Loop has no exitWhen condition. It will run exactly maxIterations times.");
				else this.validateCondition(node.exitWhen, node.id);
				this.validateStructure(node.body, depth + 1);
				break;
		}
	}
	validateCondition(cond, nodeId) {
		if (cond.type === "compare") {
			this.validateValueRef(cond.left, nodeId);
			this.validateValueRef(cond.right, nodeId);
		} else if (cond.type === "logic") {
			if (cond.conditions.length === 0) this.addIssue("error", nodeId, "COND_EMPTY", `Logic "${cond.op}" has no sub-conditions.`);
			if (cond.op === "not" && cond.conditions.length !== 1) this.addIssue("error", nodeId, "COND_NOT_ARITY", "\"not\" must have exactly one sub-condition.");
			for (const sub of cond.conditions) this.validateCondition(sub, nodeId);
		}
	}
	validateValueRef(ref, nodeId) {
		if (typeof ref !== "object" || ref === null) return;
		if (ref.type === "step_output") {} else if (ref.type === "runtime") {
			if (!new Set([
				"price",
				"balance",
				"gas_price",
				"timestamp",
				"block_number"
			]).has(ref.fn)) this.addIssue("error", nodeId, "REF_BAD_FN", `Unknown runtime function: "${ref.fn}".`);
		}
	}
	validateTiming(plan) {
		if (!plan.trigger) return;
		const now = Date.now();
		switch (plan.trigger.type) {
			case "time": {
				const at = new Date(plan.trigger.at).getTime();
				if (isNaN(at)) this.addIssue("error", null, CONTRADICTION_CODES.IMPOSSIBLE_TIMING, `Invalid trigger time: "${plan.trigger.at}". Use ISO 8601 format.`);
				else if (at <= now) this.addIssue("error", null, CONTRADICTION_CODES.IMPOSSIBLE_TIMING, `Trigger time "${plan.trigger.at}" is in the past.`);
				else if (at - now > 720 * 60 * 60 * 1e3) this.addIssue("warning", null, "TIME_FAR_FUTURE", "Trigger is more than 30 days out. Consider whether conditions might change.");
				break;
			}
			case "interval":
				if (plan.trigger.everyMs < 3e4) this.addIssue("error", null, CONTRADICTION_CODES.IMPOSSIBLE_TIMING, "Interval must be at least 30 seconds.");
				if (plan.trigger.everyMs < 6e4) this.addIssue("warning", null, "TIME_FAST_INTERVAL", "Interval under 1 minute may cause excessive RPC calls and gas costs.");
				if (plan.trigger.maxRuns && plan.trigger.maxRuns > 1e4) this.addIssue("warning", null, "TIME_MANY_RUNS", `${plan.trigger.maxRuns} executions is a lot. Consider a reasonable bound.`);
				break;
			case "condition":
				if (plan.trigger.pollIntervalMs && plan.trigger.pollIntervalMs < 1e4) this.addIssue("warning", null, "TIME_FAST_POLL", "Polling faster than 10s may cause rate limiting.");
				if (!plan.trigger.expiresAfterMs) this.addIssue("warning", null, "TIME_NO_EXPIRY", "Condition trigger has no expiry. It will watch indefinitely. Consider setting expiresAfterMs.");
				this.validateCondition(plan.trigger.when, "trigger");
				break;
		}
		this.validateWaitTiming(plan.root, now);
	}
	validateWaitTiming(node, now) {
		if (node.type === "wait") {
			if (node.untilTime) {
				const t = new Date(node.untilTime).getTime();
				if (isNaN(t)) this.addIssue("error", node.id, CONTRADICTION_CODES.IMPOSSIBLE_TIMING, `Invalid wait time: "${node.untilTime}".`);
			}
			if (node.maxWaitMs && node.maxWaitMs > 10080 * 60 * 1e3) this.addIssue("warning", node.id, "WAIT_LONG", "Wait timeout exceeds 7 days. Consider a shorter bound.");
			if (!node.maxWaitMs && node.until) this.addIssue("warning", node.id, "WAIT_NO_TIMEOUT", "Wait on condition has no maxWaitMs. It could wait indefinitely. Default: 24h.");
		}
		if (node.type === "sequence" || node.type === "parallel") for (const s of node.steps) this.validateWaitTiming(s, now);
		else if (node.type === "if") {
			this.validateWaitTiming(node.then, now);
			if (node.else) this.validateWaitTiming(node.else, now);
		} else if (node.type === "loop") this.validateWaitTiming(node.body, now);
	}
	validateFinancial(root) {
		const actions = this.collectActions(root);
		const buys = [];
		const sells = [];
		for (const action of actions) {
			const tool = action.tool;
			const params = action.params;
			if (tool === "defi_swap") {
				const tokenIn = this.resolveParamString(params.token_in ?? params.tokenIn);
				const tokenOut = this.resolveParamString(params.token_out ?? params.tokenOut);
				const amount = this.resolveParamString(params.amount);
				if (tokenIn) sells.push({
					nodeId: action.id,
					token: this.normalizeToken(tokenIn),
					amount
				});
				if (tokenOut) buys.push({
					nodeId: action.id,
					token: this.normalizeToken(tokenOut)
				});
			} else if (tool === "transfer") {
				const token = this.resolveParamString(params.token);
				const amount = this.resolveParamString(params.amount);
				if (token) sells.push({
					nodeId: action.id,
					token: this.normalizeToken(token),
					amount
				});
			}
		}
		for (const buy of buys) for (const sell of sells) if (buy.token === sell.token && buy.nodeId !== sell.nodeId) this.addIssue("warning", buy.nodeId, CONTRADICTION_CODES.BUY_AND_SELL_SAME_TOKEN, `Plan both buys and sells ${buy.token} (steps "${buy.nodeId}" and "${sell.nodeId}"). This may be intentional (arbitrage) or a mistake.`);
		for (let i = 0; i < actions.length; i++) for (let j = i + 1; j < actions.length; j++) if (actions[i].tool === actions[j].tool && this.paramsEqual(actions[i].params, actions[j].params)) this.addIssue("warning", actions[j].id, CONTRADICTION_CODES.DUPLICATE_ACTION, `Step "${actions[j].id}" appears to be a duplicate of "${actions[i].id}" (same tool and parameters).`);
		const spendsByToken = /* @__PURE__ */ new Map();
		for (const sell of sells) {
			const existing = spendsByToken.get(sell.token) ?? [];
			existing.push(sell.nodeId);
			spendsByToken.set(sell.token, existing);
		}
		for (const [token, nodeIds] of spendsByToken) if (nodeIds.length > 1) this.addIssue("warning", nodeIds[1], CONTRADICTION_CODES.SPEND_MORE_THAN_BALANCE, `Multiple steps spend ${token} (${nodeIds.join(", ")}). The second step may not have enough balance. Consider checking balance between steps.`);
	}
	validateTools(node) {
		if (node.type === "action") {
			this.toolsUsed.add(node.tool);
			if (!KNOWN_TOOLS.has(node.tool)) this.addIssue("warning", node.id, CONTRADICTION_CODES.MISSING_TOOL, `Unknown tool: "${node.tool}". It may be a custom or future tool.`);
			if (WRITE_TOOLS.has(node.tool) && !node.requireConfirmation) this.addIssue("info", node.id, "TOOL_WRITE_NO_CONFIRM", `"${node.tool}" is a write operation. Consider setting requireConfirmation: true for safety.`);
			const chainId = this.resolveParamNumber(node.params.chain_id ?? node.params.chainId);
			if (chainId) {
				this.chainsUsed.add(chainId);
				if (![
					1,
					8453,
					42161,
					10,
					137
				].includes(chainId)) this.addIssue("warning", node.id, CONTRADICTION_CODES.UNSUPPORTED_CHAIN, `Chain ${chainId} is not in the standard supported set (Ethereum, Base, Arbitrum, Optimism, Polygon).`);
			}
			for (const [key, val] of Object.entries(node.params)) if (val === void 0 || val === null) this.addIssue("warning", node.id, "TOOL_NULL_PARAM", `Parameter "${key}" is null/undefined.`);
		}
		if (node.type === "sequence" || node.type === "parallel") for (const s of node.steps) this.validateTools(s);
		else if (node.type === "if") {
			this.validateTools(node.then);
			if (node.else) this.validateTools(node.else);
		} else if (node.type === "loop") this.validateTools(node.body);
	}
	validateSafety(root) {
		const actions = this.collectActions(root);
		const writeOps = actions.filter((a) => WRITE_TOOLS.has(a.tool));
		if (writeOps.length > 5) this.addIssue("warning", null, "SAFETY_MANY_WRITES", `Plan has ${writeOps.length} write operations. Consider whether this is intentional.`);
		for (const action of actions) if (action.tool === "defi_swap" || action.tool === "transfer") {
			const amount = this.resolveParamString(action.params.amount);
			if (amount) {
				const num = parseFloat(amount);
				if (!isNaN(num) && num > HIGH_VALUE_ETH_THRESHOLD) this.addIssue("warning", action.id, "SAFETY_HIGH_VALUE", `Step "${action.id}" involves ${amount} which may be high value. Verify amounts carefully.`);
			}
		}
		const gasEstimate = this.estimateGas(root);
		if (gasEstimate > MAX_TOTAL_GAS_ETH) this.addIssue("warning", null, "SAFETY_HIGH_GAS", `Estimated total gas: ${gasEstimate.toFixed(4)} ETH. This is above the ${MAX_TOTAL_GAS_ETH} ETH threshold.`);
		this.checkParallelWrites(root);
	}
	checkParallelWrites(node) {
		if (node.type === "parallel") {
			const writes = node.steps.filter((s) => s.type === "action" && WRITE_TOOLS.has(s.tool));
			if (writes.length > 1) this.addIssue("error", node.id, "SAFETY_PARALLEL_WRITES", `Parallel node "${node.id}" contains ${writes.length} write operations. Write operations must be sequential to prevent nonce conflicts and race conditions.`);
		}
		if (node.type === "sequence" || node.type === "parallel") for (const s of node.steps) this.checkParallelWrites(s);
		else if (node.type === "if") {
			this.checkParallelWrites(node.then);
			if (node.else) this.checkParallelWrites(node.else);
		} else if (node.type === "loop") this.checkParallelWrites(node.body);
	}
	validateDependencies(root) {
		const definedBefore = /* @__PURE__ */ new Set();
		this.walkDependencies(root, definedBefore);
	}
	walkDependencies(node, definedBefore) {
		if (node.type === "action") {
			for (const [key, val] of Object.entries(node.params)) if (typeof val === "object" && val !== null && "type" in val && val.type === "step_output") {
				const ref = val;
				if (!this.nodeIds.has(ref.stepId)) this.addIssue("error", node.id, CONTRADICTION_CODES.CIRCULAR_DEPENDENCY, `Step "${node.id}" references output of "${ref.stepId}" which does not exist.`);
				else if (!definedBefore.has(ref.stepId)) this.addIssue("error", node.id, CONTRADICTION_CODES.CIRCULAR_DEPENDENCY, `Step "${node.id}" references output of "${ref.stepId}" which hasn't executed yet at this point.`);
			}
			definedBefore.add(node.id);
		} else if (node.type === "sequence") for (const s of node.steps) this.walkDependencies(s, definedBefore);
		else if (node.type === "parallel") {
			const beforeParallel = new Set(definedBefore);
			for (const s of node.steps) this.walkDependencies(s, new Set(beforeParallel));
			for (const s of node.steps) this.collectNodeIds(s).forEach((id) => definedBefore.add(id));
		} else if (node.type === "if") {
			const beforeIf = new Set(definedBefore);
			this.walkDependencies(node.then, new Set(beforeIf));
			if (node.else) this.walkDependencies(node.else, new Set(beforeIf));
		} else if (node.type === "loop") {
			this.walkDependencies(node.body, new Set(definedBefore));
			this.collectNodeIds(node.body).forEach((id) => definedBefore.add(id));
		} else if (node.type === "wait") definedBefore.add(node.id);
	}
	collectNodeIds(node) {
		const ids = [node.id];
		if (node.type === "sequence" || node.type === "parallel") for (const s of node.steps) ids.push(...this.collectNodeIds(s));
		else if (node.type === "if") {
			ids.push(...this.collectNodeIds(node.then));
			if (node.else) ids.push(...this.collectNodeIds(node.else));
		} else if (node.type === "loop") ids.push(...this.collectNodeIds(node.body));
		return ids;
	}
	/** Rough gas estimate in ETH. Very approximate — real estimation requires RPC. */
	estimateGas(node) {
		const GAS_COSTS_ETH = {
			defi_swap: .002,
			transfer: 5e-4,
			clawnch_launch: .01,
			bridge: .003,
			permit2: 3e-4,
			liquidity: .005,
			bankr_launch: .01
		};
		if (node.type === "action") return GAS_COSTS_ETH[node.tool] ?? 0;
		else if (node.type === "sequence" || node.type === "parallel") return node.steps.reduce((sum, s) => sum + this.estimateGas(s), 0);
		else if (node.type === "if") {
			const thenGas = this.estimateGas(node.then);
			const elseGas = node.else ? this.estimateGas(node.else) : 0;
			return Math.max(thenGas, elseGas);
		} else if (node.type === "loop") return this.estimateGas(node.body) * Math.min(node.maxIterations, 10);
		return 0;
	}
	/** Rough duration estimate in ms. */
	estimateDuration(node) {
		const TOOL_DURATION_MS = {
			defi_swap: 15e3,
			transfer: 1e4,
			clawnch_launch: 3e4,
			bridge: 6e4,
			defi_price: 3e3,
			analytics: 5e3,
			market_intel: 5e3
		};
		if (node.type === "action") return TOOL_DURATION_MS[node.tool] ?? 5e3;
		else if (node.type === "sequence") return node.steps.reduce((sum, s) => sum + this.estimateDuration(s), 0);
		else if (node.type === "parallel") return Math.max(...node.steps.map((s) => this.estimateDuration(s)), 0);
		else if (node.type === "if") return Math.max(this.estimateDuration(node.then), node.else ? this.estimateDuration(node.else) : 0);
		else if (node.type === "wait") return node.durationMs ?? node.maxWaitMs ?? 6e4;
		else if (node.type === "loop") return this.estimateDuration(node.body) * Math.min(node.maxIterations, 10) + (node.delayMs ?? 0) * Math.min(node.maxIterations, 10);
		return 0;
	}
	collectActions(node) {
		if (node.type === "action") return [node];
		if (node.type === "sequence" || node.type === "parallel") return node.steps.flatMap((s) => this.collectActions(s));
		if (node.type === "if") {
			const actions = this.collectActions(node.then);
			if (node.else) actions.push(...this.collectActions(node.else));
			return actions;
		}
		if (node.type === "loop") return this.collectActions(node.body);
		return [];
	}
	resolveParamString(val) {
		if (typeof val === "string") return val;
		if (typeof val === "object" && val !== null && "type" in val) {
			const ref = val;
			if (ref.type === "literal") return String(ref.value);
		}
	}
	resolveParamNumber(val) {
		const s = this.resolveParamString(val);
		if (s === void 0) return void 0;
		const n = Number(s);
		return isNaN(n) ? void 0 : n;
	}
	normalizeToken(token) {
		return TOKEN_ALIASES[token] ?? TOKEN_ALIASES[token.toUpperCase()] ?? token.toUpperCase();
	}
	paramsEqual(a, b) {
		const aKeys = Object.keys(a).sort();
		const bKeys = Object.keys(b).sort();
		if (aKeys.length !== bKeys.length) return false;
		for (let i = 0; i < aKeys.length; i++) {
			if (aKeys[i] !== bKeys[i]) return false;
			if (this.resolveParamString(a[aKeys[i]]) !== this.resolveParamString(b[bKeys[i]])) return false;
		}
		return true;
	}
	addIssue(severity, nodeId, code, message) {
		this.issues.push({
			severity,
			nodeId,
			code,
			message
		});
	}
};
//#endregion
export { PlanValidator };

//# sourceMappingURL=plan-validator.mjs.map