import { LOCAL_UI_SCOPE_DETAILS, isLocalUiScope, } from "../capabilities/LocalUiCapabilityService.js";
export const BRIDGE_AUTHORITY_CONTRACT_VERSION = 1;
export const BRIDGE_AUTHORITY_DIAGNOSTIC_ENVELOPE_VERSION = 1;
const LOOPBACK_HOSTS = ["127.0.0.1", "::1", "localhost"];
const LOCAL_UI_SESSION_BINDINGS = [
    "origin",
    "workspaceId",
    "deviceId",
    "userSessionId",
    "scopes",
];
export const BRIDGE_AUTHORITY_DIAGNOSTIC_GUARDS = [
    "auth",
    "origin",
    "workspace",
    "scope",
    "local-ui-session-required",
];
export const BRIDGE_AUTHORITY_DIAGNOSTIC_REASONS = [
    "missing_authentication",
    "invalid_authentication",
    "wrong_origin",
    "wrong_workspace",
    "insufficient_scope",
    "local_ui_session_required",
    "bridge_token_required",
    "bridge_token_not_allowed",
    "local_ui_session_invalid",
    "unknown",
];
const DIAGNOSTIC_MESSAGES = {
    missing_authentication: "Authentication is required for this bridge authority surface.",
    invalid_authentication: "The bridge authority credential was not accepted.",
    wrong_origin: "The local UI origin is not authorized for this session.",
    wrong_workspace: "The local UI session is not scoped to this workspace.",
    insufficient_scope: "The local UI session does not include the required scope.",
    local_ui_session_required: "This browser-facing bridge surface requires a local UI session.",
    bridge_token_required: "This bridge-owned surface requires the launcher bridge capability token.",
    bridge_token_not_allowed: "Bridge capability tokens are not accepted on this browser-facing surface.",
    local_ui_session_invalid: "The local UI session was not accepted.",
    unknown: "Bridge authority rejected the request.",
};
const DEFAULT_REASON_BY_GUARD = {
    auth: "missing_authentication",
    origin: "wrong_origin",
    workspace: "wrong_workspace",
    scope: "insufficient_scope",
    "local-ui-session-required": "local_ui_session_required",
    unknown: "unknown",
};
const SAFE_METHODS = new Set([
    "GET",
    "POST",
    "PUT",
    "PATCH",
    "DELETE",
    "OPTIONS",
    "HEAD",
]);
const SAFE_AUTH_SCHEMES = new Set([
    "bridge-token",
    "local-ui-session",
    "none",
]);
const SAFE_BINDING_RESULTS = new Set([
    "matched",
    "mismatched",
    "missing",
    "not_checked",
]);
/**
 * Builds the bridge authority contract from static and optional session-scoped
 * facts only. It intentionally avoids DB, filesystem, provider, runtime, and
 * subprocess inspection so callers can construct it on any request path.
 */
export function buildBridgeAuthorityContract(input = {}) {
    return {
        contractVersion: BRIDGE_AUTHORITY_CONTRACT_VERSION,
        diagnosticEnvelopeVersion: BRIDGE_AUTHORITY_DIAGNOSTIC_ENVELOPE_VERSION,
        service: "patchtrail-agent-bridge",
        binding: {
            invariant: "loopback_only",
            allowedHosts: LOOPBACK_HOSTS,
            configuredHostClass: classifyBinding(input.config),
            nonLoopbackOverride: "development_diagnostics_only",
        },
        health: {
            path: "/health",
            authentication: "none",
            invariant: "coarse_status_only",
            exposes: ["status", "service", "version", "mode", "runtime.state"],
            excludes: [
                "credentials",
                "credentialScope",
                "providerPayloads",
                "rawCommands",
                "stackTraces",
                "tokens",
            ],
        },
        localUi: {
            browserPathAuthority: {
                requiredCredential: "local_ui_session",
                bridgeCapabilityTokenAccepted: false,
            },
            handoff: {
                tokenPrefix: "pt_lui_",
                delivery: "url_fragment_only",
                fragmentParameter: "patchTrailLocalHandoff",
                exchange: "one_time",
                routeCredentialAccepted: false,
            },
            session: {
                tokenPrefix: "pt_lui_sess_",
                acceptedCredentials: [
                    "Authorization: Bearer <local-ui-session>",
                    "x-patchtrail-local-ui-session",
                ],
                requiredBindings: LOCAL_UI_SESSION_BINDINGS,
                scopeEvaluation: "per_route_required_scope",
                ...(input.session
                    ? {
                        currentSession: {
                            binding: "origin_workspace_device_user_session_scope",
                            grantedScopes: uniqueLocalUiScopes(input.session.scopes),
                        },
                    }
                    : {}),
            },
            supportedScopes: LOCAL_UI_SCOPE_DETAILS.map((scope) => ({ ...scope })),
        },
        runIntentReceipts: {
            contractVersion: "run-intent-bridge-receipt.v1",
            requiredTrustMetadata: ["keyId", "issuer", "audience"],
            signature: {
                algorithm: "ECDSA-P256-SHA256",
                encoding: "ieee-p1363-base64url",
                prefix: "ris_",
            },
            verification: {
                keySelection: "keyId",
                issuer: "configured_exact_match",
                audience: "configured_exact_match",
                trustedKeyCount: input.config?.runIntentReceiptTrust.trustedPublicKeys.length ?? 0,
                trustSource: classifyReceiptTrustSource(input.config),
            },
            rejects: [
                "missing_receipt",
                "unknown_key_id",
                "wrong_issuer",
                "wrong_audience",
                "invalid_signature",
                "expired_receipt",
                "replayed_receipt",
            ],
            excludes: [
                "privateKey",
                "publicKeyMaterial",
                "rawSignature",
                "rawEnvValues",
            ],
        },
        diagnostics: {
            envelopeVersion: BRIDGE_AUTHORITY_DIAGNOSTIC_ENVELOPE_VERSION,
            error: "bridge_authority_rejected",
            safeRequestFields: ["method", "routeId"],
            safeMetadataFields: [
                "authScheme",
                "requiredScope",
                "grantedScopes",
                "originBinding",
                "workspaceBinding",
                "sessionPresent",
                "bridgeTokenPresent",
                "healthPathAllowed",
            ],
            unsafeValues: [
                "tokens",
                "urlFragments",
                "rawCommands",
                "providerKeys",
                "providerPayloads",
                "paths",
                "stackTraces",
            ],
        },
    };
}
export function createBridgeAuthorityDiagnostic(input = {}) {
    const guard = sanitizeGuard(input.guard);
    const reason = sanitizeReason(input.reason) ?? DEFAULT_REASON_BY_GUARD[guard];
    return {
        envelopeVersion: BRIDGE_AUTHORITY_DIAGNOSTIC_ENVELOPE_VERSION,
        error: "bridge_authority_rejected",
        status: sanitizeStatus(input.status, guard),
        guard,
        reason,
        message: DIAGNOSTIC_MESSAGES[reason],
        request: sanitizeRequest(input.request),
        metadata: sanitizeMetadata(input.metadata),
    };
}
function classifyBinding(config) {
    if (!config)
        return "unknown";
    if (isLoopbackHost(config.host))
        return "loopback";
    if (config.allowNonLoopback)
        return "non_loopback_development_override";
    return "unknown";
}
function classifyReceiptTrustSource(config) {
    if (!config)
        return "unknown";
    const sources = new Set(config.runIntentReceiptTrust.trustedPublicKeys.map((key) => key.source));
    if (sources.size === 0)
        return "unknown";
    if (sources.size > 1)
        return "mixed";
    return sources.has("configured") ? "configured" : "development_fixture";
}
function isLoopbackHost(host) {
    return LOOPBACK_HOSTS.includes(host);
}
function uniqueLocalUiScopes(scopes) {
    const seen = new Set();
    const unique = [];
    for (const scope of scopes) {
        if (seen.has(scope))
            continue;
        seen.add(scope);
        unique.push(scope);
    }
    return unique;
}
function sanitizeGuard(value) {
    return isKnownGuard(value) ? value : "unknown";
}
function isKnownGuard(value) {
    return (typeof value === "string" &&
        BRIDGE_AUTHORITY_DIAGNOSTIC_GUARDS.includes(value));
}
function sanitizeReason(value) {
    if (typeof value !== "string")
        return undefined;
    return BRIDGE_AUTHORITY_DIAGNOSTIC_REASONS.includes(value)
        ? value
        : undefined;
}
function sanitizeStatus(value, guard) {
    if (value === 400 || value === 401 || value === 403)
        return value;
    if (guard === "auth" || guard === "local-ui-session-required")
        return 401;
    return 403;
}
function sanitizeRequest(value) {
    if (!isRecord(value))
        return {};
    const request = {};
    const method = sanitizeMethod(value.method);
    if (method)
        request.method = method;
    const routeId = sanitizeRouteId(value.routeId);
    if (routeId)
        request.routeId = routeId;
    return request;
}
function sanitizeMethod(value) {
    if (typeof value !== "string")
        return undefined;
    const method = value.toUpperCase();
    return SAFE_METHODS.has(method)
        ? method
        : undefined;
}
function sanitizeRouteId(value) {
    if (typeof value !== "string")
        return undefined;
    if (value.length < 1 || value.length > 80)
        return undefined;
    if (!/^[A-Za-z][A-Za-z0-9_.:-]*$/.test(value))
        return undefined;
    if (looksUnsafeString(value))
        return undefined;
    return value;
}
function sanitizeMetadata(value) {
    if (!isRecord(value))
        return {};
    const metadata = {};
    const authScheme = sanitizeAuthScheme(value.authScheme);
    if (authScheme)
        metadata.authScheme = authScheme;
    if (isLocalUiScope(value.requiredScope)) {
        metadata.requiredScope = value.requiredScope;
    }
    const grantedScopes = sanitizeGrantedScopes(value.grantedScopes);
    if (grantedScopes.length > 0)
        metadata.grantedScopes = grantedScopes;
    const originBinding = sanitizeBindingResult(value.originBinding);
    if (originBinding)
        metadata.originBinding = originBinding;
    const workspaceBinding = sanitizeBindingResult(value.workspaceBinding);
    if (workspaceBinding)
        metadata.workspaceBinding = workspaceBinding;
    if (typeof value.sessionPresent === "boolean") {
        metadata.sessionPresent = value.sessionPresent;
    }
    if (typeof value.bridgeTokenPresent === "boolean") {
        metadata.bridgeTokenPresent = value.bridgeTokenPresent;
    }
    if (typeof value.healthPathAllowed === "boolean") {
        metadata.healthPathAllowed = value.healthPathAllowed;
    }
    return metadata;
}
function sanitizeAuthScheme(value) {
    if (typeof value !== "string")
        return undefined;
    return SAFE_AUTH_SCHEMES.has(value)
        ? value
        : undefined;
}
function sanitizeBindingResult(value) {
    if (typeof value !== "string")
        return undefined;
    return SAFE_BINDING_RESULTS.has(value)
        ? value
        : undefined;
}
function sanitizeGrantedScopes(value) {
    if (!Array.isArray(value))
        return [];
    return uniqueLocalUiScopes(value.filter(isLocalUiScope));
}
function looksUnsafeString(value) {
    const lower = value.toLowerCase();
    return (lower.includes("token") ||
        lower.includes("secret") ||
        lower.includes("password") ||
        lower.includes("authorization") ||
        lower.includes("bearer") ||
        lower.includes("pt_lui") ||
        lower.includes("patchtraillocalhandoff") ||
        lower.includes("sk-") ||
        lower.includes("ghp_") ||
        lower.includes("github_pat_") ||
        lower.includes("glpat-") ||
        lower.includes("xoxb-") ||
        lower.includes("xoxp-") ||
        lower.includes("http:") ||
        lower.includes("https:") ||
        lower.includes("/") ||
        lower.includes("\\") ||
        lower.includes(" at "));
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
