/**
 * Pipeline step: Deliver results to a callback URL.
 *
 * After the pipeline completes and the report is published, this step
 * derives the report's events, filters by the caller's `events` selection,
 * builds a signed webhook envelope, and POSTs it to the caller's callback
 * URL. Used for API-triggered evaluations where the caller wants push-based
 * result delivery.
 *
 * This step is always optional — callback failure never blocks the pipeline.
 * The result is already in the Content Lake (system of record).
 *
 * @see packages/eval/src/pipeline/callback-delivery.ts
 * @see docs/design-docs/api-service-gateway.md
 */
import { assoc, buildWebhookEnvelope, deriveEvents, projectReport, } from "../../_vendor/ailf-core/index.js";
import { deliverWebhook, } from "../../pipeline/callback-delivery.js";
export class CallbackStep {
    callback;
    jobId;
    name = "callback-delivery";
    optional = true;
    constructor(callback, jobId) {
        this.callback = callback;
        this.jobId = jobId;
    }
    check() {
        const issues = [];
        if (!this.callback.url) {
            issues.push({
                message: "Callback URL is required",
                severity: "error",
                source: "callback-delivery",
            });
        }
        return issues;
    }
    async execute(ctx, state) {
        const start = Date.now();
        const elapsed = () => Date.now() - start;
        if (!ctx.webhookSigningSecret) {
            ctx.logger.warn("callback-delivery: AILF_WEBHOOK_SIGNING_SECRET unset — skipping");
            return {
                durationMs: elapsed(),
                status: "success",
                summary: "Callback skipped (no signing secret)",
            };
        }
        if (!state.report) {
            return {
                durationMs: elapsed(),
                status: "success",
                summary: "Callback skipped (no published report)",
            };
        }
        const derived = deriveEvents(state.report);
        const filter = this.callback.events;
        // matched = the subset of derived events to include in the envelope (not a boolean overlap test).
        const matched = filter && filter.length > 0
            ? derived.filter((e) => filter.includes(e))
            : derived;
        if (matched.length === 0) {
            return {
                durationMs: elapsed(),
                status: "success",
                summary: "Callback skipped (no matching events)",
            };
        }
        const envelope = buildWebhookEnvelope({
            report: projectReport(state.report),
            events: matched,
            surface: "per-run",
            deliveredAt: new Date().toISOString(),
            summary: state.report.summary,
            ...(this.jobId ? { jobId: this.jobId } : {}),
        });
        await ctx.artifactWriter.emit("callbackRequest", assoc(ctx, { name: this.callback.url }), envelope);
        ctx.logger.info(`Delivering callback to ${this.callback.url} for [${matched.join(", ")}]`);
        const result = await deliverWebhook(this.callback, envelope, {
            signingSecret: ctx.webhookSigningSecret,
        });
        await ctx.artifactWriter.emit("callbackResponse", assoc(ctx, { name: this.callback.url }), { ok: result.ok, attempts: result.attempts, error: result.error });
        if (result.ok) {
            return {
                durationMs: elapsed(),
                status: "success",
                summary: `Callback delivered to ${this.callback.url} (${result.attempts} attempt${result.attempts === 1 ? "" : "s"})`,
            };
        }
        ctx.logger.warn(`Callback delivery failed after ${result.attempts} attempts: ${result.error}`);
        return {
            durationMs: elapsed(),
            status: "failed",
            error: `Callback delivery failed: ${result.error}`,
        };
    }
}
