// Planu — Validator: Definition of Ready and Definition of Done generators
import { readFile, readdir } from 'node:fs/promises';
import { join, dirname, basename } from 'node:path';
import { parseProgressActuals } from '../actuals/progress-parser.js';
function buildDoRSpecItems(spec) {
    // Adapt description to actual file name (modern: spec.md, legacy: HU.md)
    const specFileName = spec.specPath ? basename(spec.specPath) : 'spec.md';
    return [
        {
            id: 'dor-1',
            description: 'Spec has a clear title and description',
            category: 'spec',
            required: true,
            autoCheck: true,
            status: spec.title.length > 3 ? 'passed' : 'failed',
        },
        {
            id: 'dor-2',
            description: 'Spec type and scope are defined',
            category: 'spec',
            required: true,
            autoCheck: true,
            status: spec.type.length > 0 && spec.scope.length > 0 ? 'passed' : 'failed',
        },
        {
            id: 'dor-3',
            description: 'Difficulty is assessed (1-5)',
            category: 'spec',
            required: true,
            autoCheck: true,
            status: spec.difficulty >= 1 && spec.difficulty <= 5 ? 'passed' : 'failed',
        },
        {
            id: 'dor-4',
            description: 'Estimation is complete',
            category: 'spec',
            required: true,
            autoCheck: true,
            status: spec.estimation.devHours > 0 ? 'passed' : 'failed',
        },
        {
            id: 'dor-5',
            description: `${specFileName} file exists`,
            category: 'spec',
            required: true,
            autoCheck: true,
            status: spec.specPath ? 'passed' : 'failed',
        },
    ];
}
function buildDoRTechnicalItems(spec) {
    const techFileName = spec.technicalPath ? basename(spec.technicalPath) : 'technical.md';
    return [
        {
            id: 'dor-6',
            description: `${techFileName} file exists`,
            category: 'design',
            required: true,
            autoCheck: true,
            status: spec.technicalPath ? 'passed' : 'failed',
        },
        {
            id: 'dor-7',
            description: 'Dependencies are identified',
            category: 'dependencies',
            required: false,
            autoCheck: true,
            status: 'passed', // Not having deps is valid
        },
        {
            id: 'dor-8',
            description: 'No blocking dependencies',
            category: 'dependencies',
            required: true,
            autoCheck: true,
            status: spec.blockedBy.length === 0 ? 'passed' : 'failed',
        },
        {
            id: 'dor-9',
            description: 'Risk level is assessed',
            category: 'technical',
            required: true,
            autoCheck: true,
            status: spec.risk.length > 0 ? 'passed' : 'failed',
        },
        {
            id: 'dor-10',
            description: 'Target is defined (frontend/backend/fullstack)',
            category: 'technical',
            required: true,
            autoCheck: true,
            status: spec.target.length > 0 ? 'passed' : 'failed',
        },
    ];
}
function buildDoRItems(spec) {
    return [...buildDoRSpecItems(spec), ...buildDoRTechnicalItems(spec)];
}
/**
 * Generate a Definition of Ready checklist for a spec.
 */
export function generateDoR(spec) {
    const items = buildDoRItems(spec);
    const requiredItems = items.filter((i) => i.required);
    const passedCount = items.filter((i) => i.status === 'passed').length;
    const requiredCount = requiredItems.length;
    const isReady = requiredItems.every((i) => i.status === 'passed');
    return {
        specId: spec.id,
        items,
        passedCount,
        requiredCount,
        isReady,
        generatedAt: new Date().toISOString(),
    };
}
function buildDoDItems(spec, score, hasBlockingQualityIssues, hasTestFiles) {
    // If spec is already marked "done", manual gates are implicitly satisfied —
    // the user explicitly transitioned the spec, which implies review/tests/docs passed.
    const isDone = spec.status === 'done';
    // If spec is "implementing" and test files exist, auto-pass test gates
    const isImplementing = spec.status === 'implementing';
    return [
        {
            id: 'dod-1',
            description: 'All acceptance criteria implemented',
            category: 'code',
            required: true,
            autoCheck: true,
            status: isDone || score === 100 ? 'passed' : score > 0 ? 'failed' : 'pending',
        },
        {
            id: 'dod-2',
            description: 'No critical quality issues (errors only — warnings are non-blocking)',
            category: 'quality',
            required: true,
            autoCheck: true,
            status: !hasBlockingQualityIssues ? 'passed' : 'failed',
        },
        {
            id: 'dod-3',
            description: 'Unit tests written for new code',
            category: 'tests',
            required: true,
            autoCheck: true,
            status: isDone || hasTestFiles ? 'passed' : isImplementing ? 'failed' : 'pending',
        },
        {
            id: 'dod-4',
            description: 'Code reviewed and approved',
            category: 'review',
            required: true,
            autoCheck: false,
            status: isDone ? 'passed' : 'pending',
        },
        {
            id: 'dod-5',
            description: 'Documentation updated',
            category: 'docs',
            required: false,
            autoCheck: false,
            status: isDone ? 'passed' : 'pending',
        },
        {
            id: 'dod-6',
            description: 'No regressions in existing tests',
            category: 'tests',
            required: true,
            autoCheck: true,
            status: isDone || hasTestFiles ? 'passed' : isImplementing ? 'failed' : 'pending',
        },
        {
            id: 'dod-7',
            description: 'Spec status updated to done',
            category: 'code',
            required: true,
            autoCheck: true,
            status: spec.status === 'done' ? 'passed' : 'pending',
        },
        {
            id: 'dod-8',
            description: 'Actuals recorded (hours, tokens, cost)',
            category: 'review',
            required: false,
            autoCheck: true,
            status: spec.actuals ? 'passed' : 'pending',
        },
    ];
}
/**
 * Check if actuals exist either in spec object or in progress.md file.
 */
async function hasActuals(spec) {
    if (spec.actuals) {
        return true;
    }
    // Try reading progress.md from the spec directory
    try {
        const specDir = dirname(spec.specPath);
        const progressPath = join(specDir, 'progress.md');
        const content = await readFile(progressPath, 'utf-8');
        const parsed = parseProgressActuals(content);
        return parsed !== null;
    }
    catch /* v8 ignore start */ {
        return false;
    } /* v8 ignore stop */
}
/**
 * Detect if test files exist for the spec's affected code files.
 */
async function detectTestFiles(spec, codeState) {
    // Collect affected files from codeState or impactAnalysis
    const affectedFiles = [];
    if (codeState?.affectedFiles && codeState.affectedFiles.length > 0) {
        affectedFiles.push(...codeState.affectedFiles);
    }
    else if (spec.impactAnalysis?.affectedFiles) {
        affectedFiles.push(...spec.impactAnalysis.affectedFiles);
    }
    /* v8 ignore start -- filesystem heuristic, tested via integration */
    if (affectedFiles.length === 0) {
        return false;
    }
    // Check if any affected file IS a test file
    const testPatterns = ['.test.', '.spec.', '_test.', '_spec.', '/tests/', '/__tests__/'];
    if (affectedFiles.some((f) => testPatterns.some((p) => f.includes(p)))) {
        return true;
    }
    // Check if test counterparts exist for source files
    const specDir = dirname(spec.specPath);
    try {
        const projectDir = join(specDir, '..', '..', '..');
        const testsDir = join(projectDir, 'tests');
        const testEntries = await readdir(testsDir, { recursive: true });
        for (const file of affectedFiles) {
            const base = basename(file).replace(/\.\w+$/, '');
            if (testEntries.some((tf) => tf.includes(base) && testPatterns.some((p) => tf.includes(p)))) {
                return true;
            }
        }
    }
    catch {
        // No tests directory — can't auto-detect
    }
    return false;
    /* v8 ignore stop */
}
/**
 * Generate a Definition of Done checklist for a spec.
 */
export async function generateDoD(spec, validationResult, codeState) {
    const score = validationResult?.score ?? 0;
    // Only errors and critical issues block DoD — warnings and info are non-blocking
    const blockingSeverities = ['error', 'critical'];
    const hasBlockingQualityIssues = validationResult?.qualityIssues.some((i) => blockingSeverities.includes(i.severity)) ?? false;
    const hasTestFiles = await detectTestFiles(spec, codeState);
    const items = buildDoDItems(spec, score, hasBlockingQualityIssues, hasTestFiles);
    // Override actuals check with progress.md parsing
    const actualsItem = items.find((i) => i.id === 'dod-8');
    if (actualsItem?.status === 'pending') {
        const found = await hasActuals(spec);
        if (found) {
            actualsItem.status = 'passed';
        }
    }
    const requiredItems = items.filter((i) => i.required);
    const passedCount = items.filter((i) => i.status === 'passed').length;
    const requiredCount = requiredItems.length;
    const isDone = requiredItems.every((i) => i.status === 'passed');
    return {
        specId: spec.id,
        items,
        passedCount,
        requiredCount,
        isDone,
        completedAt: isDone ? new Date().toISOString() : null,
    };
}
//# sourceMappingURL=dor-dod.js.map