import { buildBridgeAuthorityContract } from "../authority/bridgeAuthorityContract.js";
import { isLocalUiScope, LocalUiCapabilityError, } from "../capabilities/LocalUiCapabilityService.js";
import { isRecord } from "../utils/json.js";
import { authenticateLocalUiAuthorityRequest, authenticateLocalUiSession, requireBridgeToken, writeAuthFailure, writeAuthorityDiagnostic, } from "./authGuards.js";
import { AUTHORITY_CONTRACT_SCAN_OPTIONS, PUBLIC_IDENTIFIER_SCAN_OPTIONS, writeJson, } from "./publicResponse.js";
import { isLoopbackHost, readBearerToken, readHeaderString, readJsonBody, readOptionalString, readOrigin, readString, } from "./requestParsing.js";
export async function handleLocalUiPreAuthRoutes(context) {
    const { app, config, request, response, url } = context;
    if (request.method === "POST" && url.pathname === "/local-ui/session") {
        await handleLocalUiSessionExchange(context);
        return true;
    }
    if (request.method === "POST" && url.pathname === "/local-ui/session/renew") {
        handleLocalUiSessionRenew(context);
        return true;
    }
    if (request.method === "GET" && url.pathname === "/local-ui/authority") {
        const auth = authenticateLocalUiAuthorityRequest(request, app);
        if (!writeAuthFailure(response, auth, {
            request,
            routeId: "local-ui.authority",
        })) {
            return true;
        }
        if (auth.kind !== "local-ui-session") {
            writeAuthorityDiagnostic(response, {
                guard: "local-ui-session-required",
                reason: "local_ui_session_required",
                status: 403,
                metadata: {
                    authScheme: "bridge-token",
                    bridgeTokenPresent: auth.kind === "bridge",
                },
            }, { request, routeId: "local-ui.authority" });
            return true;
        }
        writeJson(response, 200, {
            authority: buildBridgeAuthorityContract({
                config,
                session: auth.session,
            }),
            session: formatLocalUiSessionMetadata(auth.session),
        }, {
            surface: "local-ui.authority",
            scanOptions: AUTHORITY_CONTRACT_SCAN_OPTIONS,
        });
        return true;
    }
    if (request.method === "POST" && url.pathname === "/local-ui/capabilities") {
        const auth = requireBridgeToken(request, app.bridgeToken);
        if (!writeAuthFailure(response, auth, {
            request,
            routeId: "local-ui.capabilities",
        })) {
            return true;
        }
        await handleLocalUiCapabilityMint(context);
        return true;
    }
    return false;
}
export async function handleLocalUiRoutes(context) {
    const { app, request, response, url } = context;
    if (request.method !== "GET" || url.pathname !== "/local-ui/session") {
        return false;
    }
    const session = authenticateLocalUiSession(request, app, "bridge:read");
    if (session.kind !== "local-ui-session") {
        if (session.kind === "error") {
            writeAuthFailure(response, session, {
                request,
                routeId: "local-ui.session",
            });
        }
        else {
            writeAuthorityDiagnostic(response, {
                guard: "local-ui-session-required",
                reason: "local_ui_session_required",
                status: 403,
            }, { request, routeId: "local-ui.session" });
        }
        return true;
    }
    writeJson(response, 200, { session: session.session }, {
        surface: "local-ui.session.metadata",
        scanOptions: PUBLIC_IDENTIFIER_SCAN_OPTIONS,
    });
    return true;
}
async function handleLocalUiCapabilityMint(context) {
    const { app, config, request, response } = context;
    if (!isLoopbackHost(config.host)) {
        writeJson(response, 403, {
            error: "non_loopback_binding",
            message: "Local UI handoff is only available on loopback bridge bindings.",
        });
        return;
    }
    const body = await readJsonBody(request);
    if (!isRecord(body)) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: "Expected JSON body.",
        });
        return;
    }
    const input = readMintCapabilityInput(body);
    try {
        writeJson(response, 201, app.localUiCapabilities.mint(input), {
            surface: "local-ui.capability-token-issued",
        });
    }
    catch (error) {
        if (error instanceof LocalUiCapabilityError) {
            writeJson(response, error.statusCode, {
                error: error.code,
                message: error.message,
            });
            return;
        }
        throw error;
    }
}
async function handleLocalUiSessionExchange(context) {
    const { app, config, request, response } = context;
    if (!isLoopbackHost(config.host)) {
        writeJson(response, 403, {
            error: "non_loopback_binding",
            message: "Local UI handoff is only available on loopback bridge bindings.",
        });
        return;
    }
    const origin = readOrigin(request);
    const body = await readJsonBody(request);
    if (!origin || !isRecord(body)) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: "Origin and JSON body are required.",
        });
        return;
    }
    const capabilityToken = readOptionalString(body.capabilityToken);
    const workspaceId = readOptionalString(body.workspaceId);
    const deviceId = readOptionalString(body.deviceId);
    const userSessionId = readOptionalString(body.userSessionId);
    if (!capabilityToken || !workspaceId || !deviceId || !userSessionId) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: "capabilityToken, workspaceId, deviceId, and userSessionId are required.",
        });
        return;
    }
    try {
        writeJson(response, 201, app.localUiCapabilities.exchange({
            capabilityToken,
            origin,
            workspaceId,
            deviceId,
            userSessionId,
        }), { surface: "local-ui.session-token-issued" });
    }
    catch (error) {
        if (error instanceof LocalUiCapabilityError) {
            writeJson(response, error.statusCode, {
                error: error.code,
                message: error.message,
            });
            return;
        }
        throw error;
    }
}
function handleLocalUiSessionRenew(context) {
    const { app, config, request, response } = context;
    if (!isLoopbackHost(config.host)) {
        writeJson(response, 403, {
            error: "non_loopback_binding",
            message: "Local UI handoff is only available on loopback bridge bindings.",
        });
        return;
    }
    const origin = readOrigin(request);
    const sessionToken = readBearerToken(request) ??
        readHeaderString(request, "x-patchtrail-local-ui-session");
    if (!origin || !sessionToken) {
        writeJson(response, 401, {
            error: "unauthorized",
            message: "Missing local UI origin or session token.",
        });
        return;
    }
    try {
        const session = app.localUiCapabilities.renewSession({
            sessionToken,
            origin,
        });
        writeJson(response, 200, { session: formatLocalUiSessionMetadata(session) }, {
            surface: "local-ui.session.metadata",
            scanOptions: PUBLIC_IDENTIFIER_SCAN_OPTIONS,
        });
    }
    catch (error) {
        if (error instanceof LocalUiCapabilityError) {
            writeJson(response, error.statusCode, {
                error: error.code,
                message: error.message,
            });
            return;
        }
        throw error;
    }
}
function formatLocalUiSessionMetadata(session) {
    return {
        id: session.id,
        origin: session.origin,
        workspaceId: session.workspaceId,
        deviceId: session.deviceId,
        userSessionId: session.userSessionId,
        scopes: [...session.scopes],
        expiresAt: session.expiresAt,
    };
}
function readMintCapabilityInput(body) {
    const scopes = Array.isArray(body.scopes)
        ? body.scopes.filter(isLocalUiScope)
        : [];
    return {
        origin: readString(body.origin, ""),
        workspaceId: readString(body.workspaceId, ""),
        deviceId: readString(body.deviceId, ""),
        userSessionId: readString(body.userSessionId, ""),
        scopes,
        ...(typeof body.ttlSeconds === "number"
            ? { ttlSeconds: body.ttlSeconds }
            : {}),
        ...(typeof body.sessionTtlSeconds === "number"
            ? { sessionTtlSeconds: body.sessionTtlSeconds }
            : {}),
    };
}
