// engine/living-specs/index.ts -- Main auto-reconcile entry point (SPEC-330)
import { readFile, access } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
import { scanFile, extractFilePaths } from './file-scanner.js';
import { extractCriteriaLines, matchCriteria } from './criteria-matcher.js';
import { replaceSectionInSpec } from '../spec-format/replace-section.js';
/**
 * Find the spec directory for a given specId within the project.
 * Looks for planu/specs/{specId}-SLUG pattern via glob.
 */
async function findSpecDir(specId, projectPath) {
    const suffix = specId + '-*/';
    const pattern = join(projectPath, 'planu', 'specs', suffix);
    const dirs = await glob(pattern, { absolute: true });
    return dirs[0] ?? null;
}
/**
 * Read a file safely, returning empty string on error.
 */
async function readSafe(filePath) {
    try {
        await access(filePath);
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return '';
    }
}
/**
 * Build the drift flags from missing files.
 */
function buildDriftFlags(fileResults) {
    return fileResults.filter((f) => !f.exists).map((f) => 'DRIFT: ' + f.path + ' missing');
}
function buildAutoReconcileProgressBody(result) {
    const coveragePct = result.criteriaTotal > 0 ? Math.round((result.criteriaMet / result.criteriaTotal) * 100) : 0;
    const driftSection = result.driftFlags.length > 0
        ? '\n### Drift Flags\n\n' + result.driftFlags.map((f) => '- ' + f).join('\n') + '\n'
        : '';
    return ('### Auto-Reconcile\n\n' +
        '> Last reconciled: ' +
        result.reconciledAt +
        '\n\n' +
        '- Files found: ' +
        String(result.filesFound) +
        '\n' +
        '- Files missing: ' +
        String(result.filesMissing) +
        '\n' +
        '- Criteria met: ' +
        String(result.criteriaMet) +
        '/' +
        String(result.criteriaTotal) +
        ' (' +
        String(coveragePct) +
        '%)' +
        driftSection +
        '\n');
}
/**
 * Auto-reconcile a spec against its actual implementation files.
 * Reads spec.md for file paths and criteria, scans files, matches criteria,
 * and writes the auto-reconcile summary into the inline ## Progress section.
 * Never throws -- returns partial result on error.
 */
export async function reconcileSpec(specId, projectPath) {
    const reconciledAt = new Date().toISOString();
    const empty = {
        specId,
        reconciledAt,
        filesFound: 0,
        filesMissing: 0,
        criteriaMet: 0,
        criteriaTotal: 0,
        driftFlags: [],
        fileResults: [],
        criteriaResults: [],
    };
    try {
        const specDir = await findSpecDir(specId, projectPath);
        if (specDir === null) {
            empty.driftFlags.push('DRIFT: spec directory for ' + specId + ' not found');
            return empty;
        }
        const specMdPath = join(specDir, 'spec.md');
        const specMdContent = await readSafe(specMdPath);
        const relativePaths = extractFilePaths(specMdContent);
        const absolutePaths = relativePaths.map((p) => join(projectPath, p));
        const fileResults = await Promise.all(absolutePaths.map((p) => scanFile(p)));
        const fileResultsWithRelative = fileResults.map((fr, i) => ({
            ...fr,
            path: relativePaths[i] ?? fr.path,
        }));
        const filesFound = fileResultsWithRelative.filter((f) => f.exists).length;
        const filesMissing = fileResultsWithRelative.filter((f) => !f.exists).length;
        const criteriaLines = extractCriteriaLines(specMdContent);
        const criteriaResults = matchCriteria(criteriaLines, fileResultsWithRelative);
        const criteriaMet = criteriaResults.filter((c) => c.met).length;
        const driftFlags = buildDriftFlags(fileResultsWithRelative);
        const result = {
            specId,
            reconciledAt,
            filesFound,
            filesMissing,
            criteriaMet,
            criteriaTotal: criteriaLines.length,
            driftFlags,
            fileResults: fileResultsWithRelative,
            criteriaResults,
        };
        await replaceSectionInSpec(specMdPath, 'Progress', buildAutoReconcileProgressBody(result));
        return result;
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return { ...empty, driftFlags: ['ERROR: ' + message] };
    }
}
//# sourceMappingURL=index.js.map