import { randomUUID } from "node:crypto";
import { redactSecretText } from "../utils/redactSecrets.js";
export class PermissionDecisionNotFoundError extends Error {
    decisionId;
    constructor(decisionId) {
        super(`Permission decision ${decisionId} was not found.`);
        this.decisionId = decisionId;
        this.name = "PermissionDecisionNotFoundError";
    }
}
export class PermissionDecisionConflictError extends Error {
    decisionId;
    constructor(decisionId) {
        super(`Permission decision ${decisionId} is already revoked.`);
        this.decisionId = decisionId;
        this.name = "PermissionDecisionConflictError";
    }
}
export class PermissionDecisionRegistry {
    localStore;
    policy;
    audit;
    constructor(localStore, policy, audit) {
        this.localStore = localStore;
        this.policy = policy;
        this.audit = audit;
    }
    hydrateActiveDecisions() {
        for (const record of this.localStore.listActivePermissionDecisionsForPolicy()) {
            this.policy.rememberDecision(recordToPolicyDecision(record));
        }
    }
    recordAllowForSession(input) {
        const decision = this.policy.recordDecision(input.toolCall, "allow_for_session", input.context, input.capabilities);
        try {
            return this.localStore.insertPermissionDecision({
                id: `pdec_${randomUUID()}`,
                workspaceId: input.context.workspaceId,
                runId: input.context.sessionId,
                runtimeSessionId: input.runtimeSessionId,
                scope: decision.scope,
                effect: decision.effect,
                option: decision.option,
                capabilities: decision.capabilities,
                pattern: decision.pattern,
                toolName: input.toolCall.name,
                toolTitle: input.toolCall.title,
                toolRisk: input.toolCall.risk,
                inputPreview: safeDecisionPreview(input.toolCall),
                affectedPaths: input.toolCall.affectedPaths ?? [],
                sourcePermissionRequestId: input.sourcePermissionRequestId,
                auditId: decision.auditId,
            });
        }
        catch (error) {
            this.policy.forgetDecision(decision.auditId);
            throw error;
        }
    }
    list(input) {
        return this.localStore.listPermissionDecisions(input);
    }
    revoke(input) {
        const existing = this.localStore.findPermissionDecision(input.decisionId);
        if (!existing || existing.workspaceId !== input.workspaceId) {
            throw new PermissionDecisionNotFoundError(input.decisionId);
        }
        if (existing.status === "revoked") {
            throw new PermissionDecisionConflictError(input.decisionId);
        }
        const revokeReason = input.reason
            ? redactSecretText(input.reason).slice(0, 500)
            : "user_revoked";
        const revokeSource = input.source ?? "unknown";
        const event = this.audit.append({
            type: "permission.decision.revoked",
            severity: "warning",
            message: "Permission decision revoked from the local registry.",
            metadata: {
                decisionId: existing.id,
                workspaceId: existing.workspaceId,
                option: existing.option,
                scope: existing.scope,
                reason: revokeReason,
                source: revokeSource,
            },
        });
        const revoked = this.localStore.revokePermissionDecision({
            id: existing.id,
            workspaceId: existing.workspaceId,
            revokeAuditId: event.id,
            revokeReason,
            revokeSource,
        });
        if (!revoked)
            throw new PermissionDecisionNotFoundError(input.decisionId);
        if (revoked.status !== "revoked") {
            throw new PermissionDecisionConflictError(input.decisionId);
        }
        this.policy.forgetDecision(existing.auditId);
        return revoked;
    }
}
function safeDecisionPreview(toolCall) {
    const name = toolCall.name.replace(/[^\w.:-]/g, " ").trim() || "tool";
    return `${name.slice(0, 80)} (${toolCall.risk})`;
}
function recordToPolicyDecision(record) {
    const sessionId = record.runId ?? record.runtimeSessionId;
    return {
        option: readPermissionOption(record.option),
        effect: record.effect,
        scope: readPermissionScope(record.scope),
        pattern: record.pattern,
        capabilities: readCapabilities(record.capabilitiesJson),
        workspaceId: record.workspaceId,
        ...(sessionId ? { sessionId } : {}),
        createdAt: record.createdAt,
        auditId: record.auditId,
    };
}
function readCapabilities(json) {
    try {
        const parsed = JSON.parse(json);
        if (!Array.isArray(parsed))
            return ["local_tools"];
        const capabilities = parsed.filter((item) => item === "local_tools" || item === "external_service_mutations");
        return capabilities.length > 0 ? capabilities : ["local_tools"];
    }
    catch {
        return ["local_tools"];
    }
}
function readPermissionOption(value) {
    if (value === "approve_once" ||
        value === "allow_for_session" ||
        value === "allow_for_project" ||
        value === "always_allow" ||
        value === "always_allow_tool_class" ||
        value === "full_escape_hatch_run" ||
        value === "full_escape_hatch_project" ||
        value === "full_escape_hatch_global" ||
        value === "deny" ||
        value === "deny_and_remember") {
        return value;
    }
    return "deny";
}
function readPermissionScope(value) {
    if (value === "once" ||
        value === "session" ||
        value === "project" ||
        value === "always" ||
        value === "tool_class" ||
        value === "escape_hatch_run" ||
        value === "escape_hatch_project" ||
        value === "escape_hatch_global") {
        return value;
    }
    return "once";
}
export function parseAffectedPaths(json) {
    try {
        const parsed = JSON.parse(json);
        if (!Array.isArray(parsed))
            return [];
        return parsed.filter((item) => typeof item === "string");
    }
    catch {
        return [];
    }
}
export function parseDecisionCapabilities(json) {
    return readCapabilities(json);
}
export function readToolRisk(value) {
    if (value === "read" ||
        value === "write" ||
        value === "shell" ||
        value === "network" ||
        value === "external") {
        return value;
    }
    return "read";
}
