/**
 * Loopback run-retry mutation route.
 *
 * POST /runs/:runId/retry
 *
 * Creates a new run as a retry of a terminated (cancelled, failed, or
 * completed) run. Mints a fresh local run-intent receipt from the original
 * run's source binding fields, creates the new run with attempt tracking
 * (attemptCount = previous.attemptCount + 1, previousRunId = previous.id),
 * and returns the bounded creation result.
 *
 * Authorized behind the least-privilege `runs:retry` scope (via the route
 * table) and workspace authorization. Bridge-token callers are rejected.
 *
 * Fail-closed: only terminal states are retryable; non-existent runs return
 * 404; runs that are still in-flight return 409 with a finite reason.
 */
import { authorizeWorkspace, requireLocalUiSessionForBrowserRoute, } from "./authGuards.js";
import { PUBLIC_IDENTIFIER_SCAN_OPTIONS, writeJson } from "./publicResponse.js";
import { readJsonBody, readOptionalString } from "./requestParsing.js";
import { bindRunIntentBridgeReceiptToRun, consumeRunIntentBridgeReceiptForCreate, formatRunIntentReceiptRejectionBody, releaseRunIntentBridgeReceiptForCreate, validateRunIntentBridgeReceiptForCreate, } from "../authority/runIntentReceipts.js";
import { formatCreateRunResult, readSessionCreateInput, } from "./runPayloads.js";
import { firstCapture, matchPath } from "./routeMatching.js";
import { isRecord } from "../utils/json.js";
const RUN_RETRY_ROUTE_PATTERN = /^\/runs\/([^/]+)\/retry$/;
const MAX_WORK_ITEM_ID_LENGTH = 200;
const MAX_ROOT_PATH_LENGTH = 4096;
/** Run states that are eligible for retry. */
const RETRYABLE_STATES = new Set(["cancelled", "failed", "completed"]);
export async function handleRunRetryRoute(context) {
    const { app, auth, request, response, runIntentReceiptState, url, localRunIntentSigner, } = context;
    if (request.method !== "POST")
        return false;
    const match = matchPath(url.pathname, RUN_RETRY_ROUTE_PATTERN);
    if (!match)
        return false;
    const runId = firstCapture(match);
    if (!requireLocalUiSessionForBrowserRoute(response, auth, {
        request,
        routeId: "runs.retry",
    })) {
        return true;
    }
    // Fail-closed: no signer means local retry is not available here.
    if (!localRunIntentSigner) {
        writeJson(response, 404, {
            error: "not_found",
            message: "Local run retry is not available.",
        });
        return true;
    }
    const run = app.localStore.findRun(runId);
    if (!run) {
        writeJson(response, 404, {
            error: "not_found",
            message: `Run ${runId} was not found.`,
        });
        return true;
    }
    if (!authorizeWorkspace(response, auth, run.workspaceId))
        return true;
    if (!RETRYABLE_STATES.has(run.status)) {
        writeJson(response, 409, {
            error: "run_not_retryable",
            message: `Run ${runId} is in state "${run.status}" and cannot be retried. Only cancelled, failed, or completed runs can be retried.`,
        });
        return true;
    }
    const body = await readJsonBody(request);
    if (!isRecord(body) || typeof body.rootPath !== "string") {
        writeJson(response, 400, {
            error: "invalid_request",
            message: "Expected JSON body with rootPath string.",
        });
        return true;
    }
    // Carry forward the original work item identity when the original run was
    // source-bound; otherwise the caller must supply workItemId explicitly.
    const workItemId = typeof body.workItemId === "string" && body.workItemId.trim().length > 0
        ? body.workItemId.trim()
        : run.sourceWorkItemId;
    if (!workItemId || workItemId.length > MAX_WORK_ITEM_ID_LENGTH) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: "workItemId must be a non-empty bounded string, either from the request body or the original run.",
        });
        return true;
    }
    const rootPath = body.rootPath.trim();
    if (rootPath.length === 0 || rootPath.length > MAX_ROOT_PATH_LENGTH) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: "rootPath must be a non-empty bounded string.",
        });
        return true;
    }
    const workspaceId = run.workspaceId;
    // Mint a new self-trusted receipt for the retry.
    const receipt = localRunIntentSigner.mintReceipt({ workspaceId, workItemId });
    const validation = validateRunIntentBridgeReceiptForCreate(receipt, workspaceId, runIntentReceiptState);
    if (validation) {
        writeJson(response, validation.statusCode, formatRunIntentReceiptRejectionBody(validation.reasonCode));
        return true;
    }
    const title = readOptionalString(body.title) ?? run.title;
    const session = readSessionCreateInput(body.session, body.title);
    const attemptCount = (run.attemptCount ?? 1) + 1;
    consumeRunIntentBridgeReceiptForCreate(receipt, runIntentReceiptState);
    let result;
    try {
        // Reconstruct source binding from the original run's fields when available.
        const sourceBinding = run.sourceWorkItemId &&
            run.sourceRepositoryId &&
            run.branchPlanId &&
            run.branchName &&
            run.baseBranch
            ? {
                workItemId: run.sourceWorkItemId,
                repositoryId: run.sourceRepositoryId,
                ...(run.sourcePreparationId
                    ? { preparationId: run.sourcePreparationId }
                    : {}),
                ...(run.sourceRunIntentId
                    ? { runIntentId: run.sourceRunIntentId }
                    : {}),
                branchPlanId: run.branchPlanId,
                branchName: run.branchName,
                baseBranch: run.baseBranch,
            }
            : undefined;
        result = await app.runService.createRun({
            workspaceId,
            rootPath,
            ...(title ? { title } : {}),
            ...(session ? { session } : {}),
            ...(sourceBinding ? { sourceBinding } : {}),
            attemptCount,
            previousRunId: run.id,
        });
    }
    catch (error) {
        releaseRunIntentBridgeReceiptForCreate(receipt, runIntentReceiptState);
        throw error;
    }
    bindRunIntentBridgeReceiptToRun(receipt, result.run.id, runIntentReceiptState);
    app.audit.append({
        type: "run.retried",
        severity: "info",
        message: "Run retried by local UI session.",
        metadata: {
            previousRunId: run.id,
            newRunId: result.run.id,
            workspaceId,
            attemptCount,
            source: "local_ui",
        },
    });
    writeJson(response, 201, {
        ...formatCreateRunResult(result),
        retry: {
            previousRunId: run.id,
            attemptCount,
        },
    }, {
        surface: "runs.retry",
        scanOptions: RUN_RETRY_SCAN_OPTIONS,
    });
    return true;
}
const RUN_RETRY_SCAN_OPTIONS = {
    allowedFieldNames: [
        ...PUBLIC_IDENTIFIER_SCAN_OPTIONS.allowedFieldNames,
        "status",
        "createdAt",
        "provider",
        "previousRunId",
        "attemptCount",
    ],
};
