// @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
// engine/autopilot/state-updater.ts — SPEC-459: Auto-update external project status on every status change
// Fire-and-forget: never blocks the event pipeline, never throws to callers.
// SPEC-753: File-lock applied to all writes; self-healing on corrupt reads.
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { withStatusLock } from '../../storage/status-store/file-lock.js';
import { validateStatusJson, rebuildStatusFromFrontmatters, writeRebuiltStatus, } from '../../storage/status-store/self-healing.js';
import { syncVersionField } from '../../storage/status-store/version-sync.js';
import { hashProjectPath, projectDataDir } from '../../storage/base-store.js';
const STATUS_FILENAME = 'status.json';
const MAX_RECENT_CHANGES = 20;
function getStatusPath(projectPath) {
    return join(projectDataDir(hashProjectPath(projectPath)), STATUS_FILENAME);
}
/**
 * SPEC-753: Load status.json with self-healing.
 * If corrupt: quarantine → rebuild from frontmatters → return fresh state.
 */
async function loadStatus(projectPath) {
    const filePath = getStatusPath(projectPath);
    // SPEC-753: Validate before parsing
    const validation = await validateStatusJson(filePath);
    if (!validation.ok && validation.error !== 'ENOENT') {
        // Corrupt external state is rebuilt in place; no project-tree quarantine.
        try {
            const rebuilt = await rebuildStatusFromFrontmatters({ projectPath });
            await writeRebuiltStatus(filePath, rebuilt);
            return rebuilt;
        }
        catch {
            // If even rebuild fails, return default
        }
    }
    try {
        const raw = await readFile(filePath, 'utf-8');
        const parsed = JSON.parse(raw);
        // SPEC-753: Version sync per session
        void syncVersionField(filePath); // fire-and-forget
        return parsed;
    }
    catch {
        return {
            updatedAt: new Date().toISOString(),
            totalSpecs: 0,
            byStatus: {},
            byType: {},
            recentChanges: [],
            version: '0.0.0',
        };
    }
}
async function saveStatus(projectPath, status) {
    const filePath = getStatusPath(projectPath);
    await mkdir(dirname(filePath), { recursive: true });
    await writeFile(filePath, JSON.stringify(status, null, 2), 'utf-8');
}
/**
 * Record a spec status change and recalculate counts.
 * Called fire-and-forget from the autopilot event bus.
 * SPEC-753: Wrapped in file-lock to prevent concurrent write corruption.
 */
export async function recordStatusChange(projectPath, _projectId, specId, fromStatus, toStatus) {
    const filePath = getStatusPath(projectPath);
    try {
        await withStatusLock(filePath, async () => {
            const status = await loadStatus(projectPath);
            // Update byStatus counts
            if (fromStatus && status.byStatus[fromStatus] !== undefined) {
                status.byStatus[fromStatus] = Math.max(0, (status.byStatus[fromStatus] ?? 0) - 1);
            }
            status.byStatus[toStatus] = (status.byStatus[toStatus] ?? 0) + 1;
            // Add to recent changes
            status.recentChanges.unshift({
                specId,
                from: fromStatus,
                to: toStatus,
                at: new Date().toISOString(),
            });
            if (status.recentChanges.length > MAX_RECENT_CHANGES) {
                status.recentChanges = status.recentChanges.slice(0, MAX_RECENT_CHANGES);
            }
            status.updatedAt = new Date().toISOString();
            await saveStatus(projectPath, status);
        });
    }
    catch {
        // Silent — state update must never fail the main flow
    }
}
/**
 * Recalculate full project status from spec store.
 * Used on init_project or when status.json is stale.
 * SPEC-753: Wrapped in file-lock.
 */
export async function refreshProjectStatus(projectPath, projectId) {
    const filePath = getStatusPath(projectPath);
    try {
        await withStatusLock(filePath, async () => {
            const { specStore } = await import('../../storage/index.js');
            const specs = await specStore.listSpecs(projectId);
            const { PLANU_VERSION } = await import('../../config/version.js');
            const byStatus = {};
            const byType = {};
            for (const spec of specs) {
                byStatus[spec.status] = (byStatus[spec.status] ?? 0) + 1;
                byType[spec.type] = (byType[spec.type] ?? 0) + 1;
            }
            const existing = await loadStatus(projectPath);
            const status = {
                updatedAt: new Date().toISOString(),
                totalSpecs: specs.length,
                byStatus,
                byType,
                recentChanges: existing.recentChanges,
                version: PLANU_VERSION,
            };
            await saveStatus(projectPath, status);
        });
    }
    catch {
        // Silent
    }
}
/**
 * Increment spec count (used on create_spec).
 * SPEC-753: Wrapped in file-lock.
 */
export async function incrementSpecCount(projectPath) {
    const filePath = getStatusPath(projectPath);
    try {
        await withStatusLock(filePath, async () => {
            const status = await loadStatus(projectPath);
            status.totalSpecs += 1;
            status.byStatus.draft = (status.byStatus.draft ?? 0) + 1;
            status.updatedAt = new Date().toISOString();
            await saveStatus(projectPath, status);
        });
    }
    catch {
        // Silent
    }
}
//# sourceMappingURL=state-updater.js.map