import { createHash } from 'node:crypto';
import { stat } from 'node:fs/promises';
import { isAbsolute, join, normalize } from 'node:path';
export function criterionKey(criterion) {
    const normalized = normalizeCriterion(criterion);
    return createHash('sha256').update(normalized).digest('hex').slice(0, 16);
}
export async function buildSpecEvidenceIndex(args) {
    const diagnostics = [...args.artifacts.invalidArtifacts];
    const matrixRows = args.artifacts.traceabilityMatrix?.rows ?? [];
    const contractValidations = args.artifacts.contractValidations;
    const entries = await Promise.all(args.criteria.map(async (criterion) => {
        const rows = matrixRows.filter((row) => normalizeCriterion(row.acceptanceCriterion) === normalizeCriterion(criterion));
        const evidence = await collectEvidence({
            rows,
            contractValidations,
            projectPath: args.projectPath,
            diagnostics,
        });
        return {
            criterion,
            criterionKey: criterionKey(criterion),
            evidence,
        };
    }));
    return {
        specId: args.specId,
        generatedAt: args.now ?? new Date().toISOString(),
        criteria: entries,
        diagnostics,
    };
}
async function collectEvidence(args) {
    const records = [];
    for (const row of args.rows) {
        if (row.scenario) {
            records.push(record('scenario', row.scenario, 'valid', 'traceability-matrix'));
        }
        for (const test of row.testEvidence ?? []) {
            records.push(await pathRecord('test', test, 'traceability-matrix', args));
        }
        for (const contract of row.contractEvidence ?? []) {
            records.push(await pathRecord('contract', contract, 'traceability-matrix', args));
        }
        if (row.manualEvidence) {
            records.push(record('manual', row.manualEvidence, 'valid', 'traceability-matrix'));
        }
        for (const changedFile of row.changedFiles) {
            records.push(await pathRecord('changed-file', changedFile, 'traceability-matrix', args));
        }
        if (row.validationEvidence) {
            records.push(validationRecord(row.validationEvidence));
        }
        if (row.reviewerEvidence) {
            records.push(record('reviewer', row.reviewerEvidence, 'valid', 'traceability-matrix'));
        }
    }
    for (const validation of args.contractValidations) {
        if (validation.reportPath) {
            records.push(await pathRecord('contract', validation.reportPath, `contract-validation:${validation.kind}`, args));
        }
        if (validation.summary) {
            records.push({
                ...record('contract', validation.summary, validation.passed ? 'valid' : 'invalid', `contract-validation:${validation.kind}`),
                passed: validation.passed,
            });
        }
    }
    return records;
}
function validationRecord(value) {
    const parsed = parseValidationEvidence(value);
    return {
        kind: 'validation',
        value,
        status: parsed.passed === false ? 'invalid' : 'valid',
        source: 'traceability-matrix',
        ...parsed,
    };
}
function parseValidationEvidence(value) {
    try {
        const parsed = JSON.parse(value);
        if (typeof parsed !== 'object' || parsed === null) {
            return { command: value };
        }
        const parsedRecord = parsed;
        return {
            command: typeof parsedRecord.command === 'string' ? parsedRecord.command : value,
            passed: typeof parsedRecord.passed === 'boolean' ? parsedRecord.passed : undefined,
            timestamp: typeof parsedRecord.timestamp === 'string' ? parsedRecord.timestamp : undefined,
        };
    }
    catch {
        return { command: value };
    }
}
async function pathRecord(kind, value, source, args) {
    const safe = resolveSafeProjectPath(value, args.projectPath);
    if (!safe.ok) {
        args.diagnostics.push(`${value}: ${safe.reason}`);
        return record(kind, value, 'invalid', source, safe.reason);
    }
    if (!args.projectPath) {
        return record(kind, value, 'missing', source, 'projectPath unavailable');
    }
    const exists = await stat(safe.path)
        .then(() => true)
        .catch(() => false);
    return record(kind, value, exists ? 'valid' : 'stale', source, exists ? undefined : 'file missing');
}
function resolveSafeProjectPath(value, projectPath) {
    if (value.trim().length === 0) {
        return { ok: false, reason: 'empty evidence path' };
    }
    if (isAbsolute(value)) {
        return { ok: false, reason: 'absolute evidence paths are not allowed' };
    }
    const normalized = normalize(value);
    if (normalized.startsWith('..') || normalized.includes('/../')) {
        return { ok: false, reason: 'path traversal is not allowed' };
    }
    if (!projectPath) {
        return { ok: true, path: normalized };
    }
    return { ok: true, path: join(projectPath, normalized) };
}
function record(kind, value, status, source, reason) {
    return { kind, value, status, source, ...(reason ? { reason } : {}) };
}
function normalizeCriterion(value) {
    return value
        .replace(/^-\s*\[[ x]\]\s*/i, '')
        .replace(/\s+/g, ' ')
        .trim()
        .toLowerCase();
}
//# sourceMappingURL=index-builder.js.map