export function buildRunContext(run) {
    const steps = {};
    // History is chronological — later entries for the same step win.
    for (const entry of run.history) {
        if (!entry.outputs && steps[entry.stepId])
            continue;
        steps[entry.stepId] = {
            outputs: entry.outputs ?? steps[entry.stepId]?.outputs ?? {},
            ...(entry.result && { verdict: entry.result }),
        };
    }
    return {
        inputs: run.inputs,
        steps,
        run: { id: run.runId, jobId: run.jobId },
    };
}
function getNestedValue(obj, path) {
    let current = obj;
    for (const part of path.split(".")) {
        if (current === null || current === undefined || typeof current !== "object")
            return undefined;
        current = current[part];
    }
    return current;
}
/** Resolve a context ref like "steps.plan.outputs.plan_md". */
export function resolveRef(ctx, ref) {
    return getNestedValue(ctx, ref.trim());
}
/**
 * Replace {{ref}} placeholders. Throws listing every unresolved ref so the
 * step (and run) fails with an actionable message.
 */
export function interpolate(template, ctx) {
    const unresolved = [];
    const result = template.replace(/\{\{([^}]+)\}\}/g, (_match, rawRef) => {
        const value = resolveRef(ctx, rawRef);
        if (value === undefined || value === null) {
            unresolved.push(rawRef.trim());
            return "";
        }
        return typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
    });
    if (unresolved.length > 0) {
        throw new Error(`Unresolved template reference(s): ${unresolved.map((r) => `{{${r}}}`).join(", ")}`);
    }
    return result;
}
function evaluateCondition(ctx, cond) {
    const value = resolveRef(ctx, cond.ref);
    switch (cond.op) {
        case "exists":
            return value !== undefined && value !== null;
        case "not_exists":
            return value === undefined || value === null;
        case "eq":
            return String(value) === cond.value;
        case "neq":
            return String(value) !== cond.value;
        case "contains":
            return typeof value === "string" && cond.value !== undefined && value.includes(cond.value);
        case "gt":
            return Number(value) > Number(cond.value);
        case "lt":
            return Number(value) < Number(cond.value);
        default:
            return false;
    }
}
/** AND of condition.all, AND'd with OR of condition.any (when present). */
export function evaluateGate(step, ctx) {
    const all = step.condition.all ?? [];
    const any = step.condition.any ?? [];
    if (all.some((cond) => !evaluateCondition(ctx, cond)))
        return false;
    if (any.length > 0 && !any.some((cond) => evaluateCondition(ctx, cond)))
        return false;
    return true;
}
