// engine/spec-state-syncer.ts — SPEC-334: Pure logic for spec state synchronisation.
// Compares YAML frontmatter status values from spec.md files against provided data
// and produces a report of divergences. Storage I/O is handled by the tool layer.
import { readFile } from 'node:fs/promises';
import { glob } from 'glob';
import { parseFrontmatter } from './frontmatter-parser.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const VALID_STATUSES = new Set([
    'draft',
    'review',
    'approved',
    'implementing',
    'done',
    'discarded',
]);
const SPEC_ID_RE = /SPEC-\d+/;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function extractSpecId(filePath) {
    const match = SPEC_ID_RE.exec(filePath);
    return match?.[0] ?? null;
}
function isValidStatus(value) {
    return typeof value === 'string' && VALID_STATUSES.has(value);
}
// ---------------------------------------------------------------------------
// Internal — process a single spec.md file
// ---------------------------------------------------------------------------
async function processSingleSpec(filePath, dataStatusMap, report) {
    const specId = extractSpecId(filePath);
    if (specId === null) {
        return null;
    }
    let content;
    try {
        content = await readFile(filePath, 'utf-8');
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        report.errors.push(`Failed to read ${filePath}: ${msg}`);
        return null;
    }
    const { metadata } = parseFrontmatter(content);
    const yamlStatus = metadata.status;
    if (yamlStatus === undefined || yamlStatus === null) {
        return null;
    }
    if (!isValidStatus(yamlStatus)) {
        return null;
    }
    const dataStatus = dataStatusMap.get(specId);
    if (dataStatus === undefined) {
        return null;
    }
    if (yamlStatus === dataStatus) {
        return null;
    }
    report.divergences.push({ specId, yamlStatus, dataStatus });
    return { specId, newStatus: yamlStatus };
}
// ---------------------------------------------------------------------------
// Public API — pure engine functions (no storage I/O)
// ---------------------------------------------------------------------------
/**
 * Scan planu/specs for spec.md files, compare their YAML status against
 * the provided data entries, and return a list of specs that need updating
 * together with a partial report (divergences + errors already populated).
 *
 * The caller (tool layer) is responsible for persisting updates to storage.
 */
export async function detectSpecStateDivergences(projectPath, dataEntries) {
    const report = { synced: 0, divergences: [], errors: [], rejected: [] };
    const updates = [];
    const dataStatusMap = new Map(dataEntries.map((s) => [s.id, s.status]));
    let specFiles;
    try {
        specFiles = await glob('planu/specs/*/spec.md', {
            cwd: projectPath,
            absolute: true,
        });
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        report.errors.push(`Failed to glob spec files in ${projectPath}: ${msg}`);
        return { updates, report };
    }
    for (const filePath of specFiles) {
        const result = await processSingleSpec(filePath, dataStatusMap, report);
        if (result !== null) {
            updates.push(result);
        }
    }
    return { updates, report };
}
//# sourceMappingURL=spec-state-syncer.js.map