// engine/spec-coverage/core.ts — Orchestrator for spec coverage analysis.
// Coordinates test-finder, criteria-mapper, scoring, and persistence.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { readJson, writeJson, projectDataDir } from '../../storage/base-store.js';
import { findTestFiles, detectTestRunners, readTestFileContent } from './test-finder.js';
import { extractCriteria, mapTestsToCriterion } from './criteria-mapper.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const COVERAGE_FILE = 'spec-coverage.json';
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Runs the full spec coverage analysis for one spec.
 * Reads the HU.md, discovers test files, maps tests to criteria, calculates
 * score and grade, persists results, and returns the result object.
 */
export async function analyzeSpecCoverage(projectId, specId, huPath, projectPath) {
    // 1. Read HU.md content
    const huContent = await readHuFile(huPath);
    // 2. Extract acceptance criteria
    const rawCriteria = extractCriteria(huContent);
    if (rawCriteria.length === 0) {
        throw new Error(`No acceptance criteria found in HU.md for spec ${specId}. ` +
            `Expected format: "- [ ] **AC-01**: description"`);
    }
    // 3. Find test files
    const testFiles = await findTestFiles(projectPath);
    // 4. Load test file contents (needed for criteria mapping)
    const testContents = new Map();
    await Promise.all(testFiles.map(async (tf) => {
        const content = await readTestFileContent(projectPath, tf.file);
        testContents.set(tf.file, content);
    }));
    // 5. Map each criterion to tests
    const criteria = rawCriteria.map((rc) => mapTestsToCriterion(rc, testFiles, testContents, specId));
    // 6. Detect test runners
    const detectedRunners = await detectTestRunners(projectPath);
    // 7. Calculate score and grade
    const coveredCriteria = criteria.filter((c) => c.status === 'covered').length;
    const superficialCriteria = criteria.filter((c) => c.status === 'superficial').length;
    const uncoveredCriteria = criteria.filter((c) => c.status === 'uncovered').length;
    const totalCriteria = criteria.length;
    // Only high/medium confidence (covered) counts toward the score
    const score = totalCriteria > 0 ? Math.round((coveredCriteria / totalCriteria) * 100) : 0;
    const grade = calculateGrade(score);
    const result = {
        specId,
        analyzedAt: new Date().toISOString(),
        score,
        grade,
        totalCriteria,
        coveredCriteria,
        superficialCriteria,
        uncoveredCriteria,
        detectedRunners,
        criteria,
    };
    // 8. Persist (best-effort — never throw on persistence failure)
    await persistResult(projectId, specId, result);
    return result;
}
/**
 * Loads persisted coverage results from storage for a project.
 * Returns null if no data exists or the file is corrupted.
 */
export async function loadCoverageStore(projectId) {
    const filePath = join(projectDataDir(projectId), COVERAGE_FILE);
    const defaultStore = { version: 1, updatedAt: '', specs: {} };
    try {
        const store = await readJson(filePath, defaultStore);
        /* c8 ignore next 3 — defensive guard for corrupted files */
        if (typeof store.specs !== 'object') {
            return null;
        }
        return store;
    }
    catch {
        return null;
    }
}
// ---------------------------------------------------------------------------
// Scoring helpers
// ---------------------------------------------------------------------------
export function calculateGrade(score) {
    if (score >= 90) {
        return 'A';
    }
    if (score >= 75) {
        return 'B';
    }
    if (score >= 50) {
        return 'C';
    }
    return 'D';
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
async function readHuFile(huPath) {
    try {
        return await readFile(huPath, 'utf-8');
    }
    catch (err) {
        throw new Error(`Cannot read HU.md at "${huPath}": ${err instanceof Error ? err.message : String(err)}`, { cause: err });
    }
}
async function persistResult(projectId, specId, result) {
    const filePath = join(projectDataDir(projectId), COVERAGE_FILE);
    try {
        const existing = await readJson(filePath, {
            version: 1,
            updatedAt: '',
            specs: {},
        });
        const updated = {
            version: 1,
            updatedAt: new Date().toISOString(),
            specs: {
                ...existing.specs,
                [specId]: result,
            },
        };
        await writeJson(filePath, updated);
    }
    catch {
        // Persistence failure is non-fatal — analysis result is still returned
    }
}
//# sourceMappingURL=core.js.map