// storage/drift-cache-store.ts — Persists background drift scan results (SPEC-225)
import { readJson, writeJson, projectDataDir } from './base-store.js';
import { withFileLock } from './file-mutex.js';
// ---------------------------------------------------------------------------
// File path
// ---------------------------------------------------------------------------
function driftCacheFile(projectId) {
    return `${projectDataDir(projectId)}/workers/drift-cache.json`;
}
// ---------------------------------------------------------------------------
// DriftCacheStore
// ---------------------------------------------------------------------------
/**
 * Persists auto-drift scan results to data/{projectId}/workers/drift-cache.json.
 *
 * Each entry tracks driftScore, driftedFiles, cachedAt, and a `shown` flag.
 * shown=false means the banner has not been presented to the user yet.
 */
export class DriftCacheStore {
    projectId;
    constructor(projectId) {
        this.projectId = projectId;
    }
    async load() {
        return readJson(driftCacheFile(this.projectId), { entries: {} });
    }
    async save(data) {
        await writeJson(driftCacheFile(this.projectId), data);
    }
    /**
     * Upsert a drift cache entry for a spec.
     * Always sets shown=false so the banner will be displayed on the next tool call.
     */
    async upsert(entry) {
        await withFileLock(driftCacheFile(this.projectId), async () => {
            const data = await this.load();
            const updated = { ...entry, shown: false };
            await this.save({
                entries: { ...data.entries, [entry.specId]: updated },
            });
        });
    }
    /**
     * Returns pending drift entries (shown=false) above the given score threshold.
     * Threshold is a value 0-1 (e.g. 0.3 = 30% drift).
     */
    async getPendingBanners(threshold) {
        const data = await this.load();
        return Object.values(data.entries).filter((e) => !e.shown && e.driftScore >= threshold);
    }
    /**
     * Mark all pending entries as shown so the banner is not repeated.
     */
    async markAllShown() {
        await withFileLock(driftCacheFile(this.projectId), async () => {
            const data = await this.load();
            const updated = {};
            for (const [key, entry] of Object.entries(data.entries)) {
                updated[key] = { ...entry, shown: true };
            }
            await this.save({ entries: updated });
        });
    }
    /**
     * Mark a single entry as shown.
     */
    async markShown(specId) {
        await withFileLock(driftCacheFile(this.projectId), async () => {
            const data = await this.load();
            const entry = data.entries[specId];
            if (!entry) {
                return;
            }
            await this.save({
                entries: { ...data.entries, [specId]: { ...entry, shown: true } },
            });
        });
    }
    /**
     * Returns the cached entry for a spec, or undefined if not present.
     */
    async get(specId) {
        const data = await this.load();
        return data.entries[specId];
    }
    /**
     * Removes stale entries older than maxAgeMs.
     */
    async evictStale(maxAgeMs) {
        await withFileLock(driftCacheFile(this.projectId), async () => {
            const data = await this.load();
            const now = Date.now();
            const fresh = {};
            for (const [key, entry] of Object.entries(data.entries)) {
                if (now - new Date(entry.cachedAt).getTime() < maxAgeMs) {
                    fresh[key] = entry;
                }
            }
            await this.save({ entries: fresh });
        });
    }
}
//# sourceMappingURL=drift-cache-store.js.map