"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderProjectReview = renderProjectReview;
exports.parseProjectReview = parseProjectReview;
exports.validateClosure = validateClosure;
exports.previewMutationResult = previewMutationResult;
exports.aggregationOwnerId = aggregationOwnerId;
exports.aggregationOverviewPath = aggregationOverviewPath;
exports.applyMutation = applyMutation;
exports.replaceOverviewStatus = replaceOverviewStatus;
exports.replaceOverviewStatusValue = replaceOverviewStatusValue;
exports.parseDependencies = parseDependencies;
exports.hasEvidence = hasEvidence;
exports.isPendingEvidence = isPendingEvidence;
exports.sectionSignalsEvidence = sectionSignalsEvidence;
exports.parseJsonSection = parseJsonSection;
exports.readNestedUpstream = readNestedUpstream;
exports.normalizeDisposition = normalizeDisposition;
exports.normalizeSelectedTaskIds = normalizeSelectedTaskIds;
exports.dedupePreconditions = dedupePreconditions;
exports.titleLevel = titleLevel;
exports.finding = finding;
exports.sortFindings = sortFindings;
exports.revision = revision;
exports.normalizeRevision = normalizeRevision;
exports.scalar = scalar;
exports.yamlString = yamlString;
exports.requireText = requireText;
exports.requireRepositoryRoot = requireRepositoryRoot;
exports.requireRelativePath = requireRelativePath;
exports.normalizeStructuredFollowUpError = normalizeStructuredFollowUpError;
// Structured and governed project review aggregation, persistence, and closure orchestration.
const node_path_1 = __importDefault(require("node:path"));
const node_crypto_1 = require("node:crypto");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const planning_artifact_document_1 = require("../planning/planning-artifact-document");
const planning_document_utils_1 = require("../planning/planning-document-utils");
const work_creation_1 = require("../work-creation");
const contracts_and_aggregation_1 = require("./contracts-and-aggregation");
function renderProjectReview(input) {
    const aggregation = input.review.aggregation;
    const reviewedEvidence = {
        overviewRevision: aggregation.overviewRevision,
        specifications: aggregation.specifications.map((entry) => ({
            specId: entry.specId,
            specPath: entry.specPath,
            revision: entry.currentRevision ?? entry.recordedRevision,
        })),
        hierarchyArtifacts: aggregation.hierarchyArtifacts.map((entry) => ({
            artifactType: entry.artifactType,
            artifactPath: entry.artifactPath,
            artifactRevision: entry.artifactRevision,
        })),
        tasks: aggregation.tasks.map((entry) => ({
            taskId: entry.taskId,
            taskDefinitionPath: entry.taskDefinitionPath,
            taskDefinitionRevision: entry.taskDefinitionRevision,
            implementationSummaryPath: entry.implementationSummaryPath,
            implementationSummaryRevision: entry.implementationSummaryRevision,
        })),
        reconciliations: aggregation.reconciliations.map((entry) => ({
            milestoneId: entry.milestoneId,
            milestoneOverviewPath: entry.milestoneOverviewPath,
            reconciliationPath: entry.reconciliationPath,
            reconciliationRevision: entry.reconciliationRevision,
            acceptedResidualWork: entry.acceptedResidualWork,
            followUpWork: entry.followUpWork,
            deferredWork: entry.deferredWork,
            waiverEvidence: entry.waiverEvidence,
        })),
    };
    return [
        '---',
        'artifact_schema_version: 1',
        'artifact_type: project_review',
        aggregation.workflowLevel === 'structured'
            ? `work_id: ${yamlString(aggregationOwnerId(aggregation))}`
            : `slice_id: ${yamlString(aggregationOwnerId(aggregation))}`,
        `workflow_level: ${aggregation.workflowLevel}`,
        `review_disposition: ${input.disposition}`,
        'status: completed',
        'upstream:',
        aggregation.workflowLevel === 'structured'
            ? `  work_overview: ${yamlString(aggregationOverviewPath(aggregation))}`
            : `  slice_overview: ${yamlString(aggregationOverviewPath(aggregation))}`,
        '---',
        '',
        `# ${titleLevel(aggregation.workflowLevel)} Project Review: ${aggregationOwnerId(aggregation)}`,
        '',
        '## Review Summary',
        '',
        input.summary,
        '',
        '## Reviewer',
        '',
        input.reviewer,
        '',
        '## Disposition',
        '',
        `\`${input.disposition}\``,
        '',
        '## Specification Coverage',
        '',
        ...aggregation.specifications.map((entry) => `- \`${entry.specId}\` at \`${entry.specPath}\`: ${entry.current ? 'current' : 'stale'}; tasks: ${entry.coveredTaskIds.map((taskId) => `\`${taskId}\``).join(', ') || 'none'}.`),
        '',
        '## Reconciliation Disposition Evidence',
        '',
        ...(aggregation.reconciliations.length === 0
            ? ['Not applicable to Structured work.']
            : aggregation.reconciliations.map((entry) => `- \`${entry.milestoneId}\` at \`${entry.reconciliationPath}\`: residual=${entry.acceptedResidualWork}; follow_up=${entry.followUpWork}; deferred=${entry.deferredWork}; waiver=${entry.waiverEvidence}.`)),
        '',
        '## Findings',
        '',
        ...(input.review.findings.length === 0
            ? ['None.']
            : input.review.findings.map((entry) => `- ${entry.severity}: ${entry.code} — ${entry.message}`)),
        '',
        '## Reviewed Evidence',
        '',
        '```json',
        JSON.stringify(reviewedEvidence, null, 2),
        '```',
        '',
        '## Follow-Up Task Contracts',
        '',
        '```json',
        JSON.stringify(input.followUpTasks, null, 2),
        '```',
        '',
        '## Closeout Boundary',
        '',
        'This authored review does not create follow-up tasks or close the overview. Those outcomes require separate explicit preview/apply commands.',
        '',
    ].join('\n');
}
function parseProjectReview(text, reviewPath) {
    const document = (0, planning_document_utils_1.parseMarkdownFrontMatter)(text);
    const artifactType = (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'artifact_type');
    const workflowLevel = (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'workflow_level');
    const ownerId = workflowLevel === 'governed'
        ? (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'slice_id')
        : (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'work_id');
    const dispositionValue = (0, planning_document_utils_1.readTopLevelField)(document.frontMatterLines, 'review_disposition');
    const overviewPath = readNestedUpstream(document.frontMatterLines, workflowLevel === 'governed' ? 'slice_overview' : 'work_overview');
    if (artifactType !== 'project_review' ||
        (workflowLevel !== 'structured' && workflowLevel !== 'governed') ||
        !ownerId ||
        !overviewPath ||
        !dispositionValue) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `Project review '${reviewPath}' is missing canonical identity or disposition metadata.`);
    }
    const parsed = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(text);
    const reviewedEvidence = parseJsonSection(parsed, 'Reviewed Evidence');
    const followUpTasks = parseJsonSection(parsed, 'Follow-Up Task Contracts');
    const normalizedReviewedEvidence = reviewedEvidence === null
        ? null
        : {
            ...reviewedEvidence,
            hierarchyArtifacts: Array.isArray(reviewedEvidence.hierarchyArtifacts)
                ? reviewedEvidence.hierarchyArtifacts
                : [],
            reconciliations: Array.isArray(reviewedEvidence.reconciliations) ? reviewedEvidence.reconciliations : [],
        };
    if (!normalizedReviewedEvidence ||
        !Array.isArray(normalizedReviewedEvidence.specifications) ||
        !Array.isArray(normalizedReviewedEvidence.hierarchyArtifacts) ||
        !Array.isArray(normalizedReviewedEvidence.tasks) ||
        !Array.isArray(followUpTasks)) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `Project review '${reviewPath}' has invalid reviewed-evidence or follow-up contracts.`);
    }
    return {
        ownerId,
        workflowLevel,
        overviewPath,
        disposition: normalizeDisposition(dispositionValue),
        reviewedEvidence: normalizedReviewedEvidence,
        followUpTasks,
    };
}
function validateClosure(aggregation, review) {
    const levelTitle = titleLevel(aggregation.workflowLevel);
    if (review.ownerId !== aggregationOwnerId(aggregation) ||
        review.workflowLevel !== aggregation.workflowLevel ||
        review.overviewPath !== aggregationOverviewPath(aggregation)) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `Project review identity does not match the selected ${levelTitle} overview.`);
    }
    if (review.disposition !== 'accepted' && review.disposition !== 'accepted_with_follow_up') {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Project review disposition '${review.disposition}' does not authorize closure.`);
    }
    if (!aggregation.closeoutEligible) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `${levelTitle} project still has incomplete tasks, reconciliations, or blocking aggregate findings.`);
    }
    if (review.reviewedEvidence.overviewRevision !== aggregation.overviewRevision) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `${levelTitle} overview changed after project review; refresh the review before closure.`);
    }
    const specificationRevisions = new Map(aggregation.specifications.map((entry) => [entry.specPath, entry.currentRevision]));
    for (const specification of review.reviewedEvidence.specifications) {
        if (specificationRevisions.get(specification.specPath) !== normalizeRevision(specification.revision)) {
            throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Specification '${specification.specId}' changed after project review.`);
        }
    }
    const currentHierarchyArtifacts = new Map(aggregation.hierarchyArtifacts.map((entry) => [entry.artifactPath, entry]));
    for (const hierarchyArtifact of review.reviewedEvidence.hierarchyArtifacts) {
        const current = currentHierarchyArtifacts.get(hierarchyArtifact.artifactPath);
        if (!current ||
            current.artifactType !== hierarchyArtifact.artifactType ||
            current.artifactRevision !== hierarchyArtifact.artifactRevision) {
            throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Reviewed hierarchy artifact '${hierarchyArtifact.artifactPath}' changed after project review.`);
        }
    }
    if (review.reviewedEvidence.hierarchyArtifacts.length !== aggregation.hierarchyArtifacts.length) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', 'Current Governed hierarchy artifact set differs from reviewed evidence.');
    }
    const currentTasks = new Map(aggregation.tasks.map((task) => [task.taskDefinitionPath, task]));
    for (const task of review.reviewedEvidence.tasks) {
        const current = currentTasks.get(task.taskDefinitionPath);
        if (!current ||
            current.taskDefinitionRevision !== task.taskDefinitionRevision ||
            current.implementationSummaryRevision !== task.implementationSummaryRevision) {
            throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Reviewed task '${task.taskId}' changed after project review.`);
        }
    }
    const currentReconciliations = new Map(aggregation.reconciliations.map((entry) => [entry.reconciliationPath, entry]));
    for (const reconciliation of review.reviewedEvidence.reconciliations) {
        const current = currentReconciliations.get(reconciliation.reconciliationPath);
        if (!current ||
            current.milestoneId !== reconciliation.milestoneId ||
            current.milestoneOverviewPath !== reconciliation.milestoneOverviewPath ||
            current.reconciliationRevision !== reconciliation.reconciliationRevision ||
            current.acceptedResidualWork !== reconciliation.acceptedResidualWork ||
            current.followUpWork !== reconciliation.followUpWork ||
            current.deferredWork !== reconciliation.deferredWork ||
            current.waiverEvidence !== reconciliation.waiverEvidence) {
            throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Reviewed reconciliation '${reconciliation.reconciliationPath}' changed after project review.`);
        }
    }
    const reviewedPaths = new Set(review.reviewedEvidence.tasks.map((task) => task.taskDefinitionPath));
    const followUpIds = new Set(review.followUpTasks.map((task) => task.taskId));
    const unknown = aggregation.tasks.filter((task) => !reviewedPaths.has(task.taskDefinitionPath) && !followUpIds.has(task.taskId));
    const currentTaskIds = new Set(aggregation.tasks.map((task) => task.taskId));
    const missingFollowUp = [...followUpIds].filter((taskId) => !currentTaskIds.has(taskId));
    const reviewedReconciliationPaths = new Set(review.reviewedEvidence.reconciliations.map((entry) => entry.reconciliationPath));
    const unknownReconciliations = aggregation.reconciliations.filter((entry) => !reviewedReconciliationPaths.has(entry.reconciliationPath));
    if (unknown.length > 0 || missingFollowUp.length > 0) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Current task set differs from reviewed and declared follow-up work (unknown: ${unknown.map((task) => task.taskId).join(', ') || 'none'}; missing follow-up: ${missingFollowUp.join(', ') || 'none'}).`);
    }
    if (unknownReconciliations.length > 0 || reviewedReconciliationPaths.size !== aggregation.reconciliations.length) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', `Current reconciliation set differs from reviewed evidence (unknown: ${unknownReconciliations.map((entry) => entry.reconciliationPath).join(', ') || 'none'}).`);
    }
    if (aggregation.workflowLevel === 'governed' &&
        review.disposition === 'accepted_with_follow_up' &&
        aggregation.counts.reconciliationsWithResidualWork === 0) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('not-closeable', 'Governed accepted-with-follow-up closure no longer has current reconciliation-owned residual evidence.');
    }
}
function previewMutationResult(aggregation, operation, disposition, preconditions, intendedPaths, nextAction) {
    return {
        workflowLevel: aggregation.workflowLevel,
        ...(aggregation.workflowLevel === 'structured'
            ? {
                workId: aggregationOwnerId(aggregation),
                overviewPath: aggregationOverviewPath(aggregation),
            }
            : {
                sliceId: aggregationOwnerId(aggregation),
                sliceOverviewPath: aggregationOverviewPath(aggregation),
            }),
        operation,
        projectReviewPath: aggregation.projectReviewPath,
        disposition,
        expected: preconditions.map((entry) => ({
            path: entry.repositoryRelativePath,
            kind: entry.kind,
            expectedState: entry.kind === 'content-revision' ? entry.expectedRevision : entry.expectedPresence,
        })),
        intendedPaths,
        changedPaths: [],
        writeOutcome: 'previewed',
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null,
        nextAction,
    };
}
function aggregationOwnerId(aggregation) {
    const ownerId = aggregation.workflowLevel === 'governed' ? aggregation.sliceId : aggregation.workId;
    if (!ownerId) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `${titleLevel(aggregation.workflowLevel)} aggregation is missing canonical owner identity.`);
    }
    return ownerId;
}
function aggregationOverviewPath(aggregation) {
    const overviewPath = aggregation.workflowLevel === 'governed' ? aggregation.sliceOverviewPath : aggregation.overviewPath;
    if (!overviewPath) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `${titleLevel(aggregation.workflowLevel)} aggregation is missing canonical owner overview path.`);
    }
    return overviewPath;
}
function applyMutation(preview, plans, options, label) {
    const plan = plans.get(preview);
    if (plan === undefined || preview.writeOutcome !== 'previewed') {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('stale-preview', `${titleLevel(preview.workflowLevel)} ${label} apply requires the original unused in-process preview. Refresh the preview.`);
    }
    plans.delete(preview);
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(plan.repositoryRoot, { writes: plan.writes, preconditions: plan.preconditions }, options.transaction);
    const levelTitle = titleLevel(preview.workflowLevel);
    return {
        ...preview,
        changedPaths: transaction.entries.slice(0, transaction.canonicalWrites).map((entry) => entry.path),
        writeOutcome: transaction.outcome,
        transactionId: transaction.transactionId,
        conflicts: transaction.conflicts,
        recoveryPath: transaction.outcome === 'incomplete' ? transaction.transactionPath : null,
        failure: transaction.failure,
        nextAction: transaction.outcome === 'committed'
            ? preview.operation === 'project-close'
                ? `${levelTitle} project is explicitly closed.`
                : preview.workflowLevel === 'structured'
                    ? 'Create declared follow-up tasks or preview explicit project closure.'
                    : 'Preview explicit Governed project closure.'
            : transaction.outcome === 'conflict'
                ? `Refresh the ${levelTitle} ${label} preview after inspecting changed evidence.`
                : transaction.outcome === 'incomplete'
                    ? `Inspect retained transaction evidence before retrying ${levelTitle} ${label}.`
                    : `Refresh the ${levelTitle} ${label} preview; no canonical write was required.`,
    };
}
function replaceOverviewStatus(text) {
    return replaceOverviewStatusValue(text, 'completed');
}
function replaceOverviewStatusValue(text, status) {
    const lines = text.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === '---');
    const index = lines.findIndex((line, lineIndex) => lineIndex > 0 && lineIndex < closing && /^status:\s*/.test(line));
    if (closing < 0 || index < 0) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', 'Work overview status field is missing.');
    }
    lines[index] = `status: ${status}`;
    return `${lines.join('\n').trimEnd()}\n`;
}
function parseDependencies(lines) {
    const values = [];
    const index = lines.findIndex((line) => /^depends_on:\s*/.test(line));
    if (index < 0 || /^depends_on:\s*\[\s*\]\s*$/.test(lines[index]))
        return values;
    for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
        const match = lines[cursor].match(/^\s{2}-\s*(.+)$/);
        if (match === null)
            break;
        values.push(scalar(match[1]));
    }
    return values;
}
function hasEvidence(document, heading) {
    if (document === null)
        return false;
    const text = document.sections.find((section) => section.heading === heading)?.contentText.trim() ?? '';
    return text !== '' && !/^(pending|none yet|not started)/i.test(text) && !/pending review|record .* after/i.test(text);
}
function isPendingEvidence(value) {
    const normalized = value.trim();
    return (normalized === '' ||
        /^(pending|not started|not reviewed|none yet)\b/i.test(normalized) ||
        /\bpending (?:review|task|implementation|validation)\b/i.test(normalized) ||
        /\badd completed task\b/i.test(normalized) ||
        /\bplaceholder\b/i.test(normalized));
}
function sectionSignalsEvidence(value) {
    if (value === undefined)
        return false;
    const normalized = value.trim();
    if (normalized === '')
        return false;
    if (/^(?:-\s*)?none(?:\.| currently\.)?$/i.test(normalized))
        return false;
    if (/^no (?:accepted |active |remaining |follow-up |deferred |backlog )?(?:residual |follow-up |deferred |backlog |waiver)?work(?: remains)?\.?$/i.test(normalized)) {
        return false;
    }
    if (/^no (?:active )?waivers?\.?$/i.test(normalized))
        return false;
    return !isPendingEvidence(normalized);
}
function parseJsonSection(document, heading) {
    const text = document.sections.find((section) => section.heading === heading)?.contentText ?? '';
    const match = text.match(/```json\s*([\s\S]*?)\s*```/);
    if (match === null)
        return null;
    try {
        return JSON.parse(match[1]);
    }
    catch {
        return null;
    }
}
function readNestedUpstream(lines, key) {
    let inUpstream = false;
    for (const line of lines) {
        if (line === 'upstream:') {
            inUpstream = true;
            continue;
        }
        if (inUpstream && /^\S/.test(line))
            break;
        const match = line.match(new RegExp(`^\\s{2}${key}:\\s*(.+)$`));
        if (inUpstream && match !== null)
            return scalar(match[1]);
    }
    return null;
}
function normalizeDisposition(value) {
    const normalized = value.trim().toLowerCase().replace(/[ -]+/g, '_');
    if (!contracts_and_aggregation_1.REVIEW_DISPOSITIONS.has(normalized)) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `Unsupported project review disposition '${value}'.`);
    }
    return normalized;
}
function normalizeSelectedTaskIds(values) {
    if (!Array.isArray(values) || values.length === 0) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', 'Select at least one follow-up task id.');
    }
    return values.map((value) => requireText(value, 'Follow-up task id'));
}
function dedupePreconditions(preconditions) {
    const seen = new Set();
    return preconditions.filter((entry) => {
        const key = `${entry.kind}:${entry.repositoryRelativePath}`;
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
function titleLevel(value) {
    return value === 'structured' ? 'Structured' : 'Governed';
}
function finding(code, severity, findingPath, message) {
    return { code, severity, path: findingPath, message };
}
function sortFindings(findings) {
    const rank = { error: 0, warning: 1, informational: 2 };
    return [...findings].sort((left, right) => rank[left.severity] - rank[right.severity] ||
        left.path.localeCompare(right.path) ||
        left.code.localeCompare(right.code) ||
        left.message.localeCompare(right.message));
}
function revision(value) {
    return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
}
function normalizeRevision(value) {
    return value.startsWith('sha256:') ? value.slice('sha256:'.length) : value;
}
function scalar(value) {
    const trimmed = value.trim();
    if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
        return trimmed.slice(1, -1);
    }
    return trimmed;
}
function yamlString(value) {
    return JSON.stringify(value);
}
function requireText(value, label) {
    if (typeof value !== 'string' || value.trim() === '') {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `${label} must be non-empty.`);
    }
    return value.trim();
}
function requireRepositoryRoot(value) {
    if (typeof value !== 'string' || value.trim() === '') {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', 'Repository root is required.');
    }
    return node_path_1.default.resolve(value);
}
function requireRelativePath(value, label) {
    const normalized = requireText(value, label).replace(/\\/g, '/');
    if (node_path_1.default.posix.isAbsolute(normalized) || normalized.split('/').includes('..')) {
        throw new contracts_and_aggregation_1.StructuredProjectReviewError('invalid-request', `${label} must be repository-relative and contained.`);
    }
    return normalized.replace(/^\.\//, '');
}
// Keep the follow-up adapter's inherited error identity stable for callers.
function normalizeStructuredFollowUpError(error) {
    return error instanceof work_creation_1.StructuredWorkCreationError || error instanceof contracts_and_aggregation_1.StructuredProjectReviewError
        ? error
        : error instanceof Error
            ? error
            : new Error('Invalid Structured follow-up request.');
}
