// storage/global-projects-store.ts — SPEC-296: Persist cross-repo project registry.
// Persistence file: data/global/registered-projects.json
import { readJson, writeJson, globalDataDir, hashProjectPath } from './base-store.js';
import { withFileLock } from './file-mutex.js';
import { isEphemeralProject } from '../engine/data-projects-gc/pattern-matcher.js';
// ---------------------------------------------------------------------------
// File path
// ---------------------------------------------------------------------------
function registryFile() {
    return `${globalDataDir()}/registered-projects.json`;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Read the full registry from disk.
 * Returns an empty registry when the file does not exist.
 */
export async function getRegistry() {
    return readJson(registryFile(), {
        projects: [],
        updatedAt: new Date(0).toISOString(),
    });
}
/**
 * Persist the full registry to disk.
 */
export async function saveRegistry(registry) {
    await writeJson(registryFile(), registry);
}
/**
 * Add a project to the registry.
 * If the project already exists (same path), its hash is refreshed and the
 * entry is returned without duplicating it.
 * Returns the registered project entry.
 */
export async function addProject(projectPath) {
    const hash = hashProjectPath(projectPath);
    // Preventive filter (SPEC-581): ephemeral/test paths are kept in-memory only.
    if (isEphemeralProject(projectPath)) {
        return {
            path: projectPath,
            hash,
            registeredAt: new Date().toISOString(),
            specCount: 0,
        };
    }
    return withFileLock(registryFile(), async () => {
        const registry = await getRegistry();
        const existing = registry.projects.find((p) => p.path === projectPath);
        if (existing !== undefined) {
            existing.hash = hash;
            registry.updatedAt = new Date().toISOString();
            await saveRegistry(registry);
            return existing;
        }
        const entry = {
            path: projectPath,
            hash,
            registeredAt: new Date().toISOString(),
            specCount: 0,
        };
        registry.projects.push(entry);
        registry.updatedAt = new Date().toISOString();
        await saveRegistry(registry);
        return entry;
    });
}
/**
 * Remove a project from the registry by path.
 * Returns true if the project was found and removed, false otherwise.
 */
export async function removeProject(projectPath) {
    return withFileLock(registryFile(), async () => {
        const registry = await getRegistry();
        const idx = registry.projects.findIndex((p) => p.path === projectPath);
        if (idx === -1) {
            return false;
        }
        registry.projects.splice(idx, 1);
        registry.updatedAt = new Date().toISOString();
        await saveRegistry(registry);
        return true;
    });
}
/**
 * Return the list of all registered projects.
 *
 * Filters out ephemeral entries (test fixtures, /var/folders/, /tmp/) on every
 * read to handle entries written by older versions before SPEC-581's preventive
 * filter shipped. When any entry is filtered, a fire-and-forget prune writes
 * the cleaned registry back to disk so subsequent reads avoid the work.
 *
 * Use {@link getRegistry} when callers need the raw, unfiltered registry
 * (e.g. for diagnostics or repair tooling).
 */
export async function getProjects() {
    const registry = await getRegistry();
    const live = registry.projects.filter((p) => !isEphemeralProject(p.path));
    if (live.length !== registry.projects.length) {
        void pruneEphemeralEntries();
    }
    return live;
}
/**
 * Background cleanup: remove ephemeral entries from the persisted registry.
 * Idempotent and safe to call concurrently — protected by the registry file
 * lock. Intentionally does not throw — pruning is best-effort.
 */
async function pruneEphemeralEntries() {
    try {
        await withFileLock(registryFile(), async () => {
            const registry = await getRegistry();
            const live = registry.projects.filter((p) => !isEphemeralProject(p.path));
            if (live.length === registry.projects.length) {
                return;
            }
            const next = {
                projects: live,
                updatedAt: new Date().toISOString(),
            };
            await saveRegistry(next);
        });
    }
    catch {
        /* best-effort — pruning never blocks reads */
    }
}
//# sourceMappingURL=global-projects-store.js.map