import { randomBytes, randomInt } from "node:crypto";
const DEFAULT_PAIRING_TTL_MS = 5 * 60 * 1000;
const CLIENT_TOKEN_BYTES = 24;
const PERMISSION_RANK = {
    observe: 0,
    prompt: 1,
    control: 2,
    admin: 3,
};
export class AttachPairingRegistry {
    clients = new Map();
    auditEvents = [];
    now;
    pairingTtlMs;
    constructor(options = {}) {
        this.now = options.now ?? Date.now;
        this.pairingTtlMs = options.pairingTtlMs ?? DEFAULT_PAIRING_TTL_MS;
    }
    request(input = {}) {
        const now = input.now ?? this.now();
        const client = {
            id: randomBytes(12).toString("base64url"),
            code: randomInt(0, 1_000_000).toString().padStart(6, "0"),
            name: sanitizeName(input.name),
            userAgent: input.userAgent,
            remoteAddress: input.remoteAddress,
            permission: input.permission ?? "prompt",
            status: "pending",
            createdAt: now,
            expiresAt: now + this.pairingTtlMs,
        };
        this.clients.set(client.id, client);
        this.recordAudit({
            action: "pair_requested",
            clientId: client.id,
            clientName: client.name,
            permission: client.permission,
            result: "ok",
        });
        return client;
    }
    approve(code, now = this.now()) {
        const client = this.findPendingByCode(code, now);
        if (!client)
            return undefined;
        const approved = {
            ...client,
            status: "approved",
            clientToken: randomBytes(CLIENT_TOKEN_BYTES).toString("base64url"),
            approvedAt: now,
        };
        this.clients.set(client.id, approved);
        this.recordAudit({
            action: "pair_approved",
            clientId: approved.id,
            clientName: approved.name,
            permission: approved.permission,
            result: "ok",
        });
        return approved;
    }
    deny(code, now = this.now()) {
        const client = this.findPendingByCode(code, now);
        if (!client)
            return undefined;
        const denied = { ...client, status: "denied" };
        this.clients.set(client.id, denied);
        this.recordAudit({
            action: "pair_denied",
            clientId: denied.id,
            clientName: denied.name,
            permission: denied.permission,
            result: "ok",
        });
        return denied;
    }
    revoke(id) {
        const client = this.clients.get(id);
        if (!client)
            return false;
        this.clients.set(id, { ...client, status: "revoked" });
        this.recordAudit({
            action: "pair_revoked",
            clientId: client.id,
            clientName: client.name,
            permission: client.permission,
            result: "ok",
        });
        return true;
    }
    get(id, now = this.now()) {
        const client = this.clients.get(id);
        if (!client)
            return undefined;
        return this.refreshExpiry(client, now);
    }
    list(now = this.now()) {
        return [...this.clients.values()].map((client) => this.refreshExpiry(client, now));
    }
    checkToken(token, minimumPermission, commandType, now = this.now()) {
        if (!token) {
            this.recordAudit({ action: "command_blocked", permission: minimumPermission, commandType, result: "blocked" });
            return false;
        }
        for (const client of this.clients.values()) {
            const current = this.refreshExpiry(client, now);
            if (!isApprovedClient(current))
                continue;
            if (current.clientToken !== token)
                continue;
            const allowed = PERMISSION_RANK[current.permission] >= PERMISSION_RANK[minimumPermission];
            this.recordAudit({
                action: allowed ? "command_allowed" : "command_blocked",
                clientId: current.id,
                clientName: current.name,
                permission: current.permission,
                commandType,
                result: allowed ? "ok" : "blocked",
            });
            return allowed;
        }
        this.recordAudit({ action: "command_blocked", permission: minimumPermission, commandType, result: "blocked" });
        return false;
    }
    isTokenAllowed(token, minimumPermission, now = this.now()) {
        return this.checkToken(token, minimumPermission, "unknown", now);
    }
    getAuditEvents() {
        return [...this.auditEvents];
    }
    clear() {
        this.clients.clear();
    }
    recordAudit(event) {
        this.auditEvents.push({ ...event, timestamp: this.now() });
        if (this.auditEvents.length > 200) {
            this.auditEvents.splice(0, this.auditEvents.length - 200);
        }
    }
    findPendingByCode(code, now) {
        for (const client of this.clients.values()) {
            const current = this.refreshExpiry(client, now);
            if (current.status === "pending" && current.code === code)
                return current;
        }
        return undefined;
    }
    refreshExpiry(client, now) {
        if (client.status !== "pending" || now <= client.expiresAt)
            return client;
        const expired = { ...client, status: "expired" };
        this.clients.set(client.id, expired);
        return expired;
    }
}
function isApprovedClient(client) {
    return client.status === "approved";
}
function sanitizeName(name) {
    const trimmed = name?.trim();
    return trimmed && trimmed.length > 0 ? trimmed.slice(0, 80) : "Unnamed client";
}
//# sourceMappingURL=attach-auth.js.map