import { createHash } from 'node:crypto';
const SPARSE_THRESHOLD = 5;
const BORDERLINE_MARGIN = 2;
const CACHE_TTL_SECONDS = 7 * 24 * 60 * 60;
/**
 * Stage 4 of the write gate (Concern D). Only borderline atoms — those just
 * above the sparse-claim floor or triggered solely by a taskType — pay for an
 * LLM judgment. Verdicts are cached (keyed by claim+type) so repeated
 * extractions of the same lesson don't re-bill the model.
 */
export class LlmCritic {
    models;
    cache;
    ttlSeconds;
    constructor(models, cache, ttlSeconds = CACHE_TTL_SECONDS) {
        this.models = models;
        this.cache = cache;
        this.ttlSeconds = ttlSeconds;
    }
    isBorderline(input, triviality) {
        if (triviality.marginContentWords <= SPARSE_THRESHOLD + BORDERLINE_MARGIN)
            return true;
        const trigger = input.trigger;
        const onlyTaskTypes = (trigger.errors?.length ?? 0) === 0
            && (trigger.files?.length ?? 0) === 0
            && (trigger.symbols?.length ?? 0) === 0
            && (trigger.taskTypes?.length ?? 0) > 0;
        return onlyTaskTypes;
    }
    async judge(input) {
        if (!this.models.judgeAtomUtility)
            return undefined;
        const key = `atom_critic:${this.cacheKey(input)}`;
        let cached;
        try {
            cached = await this.cache.getJson(key);
        }
        catch (error) {
            console.error('[llm-critic] cache read failed; continuing uncached.', error);
        }
        if (cached)
            return cached;
        const verdict = await this.models.judgeAtomUtility(input);
        try {
            await this.cache.setJson(key, verdict, this.ttlSeconds);
        }
        catch (error) {
            console.error('[llm-critic] cache write failed; ignoring.', error);
        }
        return verdict;
    }
    cacheKey(input) {
        return createHash('sha256').update(`${input.type}\n${input.claim}`).digest('hex');
    }
}
