// engine/shared-agent-memory.ts — SPEC-689
import { readFile, writeFile, mkdir, unlink, rename } from 'node:fs/promises';
import { join } from 'node:path';
const MAX_STORE_BYTES = 512 * 1024; // 512 KB
const MAX_ENTRY_BYTES = 16 * 1024; // 16 KB
const ENTRY_TTL_MS = 24 * 60 * 60 * 1000; // 24 h
const EVICT_THRESHOLD = 0.8;
const LOCK_TIMEOUT_MS = 5000;
const LOCK_RETRY_INTERVAL_MS = 50;
function storeDir(projectPath, specId) {
    return join(projectPath, '.planu', 'team', specId);
}
function storePath(projectPath, specId) {
    return join(storeDir(projectPath, specId), 'shared-memory.json');
}
function lockPath(projectPath, specId) {
    return join(storeDir(projectPath, specId), 'shared-memory.json.lock');
}
async function acquireLock(lockFile) {
    const deadline = Date.now() + LOCK_TIMEOUT_MS;
    while (Date.now() < deadline) {
        try {
            await writeFile(lockFile, process.pid.toString(), { flag: 'wx' });
            return true;
        }
        catch {
            await new Promise((r) => setTimeout(r, LOCK_RETRY_INTERVAL_MS));
        }
    }
    return false;
}
async function releaseLock(lockFile) {
    try {
        await unlink(lockFile);
    }
    catch {
        // ignore — lock file may already be gone
    }
}
async function readStore(projectPath, specId) {
    try {
        const raw = await readFile(storePath(projectPath, specId), 'utf-8');
        return JSON.parse(raw);
    }
    catch {
        return {
            specId,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            sizeBytes: 0,
            entries: {},
        };
    }
}
async function persistStore(projectPath, specId, store) {
    await mkdir(storeDir(projectPath, specId), { recursive: true });
    const updated = { ...store, updatedAt: new Date().toISOString() };
    await writeFile(storePath(projectPath, specId), JSON.stringify(updated, null, 2), 'utf-8');
}
function calcSizeBytes(entries) {
    let total = 0;
    for (const entry of Object.values(entries)) {
        total += Buffer.byteLength(JSON.stringify(entry.value));
    }
    return total;
}
function pruneExpired(store) {
    const now = Date.now();
    const entries = Object.fromEntries(Object.entries(store.entries).filter(([, entry]) => now <= new Date(entry.expiresAt).getTime()));
    return { ...store, entries, sizeBytes: calcSizeBytes(entries) };
}
function evictLru(store) {
    const keys = Object.keys(store.entries);
    if (keys.length === 0) {
        return store;
    }
    const sorted = [...keys].sort((a, b) => {
        const aEntry = store.entries[a];
        const bEntry = store.entries[b];
        const aTime = aEntry !== undefined ? new Date(aEntry.accessedAt).getTime() : 0;
        const bTime = bEntry !== undefined ? new Date(bEntry.accessedAt).getTime() : 0;
        return aTime - bTime;
    });
    const evictCount = Math.max(1, Math.ceil(keys.length * 0.2));
    const toEvict = new Set(sorted.slice(0, evictCount));
    const entries = Object.fromEntries(Object.entries(store.entries).filter(([k]) => !toEvict.has(k)));
    return { ...store, entries, sizeBytes: calcSizeBytes(entries) };
}
export async function readSharedMemory(projectPath, specId, key) {
    let store = await readStore(projectPath, specId);
    store = pruneExpired(store);
    const entry = store.entries[key];
    if (entry === undefined) {
        return undefined;
    }
    const updated = {
        ...store,
        entries: {
            ...store.entries,
            [key]: { ...entry, accessedAt: new Date().toISOString() },
        },
    };
    await persistStore(projectPath, specId, updated);
    return entry.value;
}
export async function writeSharedMemory(projectPath, specId, key, value, agentId) {
    const newEntrySize = Buffer.byteLength(JSON.stringify(value));
    if (newEntrySize > MAX_ENTRY_BYTES) {
        return { ok: false, key, error: 'value too large' };
    }
    const lockFile = lockPath(projectPath, specId);
    await mkdir(storeDir(projectPath, specId), { recursive: true });
    const acquired = await acquireLock(lockFile);
    if (!acquired) {
        return { ok: false, key, error: 'lock timeout' };
    }
    try {
        let store = await readStore(projectPath, specId);
        store = pruneExpired(store);
        if (store.sizeBytes + newEntrySize > MAX_STORE_BYTES * EVICT_THRESHOLD) {
            store = evictLru(store);
        }
        if (store.sizeBytes + newEntrySize > MAX_STORE_BYTES) {
            return { ok: false, key, error: 'memory cap reached' };
        }
        const now = new Date().toISOString();
        const entry = {
            key,
            value,
            writtenBy: agentId,
            expiresAt: new Date(Date.now() + ENTRY_TTL_MS).toISOString(),
            accessedAt: now,
        };
        const finalStore = {
            ...store,
            entries: { ...store.entries, [key]: entry },
            sizeBytes: store.sizeBytes + newEntrySize,
        };
        await persistStore(projectPath, specId, finalStore);
        return { ok: true, key };
    }
    finally {
        await releaseLock(lockFile);
    }
}
export async function archiveSharedMemory(projectPath, specId) {
    const src = storePath(projectPath, specId);
    const archiveDir = join(storeDir(projectPath, specId), 'archive');
    await mkdir(archiveDir, { recursive: true });
    const dest = join(archiveDir, `shared-memory-${Date.now().toString()}.json`);
    await rename(src, dest);
}
//# sourceMappingURL=shared-agent-memory.js.map