"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TEST_PARTITION_MANIFEST_SCHEMA_VERSION = exports.DEFAULT_TEST_PARTITION_MANIFEST_PATH = void 0;
exports.inspectTaskValidationCoverage = inspectTaskValidationCoverage;
exports.parseTestPartitionManifest = parseTestPartitionManifest;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const paths_1 = require("../paths");
exports.DEFAULT_TEST_PARTITION_MANIFEST_PATH = '.day-shift/test-partitions.json';
exports.TEST_PARTITION_MANIFEST_SCHEMA_VERSION = 1;
/**
 * Qualifies declared validation coverage from repository-authored static
 * partition evidence. This function only reads the task contract and manifest;
 * it never executes validation commands or writes repository state.
 */
function inspectTaskValidationCoverage(options) {
    const manifestPath = normalizeManifestPath(options.manifestPath ?? exports.DEFAULT_TEST_PARTITION_MANIFEST_PATH);
    const absoluteManifestPath = node_path_1.default.join(options.repositoryRoot, manifestPath);
    if (!node_fs_1.default.existsSync(absoluteManifestPath)) {
        return emptyInspection(manifestPath, 'absent');
    }
    const parsedManifest = readTestPartitionManifest(absoluteManifestPath);
    if (parsedManifest.manifest === null) {
        const findings = parsedManifest.issues.map((issue) => ({
            code: 'TASK_VALIDATION_COVERAGE_MANIFEST_INVALID',
            severity: 'error',
            path: manifestPath,
            message: issue,
            recovery: `Repair '${manifestPath}' so every partition and ownership path is a canonical repository-relative path, then rerun task readiness-review.`,
            noWriteBoundary: 'Task readiness review reads partition evidence only; it does not repair the manifest or execute tests.',
        }));
        return {
            ...emptyInspection(manifestPath, 'invalid'),
            manifestIssues: parsedManifest.issues,
            findings,
        };
    }
    const manifest = parsedManifest.manifest;
    const commands = extractValidationCommands(options.validationCommandsText);
    const partitionByFacade = new Map(manifest.partitions.map((partition) => [partition.facadePath, partition]));
    const sharedSuitePaths = new Set(manifest.partitions.map((partition) => partition.sharedSuitePath));
    const selectedFacades = new Set();
    const selectedPartitionIds = new Set();
    const unregisteredTestCommands = new Set();
    const dynamicTestCommands = new Set();
    for (const command of commands) {
        const commandPaths = new Set(command.tokens.flatMap(normalizeCommandPathToken));
        let selectedKnownFacade = false;
        let referencesSharedSuiteDirectly = false;
        for (const commandPath of commandPaths) {
            const partition = partitionByFacade.get(commandPath);
            if (partition !== undefined) {
                selectedKnownFacade = true;
                selectedFacades.add(partition.facadePath);
                selectedPartitionIds.add(partition.id);
            }
            if (sharedSuitePaths.has(commandPath)) {
                referencesSharedSuiteDirectly = true;
            }
        }
        if (command.dynamic && command.testLike) {
            dynamicTestCommands.add(command.command);
        }
        else if (referencesSharedSuiteDirectly && !selectedKnownFacade) {
            dynamicTestCommands.add(command.command);
        }
        else if (command.testLike && !selectedKnownFacade) {
            unregisteredTestCommands.add(command.command);
        }
    }
    const partitionById = new Map(manifest.partitions.map((partition) => [partition.id, partition]));
    const ownershipByPath = new Map(manifest.ownership.map((ownership) => [ownership.path, ownership]));
    const assessments = [];
    const findings = [];
    for (const targetPath of uniqueCanonicalPaths(options.targetPaths)) {
        const ownership = ownershipByPath.get(targetPath);
        if (ownership === undefined)
            continue;
        const coveredPartitionIds = ownership.partitionIds.filter((partitionId) => selectedPartitionIds.has(partitionId));
        const missingPartitionIds = ownership.partitionIds.filter((partitionId) => !selectedPartitionIds.has(partitionId));
        const recommendedFacadePaths = missingPartitionIds
            .map((partitionId) => partitionById.get(partitionId)?.facadePath)
            .filter((candidate) => candidate !== undefined);
        if (missingPartitionIds.length === 0) {
            assessments.push({
                targetPath,
                state: 'proven',
                requiredPartitionIds: ownership.partitionIds,
                coveredPartitionIds,
                missingPartitionIds,
                recommendedFacadePaths,
                reason: `Every declared partition owner for '${targetPath}' is selected by an exact registered validation facade.`,
            });
            continue;
        }
        const hasUnknownSelection = dynamicTestCommands.size > 0 || unregisteredTestCommands.size > 0;
        const state = hasUnknownSelection ? 'unknown' : 'omitted';
        const reason = state === 'unknown'
            ? `Validation for '${targetPath}' uses dynamic or unregistered test selection, so the missing partition ownership cannot be proven.`
            : `Validation for '${targetPath}' omits declared partition owner${missingPartitionIds.length === 1 ? '' : 's'}: ${missingPartitionIds.join(', ')}.`;
        assessments.push({
            targetPath,
            state,
            requiredPartitionIds: ownership.partitionIds,
            coveredPartitionIds,
            missingPartitionIds,
            recommendedFacadePaths,
            reason,
        });
        findings.push({
            code: state === 'unknown' ? 'TASK_VALIDATION_COVERAGE_UNKNOWN' : 'TASK_VALIDATION_COVERAGE_OMITTED',
            severity: 'warning',
            path: options.taskDefinitionPath,
            message: reason,
            recovery: recommendedFacadePaths.length === 0
                ? `Register an exact validation facade and ownership mapping in '${manifestPath}', then cite that facade in Validation Commands.`
                : `Add the registered facade${recommendedFacadePaths.length === 1 ? '' : 's'} ${recommendedFacadePaths.join(', ')} to Validation Commands, then rerun task readiness-review.`,
            recommendedCommand: recommendedFacadePaths.length === 0 ? undefined : `node --test ${recommendedFacadePaths.join(' ')}`,
            noWriteBoundary: 'Task readiness review qualifies declared argv without executing tests or changing task metadata.',
            evidence: {
                category: 'planning-review',
                details: {
                    coverageState: state,
                    manifestPath,
                    targetPath,
                    requiredPartitionIds: ownership.partitionIds,
                    coveredPartitionIds,
                    missingPartitionIds,
                    recommendedFacadePaths,
                },
            },
        });
    }
    return {
        manifestPath,
        manifestState: 'valid',
        manifestIssues: [],
        selectedFacadePaths: [...selectedFacades].sort(),
        selectedPartitionIds: [...selectedPartitionIds].sort(),
        unregisteredTestCommands: [...unregisteredTestCommands],
        dynamicTestCommands: [...dynamicTestCommands],
        assessments,
        findings,
        readOnly: true,
    };
}
function parseTestPartitionManifest(value) {
    const issues = [];
    if (!isRecord(value)) {
        return {
            manifest: null,
            issues: ['Test partition manifest must be a JSON object.'],
        };
    }
    if (value.schemaVersion !== exports.TEST_PARTITION_MANIFEST_SCHEMA_VERSION) {
        issues.push(`Test partition manifest requires schemaVersion ${exports.TEST_PARTITION_MANIFEST_SCHEMA_VERSION}.`);
    }
    if (!Array.isArray(value.partitions)) {
        issues.push("Test partition manifest field 'partitions' must be an array.");
    }
    if (!Array.isArray(value.ownership)) {
        issues.push("Test partition manifest field 'ownership' must be an array.");
    }
    if (issues.length > 0)
        return { manifest: null, issues };
    const partitions = [];
    const partitionIds = new Set();
    const facadePaths = new Set();
    for (const [index, candidate] of value.partitions.entries()) {
        const label = `Test partition manifest partitions[${index}]`;
        if (!isRecord(candidate)) {
            issues.push(`${label} must be an object.`);
            continue;
        }
        const id = readIdentity(candidate.id, `${label}.id`, issues);
        const facadePath = readCanonicalPath(candidate.facadePath, `${label}.facadePath`, issues);
        const sharedSuitePath = readCanonicalPath(candidate.sharedSuitePath, `${label}.sharedSuitePath`, issues);
        const selectedPartition = readIdentity(candidate.selectedPartition, `${label}.selectedPartition`, issues);
        if (id === null || facadePath === null || sharedSuitePath === null || selectedPartition === null) {
            continue;
        }
        if (partitionIds.has(id))
            issues.push(`${label}.id duplicates partition identity '${id}'.`);
        if (facadePaths.has(facadePath)) {
            issues.push(`${label}.facadePath duplicates registered facade '${facadePath}'.`);
        }
        partitionIds.add(id);
        facadePaths.add(facadePath);
        partitions.push({ id, facadePath, sharedSuitePath, selectedPartition });
    }
    const ownership = [];
    const ownershipPaths = new Set();
    for (const [index, candidate] of value.ownership.entries()) {
        const label = `Test partition manifest ownership[${index}]`;
        if (!isRecord(candidate)) {
            issues.push(`${label} must be an object.`);
            continue;
        }
        const ownedPath = readCanonicalPath(candidate.path, `${label}.path`, issues);
        if (!Array.isArray(candidate.partitionIds) || candidate.partitionIds.length === 0) {
            issues.push(`${label}.partitionIds must be a non-empty array.`);
            continue;
        }
        const ownedPartitionIds = [];
        const seenOwnedIds = new Set();
        for (const [partitionIndex, rawPartitionId] of candidate.partitionIds.entries()) {
            const partitionId = readIdentity(rawPartitionId, `${label}.partitionIds[${partitionIndex}]`, issues);
            if (partitionId === null)
                continue;
            if (seenOwnedIds.has(partitionId)) {
                issues.push(`${label}.partitionIds duplicates '${partitionId}'.`);
                continue;
            }
            seenOwnedIds.add(partitionId);
            ownedPartitionIds.push(partitionId);
        }
        if (ownedPath === null)
            continue;
        if (ownershipPaths.has(ownedPath)) {
            issues.push(`${label}.path duplicates ownership path '${ownedPath}'.`);
        }
        ownershipPaths.add(ownedPath);
        ownership.push({ path: ownedPath, partitionIds: ownedPartitionIds });
    }
    for (const entry of ownership) {
        for (const partitionId of entry.partitionIds) {
            if (!partitionIds.has(partitionId)) {
                issues.push(`Test partition ownership '${entry.path}' references unknown partition identity '${partitionId}'.`);
            }
        }
    }
    if (partitions.length === 0)
        issues.push('Test partition manifest requires at least one partition.');
    if (issues.length > 0)
        return { manifest: null, issues };
    return {
        manifest: {
            schemaVersion: exports.TEST_PARTITION_MANIFEST_SCHEMA_VERSION,
            partitions,
            ownership,
        },
        issues: [],
    };
}
function readTestPartitionManifest(absolutePath) {
    try {
        return parseTestPartitionManifest(JSON.parse(node_fs_1.default.readFileSync(absolutePath, 'utf8')));
    }
    catch (error) {
        return {
            manifest: null,
            issues: [
                `Test partition manifest could not be parsed as JSON: ${error instanceof Error ? error.message : String(error)}`,
            ],
        };
    }
}
function emptyInspection(manifestPath, manifestState) {
    return {
        manifestPath,
        manifestState,
        manifestIssues: [],
        selectedFacadePaths: [],
        selectedPartitionIds: [],
        unregisteredTestCommands: [],
        dynamicTestCommands: [],
        assessments: [],
        findings: [],
        readOnly: true,
    };
}
function normalizeManifestPath(input) {
    const parsed = (0, paths_1.parseRepositoryRelativePath)(input);
    if (parsed.normalizedPath !== input) {
        throw new TypeError(`Manifest path must use canonical repository-relative POSIX form: '${input}'.`);
    }
    return parsed.normalizedPath;
}
function readCanonicalPath(value, label, issues) {
    if (typeof value !== 'string' || value.trim() === '') {
        issues.push(`${label} must be a non-empty canonical repository-relative path.`);
        return null;
    }
    try {
        const parsed = (0, paths_1.parseRepositoryRelativePath)(value);
        if (parsed.normalizedPath !== value || value.includes('\\')) {
            issues.push(`${label} must use canonical repository-relative POSIX form: '${value}'.`);
            return null;
        }
        return parsed.normalizedPath;
    }
    catch (error) {
        issues.push(`${label} is invalid: ${error instanceof Error ? error.message : String(error)}`);
        return null;
    }
}
function readIdentity(value, label, issues) {
    if (typeof value !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/u.test(value)) {
        issues.push(`${label} must be a stable lowercase machine identity.`);
        return null;
    }
    return value;
}
function extractValidationCommands(sectionText) {
    return sectionText
        .split(/\r?\n/u)
        .map((line) => line.trim())
        .filter((line) => line.length > 0 && !line.startsWith('```') && !line.startsWith('~~~') && !line.startsWith('#'))
        .map(parseCommand);
}
function parseCommand(command) {
    const tokens = [];
    let token = '';
    let quote = null;
    let dynamic = false;
    for (let index = 0; index < command.length; index += 1) {
        const character = command[index];
        if (quote === "'") {
            if (character === "'")
                quote = null;
            else
                token += character;
            continue;
        }
        if (quote === '"') {
            if (character === '"')
                quote = null;
            else if (character === '\\') {
                index += 1;
                token += command[index] ?? '';
            }
            else {
                if (character === '$' || character === '`')
                    dynamic = true;
                token += character;
            }
            continue;
        }
        if (character === "'" || character === '"') {
            quote = character;
            continue;
        }
        if (character === '\\') {
            index += 1;
            token += command[index] ?? '';
            continue;
        }
        if (/\s/u.test(character)) {
            if (token !== '') {
                tokens.push(token);
                token = '';
            }
            continue;
        }
        if ('[]|&;<>()$`*?{}'.includes(character))
            dynamic = true;
        token += character;
    }
    if (token !== '')
        tokens.push(token);
    if (quote !== null)
        dynamic = true;
    const testLike = tokens.includes('--test') ||
        tokens.some((entry) => /(?:^|\/)(?:run-v2-test-files\.js|[^/]+(?:\.test|\.suite)\.[cm]?js)$/u.test(entry));
    return { command, tokens, dynamic, testLike };
}
function normalizeCommandPathToken(token) {
    if (token === '' ||
        token.startsWith('-') ||
        token.includes('=') ||
        [...token].some((character) => '[]$`*?{}'.includes(character))) {
        return [];
    }
    try {
        return [(0, paths_1.parseRepositoryRelativePath)(token).normalizedPath];
    }
    catch {
        return [];
    }
}
function uniqueCanonicalPaths(paths) {
    const result = new Set();
    for (const candidate of paths) {
        try {
            result.add((0, paths_1.parseRepositoryRelativePath)(candidate).normalizedPath);
        }
        catch {
            continue;
        }
    }
    return [...result];
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
