import { classifyRuntimeFailureDiagnostic, sanitizeOpenCodeBinaryStatus, sanitizeRuntimeLifecycleDiagnostic, } from "../runtime/runtimeLifecycleDiagnostics.js";
import { BRIDGE_VERSION } from "../version.js";
import { authorizeWorkspace } from "./authGuards.js";
import { PUBLIC_IDENTIFIER_SCAN_OPTIONS, writeJson, } from "./publicResponse.js";
import { firstCapture, matchPath } from "./routeMatching.js";
export async function handleHealthRoute(context) {
    const { app, config, request, response, url } = context;
    if (request.method !== "GET" || url.pathname !== "/health")
        return false;
    const runtime = await app.runtime.status();
    writeJson(response, 200, {
        status: runtime.state === "running" || runtime.state === "idle"
            ? "ok"
            : "degraded",
        service: "patchtrail-agent-bridge",
        version: BRIDGE_VERSION,
        mode: config.mode,
        runtime: sanitizeGlobalRuntimeStatus(runtime, {
            includeProcess: false,
            includeDiagnostics: false,
        }),
    }, {
        surface: "health",
        scanOptions: { allowedPaths: ["$.runtime.provider"] },
    });
    return true;
}
export async function handleRuntimeRoutes(context) {
    const { app, auth, request, response, url } = context;
    if (request.method === "GET" && url.pathname === "/runtime/status") {
        if (auth.kind === "local-ui-session") {
            writeJson(response, 403, {
                error: "forbidden",
                message: "Use workspace-scoped runtime diagnostics.",
            });
            return true;
        }
        writeJson(response, 200, sanitizeGlobalRuntimeStatus(await app.runtime.status(), {
            includeProcess: true,
            includeDiagnostics: true,
        }), {
            surface: "runtime.status",
            scanOptions: { allowedPaths: ["$.provider"] },
        });
        return true;
    }
    if (request.method === "POST" && url.pathname === "/runtime/start") {
        await writeRuntimeOperationResponse(response, app, () => app.runtime.start());
        return true;
    }
    if (request.method === "POST" && url.pathname === "/runtime/stop") {
        await writeRuntimeOperationResponse(response, app, () => app.runtime.stop());
        return true;
    }
    return false;
}
export async function handleStateStatusRoute(context) {
    const { app, request, response, url } = context;
    if (request.method === "GET" && url.pathname === "/state/status") {
        writeJson(response, 200, formatLocalStoreStatus(app.localStore.status()));
        return true;
    }
    return false;
}
export async function handleWorkspaceRuntimeRoute(context) {
    const { app, auth, request, response, url } = context;
    const workspaceRuntimeStatusMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/runtime\/status$/);
    if (request.method === "GET" && workspaceRuntimeStatusMatch) {
        const workspaceId = firstCapture(workspaceRuntimeStatusMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        writeJson(response, 200, await app.runtimeDiagnostics.statusForWorkspace(workspaceId), {
            surface: "workspace.runtime.status",
            scanOptions: {
                ...PUBLIC_IDENTIFIER_SCAN_OPTIONS,
                allowedPaths: [
                    "$.provider",
                    "$.runtime.provider",
                    "$.credential",
                    "$.credential.credential",
                    "$.credential.maskedLabel",
                ],
                allowedFieldNames: [
                    ...PUBLIC_IDENTIFIER_SCAN_OPTIONS.allowedFieldNames,
                    "provider",
                ],
                allowedFindingLabels: ["masked credential label"],
            },
        });
        return true;
    }
    return false;
}
function formatLocalStoreStatus(status) {
    return {
        backend: status.backend,
        schemaVersion: status.schemaVersion,
        storage: {
            location: "local-only",
            rawStorage: "disabled",
        },
        counts: { ...status.counts },
    };
}
async function writeRuntimeOperationResponse(response, app, operation) {
    try {
        writeJson(response, 202, sanitizeGlobalRuntimeStatus(await operation(), {
            includeProcess: true,
            includeDiagnostics: true,
        }), {
            surface: "runtime.operation",
            scanOptions: { allowedPaths: ["$.provider"] },
        });
        return;
    }
    catch (error) {
        const runtime = runtimeWithOperationFailure(await app.runtime.status(), error);
        writeJson(response, runtimeOperationFailureStatusCode(runtime), sanitizeGlobalRuntimeStatus(runtime, {
            includeProcess: true,
            includeDiagnostics: true,
        }), {
            surface: "runtime.operation.failure",
            scanOptions: { allowedPaths: ["$.provider"] },
        });
    }
}
function runtimeWithOperationFailure(runtime, error) {
    if (runtime.state === "blocked_unverified_binary" ||
        runtime.lastError ||
        runtime.process?.lastError) {
        return runtime;
    }
    const diagnostic = classifyRuntimeFailureDiagnostic({
        reason: error instanceof Error ? error.message : undefined,
    });
    return {
        ...runtime,
        state: runtime.state === "running" || runtime.state === "starting"
            ? runtime.state
            : "failed",
        lastError: diagnostic.reason,
        ...(runtime.process
            ? {
                process: {
                    ...runtime.process,
                    lastError: diagnostic.reason,
                },
            }
            : {}),
    };
}
function runtimeOperationFailureStatusCode(runtime) {
    if (runtime.state === "blocked_unverified_binary")
        return 409;
    if (runtime.state === "running" || runtime.state === "starting")
        return 409;
    return 502;
}
export function sanitizeGlobalRuntimeStatus(runtime, options) {
    const binary = sanitizeOpenCodeBinaryStatus(runtime.binary);
    const runtimeDiagnostic = runtimeGlobalLifecycleDiagnostic(runtime);
    return {
        provider: runtime.provider,
        state: runtime.state,
        binary: {
            state: binary.state,
            ...(binary.opencodeVersion
                ? { opencodeVersion: binary.opencodeVersion }
                : {}),
            ...(binary.platform ? { platform: binary.platform } : {}),
            ...(options.includeDiagnostics && binary.reason
                ? {
                    reason: binary.reason,
                    reasonCode: binary.reasonCode,
                    phase: binary.phase,
                    timestamp: binary.timestamp,
                }
                : {}),
        },
        activeSessions: runtime.activeSessions,
        ...(options.includeDiagnostics && runtime.lastError && runtimeDiagnostic
            ? {
                lastError: runtimeDiagnostic.reason,
                reasonCode: runtimeDiagnostic.reasonCode,
                phase: runtimeDiagnostic.phase,
                timestamp: runtimeDiagnostic.timestamp,
            }
            : {}),
        ...(options.includeProcess && runtime.process
            ? {
                process: sanitizeGlobalRuntimeProcessStatus(runtime.process),
            }
            : {}),
    };
}
function runtimeGlobalLifecycleDiagnostic(runtime) {
    if (runtime.state === "blocked_unverified_binary") {
        return sanitizeRuntimeLifecycleDiagnostic(runtime.binary, {
            reasonCode: runtime.binary.state === "metadata_unavailable"
                ? "metadata_unavailable"
                : runtime.binary.state === "not_configured"
                    ? "not_configured"
                    : "verification_rejected",
        });
    }
    if (runtime.state === "failed" || runtime.lastError) {
        return classifyRuntimeFailureDiagnostic({
            reason: runtime.lastError ?? runtime.process?.lastError,
        });
    }
    return undefined;
}
function sanitizeGlobalRuntimeProcessStatus(process) {
    const processDiagnostic = process.lastError
        ? classifyRuntimeFailureDiagnostic({ reason: process.lastError })
        : undefined;
    return {
        state: process.state,
        host: process.host,
        ...(process.port !== undefined ? { port: process.port } : {}),
        ...(process.startedAt ? { startedAt: process.startedAt } : {}),
        ...(process.stoppedAt ? { stoppedAt: process.stoppedAt } : {}),
        ...(processDiagnostic
            ? {
                lastError: processDiagnostic.reason,
                reasonCode: processDiagnostic.reasonCode,
                phase: processDiagnostic.phase,
                timestamp: processDiagnostic.timestamp,
            }
            : {}),
        ...(process.opencodeVersion
            ? { opencodeVersion: process.opencodeVersion }
            : {}),
    };
}
