// engine/spec-migrator/drift-detector.ts — SPEC-715: Read-only drift detection
import { readdir, stat, access, readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { isOldFormat } from './lean-migration.js';
import { isNativeActive, fastScanSpecs } from '../core-bridge.js';
/** Files considered non-canonical in planu/ root (mirrors planu-root-cleaner.ts logic). */
const CANONICAL_ROOT_FILES = new Set([
    'status.json',
    'conventions.json',
    'index.html',
    'roadmap.html',
]);
const CANONICAL_ROOT_DIRS = new Set(['specs']);
const CANONICAL_SPEC_FILES = new Set(['spec.md']);
async function scanPlanuRoot(projectPath) {
    const rootFiles = [];
    const planuDir = join(projectPath, 'planu');
    try {
        const rootEntries = await readdir(planuDir);
        for (const entry of rootEntries) {
            const fullPath = join(planuDir, entry);
            try {
                const info = await stat(fullPath);
                if (info.isDirectory()) {
                    if (!CANONICAL_ROOT_DIRS.has(entry)) {
                        rootFiles.push(entry);
                    }
                }
                else if (!CANONICAL_ROOT_FILES.has(entry)) {
                    rootFiles.push(entry);
                }
            }
            catch {
                /* skip */
            }
        }
    }
    catch {
        /* skip */
    }
    return rootFiles;
}
async function scanSpecDirs(specsDir, specDirEntries, unifiedSpecs, leanSpecs) {
    const rootFiles = [];
    if (specDirEntries.length === 0) {
        return [];
    }
    for (const entry of specDirEntries) {
        const specPath = join(specsDir, entry);
        try {
            const info = await stat(specPath);
            if (!info.isDirectory()) {
                continue;
            }
            const idMatch = /^(SPEC-\d+)/i.exec(entry);
            const specId = idMatch?.[1]?.toUpperCase() ?? entry;
            const specFiles = await readdir(specPath);
            for (const file of specFiles) {
                if (!CANONICAL_SPEC_FILES.has(file)) {
                    rootFiles.push(`${entry}/${file}`);
                }
            }
            const techPath = join(specPath, 'technical.md');
            const specMdPath = join(specPath, 'spec.md');
            try {
                await access(techPath);
                const specContent = await readFile(specMdPath, 'utf-8');
                if (!/\n## Technical(\n|$)/.test(specContent)) {
                    unifiedSpecs.push(specId);
                }
            }
            catch {
                /* skip */
            }
            if (await isOldFormat(specPath)) {
                leanSpecs.push(specId);
            }
        }
        catch {
            /* skip unreadable */
        }
    }
    return rootFiles;
}
/**
 * Detect migration drift without mutating the filesystem.
 * Returns counts and IDs so the caller (list_specs) can report them.
 */
export async function detectDrift(projectPath) {
    const specsDir = join(projectPath, 'planu', 'specs');
    const unifiedSpecs = [];
    const leanSpecs = [];
    if (isNativeActive()) {
        const briefs = fastScanSpecs(projectPath);
        if (briefs) {
            for (const b of briefs) {
                if (b.needsUnified) {
                    unifiedSpecs.push(b.id);
                }
                if (b.needsLean) {
                    leanSpecs.push(b.id);
                }
            }
        }
    }
    const rootFiles = await scanPlanuRoot(projectPath);
    const specDirEntries = await readdir(specsDir).catch(() => []);
    if (isNativeActive()) {
        for (const entry of specDirEntries) {
            const specPath = join(specsDir, entry);
            try {
                const info = await stat(specPath);
                if (!info.isDirectory()) {
                    continue;
                }
                const specFiles = await readdir(specPath);
                for (const file of specFiles) {
                    if (!CANONICAL_SPEC_FILES.has(file)) {
                        rootFiles.push(`${entry}/${file}`);
                    }
                }
            }
            catch {
                /* ignore */
            }
        }
    }
    else {
        rootFiles.push(...(await scanSpecDirs(specsDir, specDirEntries, unifiedSpecs, leanSpecs)));
    }
    return {
        unifiedMigrations: unifiedSpecs.length,
        leanMigrations: leanSpecs.length,
        planuRootCleanup: rootFiles.length,
        details: { unifiedSpecs, leanSpecs, rootFiles },
    };
}
/** Format a DriftReport into a human-readable message for list_specs. */
export function formatDriftMessage(drift) {
    const parts = [];
    if (drift.leanMigrations > 0) {
        parts.push(`${String(drift.leanMigrations)} spec(s) need lean migration (${drift.details.leanSpecs.join(', ')})`);
    }
    if (drift.unifiedMigrations > 0) {
        parts.push(`${String(drift.unifiedMigrations)} spec(s) need technical.md unification (${drift.details.unifiedSpecs.join(', ')})`);
    }
    if (drift.planuRootCleanup > 0) {
        parts.push(`${String(drift.planuRootCleanup)} non-canonical file(s) in planu/ root/spec dirs`);
    }
    return `${parts.join('; ')}. Run heal_spec_docs to fix.`;
}
//# sourceMappingURL=drift-detector.js.map