// engine/clarification-gate/token-store.ts — SPEC-584: Pending clarification token CRUD
// Persists tokens in data/projects/{hash}/pending-clarifications.json with 5-min TTL.
// Lazy expiry: expired tokens are pruned on every read, no background process.
import { createHash } from 'node:crypto';
import { readJson, writeJson, projectDataDir } from '../../storage/index.js';
import { join } from 'node:path';
export const CLARIFICATION_TOKEN_TTL_MS = 5 * 60 * 1000;
const FILENAME = 'pending-clarifications.json';
const EMPTY_FILE = { version: 1, tokens: [] };
function filePath(projectId) {
    return join(projectDataDir(projectId), FILENAME);
}
function isExpired(token) {
    return Date.now() - new Date(token.emittedAt).getTime() > CLARIFICATION_TOKEN_TTL_MS;
}
/**
 * Compute a deterministic hash over a questions array.
 * Used to verify that clarificationAnswers correspond to the pending batch.
 */
export function hashQuestions(questions) {
    return createHash('sha256').update(JSON.stringify(questions)).digest('hex');
}
async function load(projectId) {
    return readJson(filePath(projectId), { ...EMPTY_FILE, tokens: [] });
}
async function save(projectId, data) {
    await writeJson(filePath(projectId), data);
}
/**
 * Read all non-expired tokens for a project.
 * Expired tokens are pruned lazily and the file is rewritten if any were removed.
 */
export async function readTokens(projectId) {
    const file = await load(projectId);
    const live = file.tokens.filter((t) => !isExpired(t));
    if (live.length < file.tokens.length) {
        await save(projectId, { version: 1, tokens: live });
    }
    return live;
}
/**
 * Find the first non-expired token matching (toolName, specId?).
 * Returns null when no match or when the token is expired.
 */
export async function findToken(projectId, toolName, specId) {
    const tokens = await readTokens(projectId);
    return (tokens.find((t) => t.toolName === toolName && (specId === undefined ? true : t.specId === specId)) ?? null);
}
/**
 * Persist a new token. Replaces any existing token for the same (toolName, specId?).
 */
export async function upsertToken(token) {
    const file = await load(token.projectId);
    const others = file.tokens.filter((t) => !(t.toolName === token.toolName && t.specId === token.specId) && !isExpired(t));
    await save(token.projectId, { version: 1, tokens: [...others, token] });
}
/**
 * Remove the token matching (projectId, toolName, specId?).
 * No-op if the token does not exist.
 */
export async function clearToken(projectId, toolName, specId) {
    const file = await load(projectId);
    const filtered = file.tokens.filter((t) => !(t.toolName === toolName && t.specId === specId) && !isExpired(t));
    await save(projectId, { version: 1, tokens: filtered });
}
//# sourceMappingURL=token-store.js.map