// storage/skill-registry-storage.ts — SPEC-680: Read/write .planu/skill-registry.json
// Tracks multi-source skill provenance within a project.
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { withFileLock } from './file-mutex.js';
import { safeJsonParse } from './base-store.js';
function isValidRegistry(parsed) {
    return (parsed !== null && typeof parsed === 'object' && 'installed' in parsed && 'sources' in parsed);
}
// ---------------------------------------------------------------------------
// Configuration constants (no hardcodes in function bodies)
// ---------------------------------------------------------------------------
export const REGISTRY_RELATIVE_PATH = '.planu/skill-registry.json';
export const DEFAULT_TTL_DAYS = 30;
export const ALL_SKILL_SOURCES = ['superpowers', 'anthropic', 'local', 'community'];
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function emptyRegistry() {
    const epoch = new Date(0).toISOString();
    return {
        createdAt: epoch,
        updatedAt: epoch,
        sources: [],
        installed: {},
    };
}
function registryPath(projectPath) {
    return join(projectPath, REGISTRY_RELATIVE_PATH);
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Read the registry from .planu/skill-registry.json.
 * Returns empty registry (timestamps at epoch) if file is missing or corrupted.
 */
export async function readSkillRegistry(projectPath) {
    const filePath = registryPath(projectPath);
    let raw;
    try {
        raw = await readFile(filePath, 'utf-8');
    }
    catch {
        // File does not exist
        return emptyRegistry();
    }
    const parsed = safeJsonParse(raw, null);
    if (!isValidRegistry(parsed)) {
        console.warn(`[skill-registry] Corrupted registry at ${filePath} — falling back to empty`);
        return emptyRegistry();
    }
    return parsed;
}
/**
 * Overwrite the full registry file atomically.
 * Creates .planu/ directory if needed.
 */
export async function writeSkillRegistry(projectPath, registry) {
    const filePath = registryPath(projectPath);
    await mkdir(dirname(filePath), { recursive: true });
    await withFileLock(filePath, async () => {
        await writeFile(filePath, JSON.stringify(registry, null, 2), 'utf-8');
    });
}
/**
 * Upsert a single skill entry (serialized via file-mutex to prevent lost writes).
 *
 * Conflict resolution rule:
 *   - If the same skill already exists from a non-local source and the new source is 'local',
 *     overwrite and record `conflictResolution: 'local-wins'`.
 *   - Otherwise, always overwrite (last write wins for same or same-priority sources).
 */
export async function writeSkillRegistryEntry(projectPath, entry) {
    const filePath = registryPath(projectPath);
    await mkdir(dirname(filePath), { recursive: true });
    await withFileLock(filePath, async () => {
        // Read inside the lock so concurrent calls see the latest version
        let registry;
        try {
            const raw = await readFile(filePath, 'utf-8');
            const parsed = safeJsonParse(raw, null);
            if (isValidRegistry(parsed)) {
                registry = parsed;
            }
            else {
                throw new Error('invalid');
            }
        }
        catch {
            // Missing or corrupt — start fresh
            const now = new Date().toISOString();
            registry = {
                createdAt: now,
                updatedAt: now,
                sources: [...ALL_SKILL_SOURCES],
                installed: {},
            };
        }
        const existing = registry.installed[entry.skillName];
        const isLocalWins = entry.source === 'local' && existing !== undefined && existing.source !== 'local';
        const newEntry = {
            ...entry,
            ...(isLocalWins ? { conflictResolution: 'local-wins' } : {}),
        };
        registry.installed[entry.skillName] = newEntry;
        registry.updatedAt = new Date().toISOString();
        await writeFile(filePath, JSON.stringify(registry, null, 2), 'utf-8');
    });
}
/**
 * Return skill names whose adaptedAt is older than their ttlDays.
 */
export async function getStaleRegistryEntries(projectPath) {
    const registry = await readSkillRegistry(projectPath);
    const now = Date.now();
    const stale = [];
    for (const [skillName, entry] of Object.entries(registry.installed)) {
        const adaptedMs = new Date(entry.adaptedAt).getTime();
        const ttlMs = entry.ttlDays * 24 * 60 * 60 * 1000;
        if (now - adaptedMs > ttlMs) {
            stale.push(skillName);
        }
    }
    return stale;
}
//# sourceMappingURL=skill-registry-storage.js.map