"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TASK_RECONCILIATION_SECTION_ORDER = exports.MILESTONE_RECONCILIATION_SECTION_ORDER = exports.ACCEPTED_RESIDUAL_SECTION = exports.RESOLVABLE_RECONCILIATION_SECTIONS = void 0;
exports.reconciliationSectionOrder = reconciliationSectionOrder;
exports.reviewReconciliationHeadingContract = reviewReconciliationHeadingContract;
exports.reviewReconciliationResolutionEvidence = reviewReconciliationResolutionEvidence;
exports.materializeReconciliationFindingIdentities = materializeReconciliationFindingIdentities;
exports.reviewReconciliationResolutionHistory = reviewReconciliationResolutionHistory;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const paths_1 = require("../paths");
const planning_artifact_document_1 = require("./planning-artifact-document");
const planning_document_utils_1 = require("./planning-document-utils");
const task_lifecycle_1 = require("./task-lifecycle");
exports.RESOLVABLE_RECONCILIATION_SECTIONS = [
    "Discovered Work",
    "Backlog Work",
    "Follow-Up Tasks",
    "Review Concerns"
];
exports.ACCEPTED_RESIDUAL_SECTION = "Accepted Residual Work";
exports.MILESTONE_RECONCILIATION_SECTION_ORDER = [
    "Review Status",
    "Milestone Intent",
    "Completed Task Summaries",
    "Acceptance Criteria Review",
    "Planned Work",
    "Resolved Findings",
    "Discovered Work",
    "Backlog Work",
    exports.ACCEPTED_RESIDUAL_SECTION,
    "Validation Review",
    "Follow-Up Tasks",
    "Review Concerns",
    "Final Notes"
];
exports.TASK_RECONCILIATION_SECTION_ORDER = [
    "Review Status",
    "Original Intent",
    "Completed Work",
    "Acceptance Criteria Review",
    "Planned Work",
    "Resolved Findings",
    "Discovered Work",
    "Backlog Work",
    exports.ACCEPTED_RESIDUAL_SECTION,
    "Validation Review",
    "Follow-Up Tasks",
    "Review Concerns",
    "Final Notes"
];
const FINDING_ID_PATTERN = /^reconciliation-finding-[a-f0-9]{24}$/;
const REVISION_PATTERN = /^sha256:[a-f0-9]{64}$/;
const EMPTY_SECTION_PATTERN = /^(?:none(?: identified by build refresh)?|none yet|not applicable)\.?$/i;
function reconciliationSectionOrder(mode) {
    return mode === "task"
        ? exports.TASK_RECONCILIATION_SECTION_ORDER
        : exports.MILESTONE_RECONCILIATION_SECTION_ORDER;
}
function reviewReconciliationHeadingContract(markdownText, mode) {
    const expectedHeadings = reconciliationSectionOrder(mode);
    const observedHeadings = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(markdownText).sections.map((section) => section.heading);
    const issues = [];
    for (const expected of expectedHeadings) {
        const count = observedHeadings.filter((heading) => heading === expected).length;
        if (count === 0) {
            issues.push({
                code: "RECONCILIATION_REVIEW_HEADING_MISSING",
                message: `Reconciliation is missing required section '${expected}'.`
            });
        }
        else if (count > 1) {
            issues.push({
                code: "RECONCILIATION_REVIEW_HEADING_DUPLICATE",
                message: `Reconciliation contains required section '${expected}' ${count} times; the canonical heading contract requires exactly one.`
            });
        }
    }
    const observedRequiredHeadings = observedHeadings.filter((heading) => expectedHeadings.includes(heading));
    if (issues.every((issue) => issue.code !== "RECONCILIATION_REVIEW_HEADING_MISSING") &&
        !arraysEqual(observedRequiredHeadings, expectedHeadings)) {
        issues.push({
            code: "RECONCILIATION_REVIEW_HEADING_ORDER_INVALID",
            message: "Reconciliation required sections do not preserve the canonical heading order."
        });
    }
    return {
        mode,
        expectedHeadings,
        observedHeadings,
        resolvedFindingsCount: observedHeadings.filter((heading) => heading === "Resolved Findings").length,
        issues: uniqueContractIssues(issues)
    };
}
function reviewReconciliationResolutionEvidence(repositoryRoot, reconciliationPaths, reconciliationPath) {
    const contextPaths = unique([
        ...reconciliationPaths,
        reconciliationPath
    ]).sort((left, right) => left.localeCompare(right));
    const history = reviewReconciliationResolutionHistory(repositoryRoot, contextPaths);
    const selectedAbsolutePath = node_path_1.default.join(repositoryRoot, reconciliationPath);
    const selectedDocument = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(node_fs_1.default.readFileSync(selectedAbsolutePath, "utf8"));
    const selectedSections = new Map(selectedDocument.sections.map((section) => [section.heading, section.contentText.trim()]));
    const selectedFindings = exports.RESOLVABLE_RECONCILIATION_SECTIONS.flatMap((section) => parseFindingSection(reconciliationPath, section, selectedSections.get(section) ?? ""));
    const selectedResolutions = parseResolutionSection(reconciliationPath, selectedSections.get("Resolved Findings") ?? "");
    const issues = [];
    for (const finding of selectedFindings) {
        if (finding.findingId === null) {
            // Reconciliation review's local resolution gate applies to explicitly
            // declared canonical findings. Narrative review notes and declared-later
            // work retain their existing review classification and are not silently
            // promoted into resolution claims.
            continue;
        }
        const resolved = history.historicalResolved.some((entry) => sameFinding(entry.finding, finding));
        if (resolved)
            continue;
        const live = history.liveWarnings.find((entry) => sameFinding(entry.finding, finding));
        if (live?.resolutionIssue !== null && live?.resolutionIssue !== undefined) {
            issues.push({
                code: classifyResolutionIssue(live.resolutionIssue),
                message: live.resolutionIssue
            });
            continue;
        }
        issues.push({
            code: "RECONCILIATION_REVIEW_RESOLUTION_MISSING",
            message: `Resolved Findings in '${reconciliationPath}' omits declared finding ` +
                `'${finding.findingId ?? "(missing canonical finding_id)"}' from '${finding.section}'.`
        });
    }
    for (const resolution of selectedResolutions) {
        if (resolution.findingId === null) {
            if (selectedFindings.length === 0 &&
                /^-\s+finding_id:\s*\S+/m.test(resolution.rawText)) {
                // Preserve a legacy structured record that is not claiming to resolve a
                // current canonical finding. Parent history review can still surface it
                // as non-canonical history without treating it as current local closure.
                continue;
            }
            issues.push({
                code: "RECONCILIATION_REVIEW_RESOLUTION_SHAPE_INVALID",
                message: `Resolved Findings in '${reconciliationPath}' contains a prose-only or malformed ` +
                    "resolution claim without a canonical finding_id."
            });
            continue;
        }
        const resolved = history.historicalResolved.some((entry) => entry.resolution.recordPath === reconciliationPath &&
            entry.resolution.findingId === resolution.findingId &&
            entry.resolution.rawText === resolution.rawText);
        if (resolved)
            continue;
        const matchingIssue = history.issues.find((issue) => issue.includes(`'${resolution.findingId}'`));
        issues.push({
            code: matchingIssue === undefined
                ? "RECONCILIATION_REVIEW_RESOLUTION_EVIDENCE_INVALID"
                : classifyResolutionIssue(matchingIssue),
            message: matchingIssue ??
                `Resolution record '${resolution.findingId}' in '${reconciliationPath}' does not match current canonical finding evidence.`
        });
    }
    return {
        reconciliationPath,
        contextPaths,
        liveWarnings: history.liveWarnings.filter((entry) => entry.finding.sourcePath === reconciliationPath),
        historicalResolved: history.historicalResolved.filter((entry) => entry.finding.sourcePath === reconciliationPath ||
            entry.resolution.recordPath === reconciliationPath),
        acceptedResidual: history.acceptedResidual.filter((entry) => entry.sourcePath === reconciliationPath),
        fullyResolvedSections: history.fullyResolvedSections.filter((entry) => entry.reconciliationPath === reconciliationPath),
        issues: uniqueContractIssues(issues),
        attribution: {
            transientImplementationCorrections: "Validation Review",
            acceptedContractDecisions: "Resolved Findings"
        }
    };
}
function materializeReconciliationFindingIdentities(reconciliationPath, sections) {
    const next = { ...sections };
    for (const section of [
        ...exports.RESOLVABLE_RECONCILIATION_SECTIONS,
        exports.ACCEPTED_RESIDUAL_SECTION
    ]) {
        const value = next[section];
        if (value === undefined)
            continue;
        next[section] = materializeFindingSection(reconciliationPath, section, value);
    }
    return next;
}
function reviewReconciliationResolutionHistory(repositoryRoot, reconciliationPaths) {
    const findings = [];
    const acceptedResidual = [];
    const resolutions = [];
    const issues = [];
    for (const reconciliationPath of reconciliationPaths) {
        const absolutePath = node_path_1.default.join(repositoryRoot, reconciliationPath);
        if (!node_fs_1.default.existsSync(absolutePath))
            continue;
        const text = node_fs_1.default.readFileSync(absolutePath, "utf8");
        const document = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(text);
        if (!document.hasFrontMatter) {
            issues.push(`Resolution history cannot inspect '${reconciliationPath}' because its front matter is missing or malformed.`);
            continue;
        }
        const sections = new Map(document.sections.map((section) => [section.heading, section.contentText.trim()]));
        for (const section of exports.RESOLVABLE_RECONCILIATION_SECTIONS) {
            findings.push(...parseFindingSection(reconciliationPath, section, sections.get(section) ?? ""));
        }
        acceptedResidual.push(...parseFindingSection(reconciliationPath, exports.ACCEPTED_RESIDUAL_SECTION, sections.get(exports.ACCEPTED_RESIDUAL_SECTION) ?? ""));
        resolutions.push(...parseResolutionSection(reconciliationPath, sections.get("Resolved Findings") ?? ""));
    }
    const findingCounts = new Map();
    for (const finding of findings) {
        if (finding.findingId === null)
            continue;
        findingCounts.set(finding.findingId, (findingCounts.get(finding.findingId) ?? 0) + 1);
    }
    const resolutionsByFinding = new Map();
    for (const resolution of resolutions) {
        if (resolution.findingId === null) {
            issues.push(`Resolved Findings in '${resolution.recordPath}' contains a prose-only or malformed resolution claim without a canonical finding_id.`);
            continue;
        }
        const entries = resolutionsByFinding.get(resolution.findingId) ?? [];
        entries.push(resolution);
        resolutionsByFinding.set(resolution.findingId, entries);
    }
    const liveWarnings = [];
    const historicalResolved = [];
    const validFindingIds = new Set();
    for (const finding of findings) {
        if (finding.findingId === null) {
            const issue = `Finding text in '${finding.sourcePath}' section '${finding.section}' is prose-only; ` +
                "run reconciliation build to assign a stable finding identity before it can be resolved.";
            issues.push(issue);
            liveWarnings.push({ finding, resolutionIssue: issue });
            continue;
        }
        if ((findingCounts.get(finding.findingId) ?? 0) !== 1) {
            const issue = `Finding identity '${finding.findingId}' is ambiguous across reconciliation history and remains live.`;
            issues.push(issue);
            liveWarnings.push({ finding, resolutionIssue: issue });
            continue;
        }
        const matching = resolutionsByFinding.get(finding.findingId) ?? [];
        if (matching.length === 0) {
            liveWarnings.push({ finding, resolutionIssue: null });
            continue;
        }
        if (matching.length !== 1) {
            const issue = `Finding identity '${finding.findingId}' has ${matching.length} resolution records; ` +
                "ambiguous resolution evidence remains live.";
            issues.push(issue);
            liveWarnings.push({ finding, resolutionIssue: issue });
            continue;
        }
        const resolutionIssue = validateResolutionRecord(repositoryRoot, finding, matching[0]);
        if (resolutionIssue !== null) {
            issues.push(resolutionIssue);
            liveWarnings.push({ finding, resolutionIssue });
            continue;
        }
        validFindingIds.add(finding.findingId);
        historicalResolved.push({ finding, resolution: matching[0] });
    }
    for (const resolution of resolutions) {
        if (resolution.findingId !== null &&
            !findingCounts.has(resolution.findingId)) {
            issues.push(`Resolution record '${resolution.findingId}' in '${resolution.recordPath}' does not match any current canonical reconciliation finding identity.`);
        }
    }
    const fullyResolvedSections = [];
    for (const reconciliationPath of reconciliationPaths) {
        for (const section of exports.RESOLVABLE_RECONCILIATION_SECTIONS) {
            const sectionFindings = findings.filter((finding) => finding.sourcePath === reconciliationPath && finding.section === section);
            if (sectionFindings.length > 0 &&
                sectionFindings.every((finding) => finding.findingId !== null && validFindingIds.has(finding.findingId))) {
                fullyResolvedSections.push({ reconciliationPath, section });
            }
        }
    }
    return {
        liveWarnings,
        historicalResolved,
        acceptedResidual,
        issues: unique(issues),
        fullyResolvedSections
    };
}
function materializeFindingSection(reconciliationPath, section, text) {
    const trimmed = text.trim();
    if (trimmed === "" || EMPTY_SECTION_PATTERN.test(trimmed))
        return trimmed;
    const records = splitTopLevelBulletRecords(trimmed);
    if (records.length === 0) {
        // Prose is deliberately not promoted into authoritative evidence. It remains
        // visible and phase review keeps it live until an explicit bullet is authored.
        return trimmed;
    }
    return records
        .map((record) => {
        const fields = parseRecordFields(record);
        if (fields.finding_id !== undefined)
            return record;
        const summary = simpleBulletSummary(record);
        if (summary === null)
            return record;
        const findingId = stableFindingId(reconciliationPath, section, summary);
        return [
            `- finding_id: ${findingId}`,
            `  summary: ${summary}`
        ].join("\n");
    })
        .join("\n");
}
function parseFindingSection(sourcePath, section, text) {
    const trimmed = text.trim();
    if (trimmed === "" || EMPTY_SECTION_PATTERN.test(trimmed))
        return [];
    const records = splitTopLevelBulletRecords(trimmed);
    if (records.length === 0) {
        return [{
                sourcePath,
                section,
                findingId: null,
                summary: normalizeInlineEvidence(trimmed),
                rawText: trimmed
            }];
    }
    return records.map((record) => {
        const fields = parseRecordFields(record);
        return {
            sourcePath,
            section,
            findingId: fields.finding_id !== undefined && FINDING_ID_PATTERN.test(fields.finding_id)
                ? fields.finding_id
                : null,
            summary: fields.summary ??
                simpleBulletSummary(record) ??
                normalizeInlineEvidence(record),
            rawText: record
        };
    });
}
function parseResolutionSection(recordPath, text) {
    const trimmed = text.trim();
    if (trimmed === "" || EMPTY_SECTION_PATTERN.test(trimmed))
        return [];
    const records = splitTopLevelBulletRecords(trimmed);
    if (records.length === 0) {
        return [{
                recordPath,
                findingId: null,
                resolvedBy: null,
                resolutionId: null,
                evidenceRevision: null,
                rawText: trimmed
            }];
    }
    return records.map((record) => {
        const fields = parseRecordFields(record);
        return {
            recordPath,
            findingId: fields.finding_id !== undefined && FINDING_ID_PATTERN.test(fields.finding_id)
                ? fields.finding_id
                : null,
            resolvedBy: fields.resolved_by ?? null,
            resolutionId: fields.resolution_id ?? null,
            evidenceRevision: fields.evidence_revision ?? null,
            rawText: record
        };
    });
}
function validateResolutionRecord(repositoryRoot, finding, resolution) {
    if (resolution.resolvedBy === null ||
        resolution.resolutionId === null ||
        resolution.evidenceRevision === null) {
        return (`Resolution record '${finding.findingId}' in '${resolution.recordPath}' must name ` +
            "resolved_by, resolution_id, and evidence_revision.");
    }
    if (!REVISION_PATTERN.test(resolution.evidenceRevision)) {
        return `Resolution record '${finding.findingId}' has a malformed evidence_revision.`;
    }
    let destinationPath;
    try {
        destinationPath = (0, paths_1.parseRepositoryRelativePath)(normalizeReferencePath(resolution.resolvedBy)).normalizedPath;
    }
    catch {
        return (`Resolution record '${finding.findingId}' names non-canonical destination ` +
            `'${resolution.resolvedBy}'.`);
    }
    if (destinationPath === finding.sourcePath ||
        destinationPath === resolution.recordPath) {
        return `Resolution record '${finding.findingId}' is circular through '${destinationPath}'.`;
    }
    const destinationAbsolute = node_path_1.default.join(repositoryRoot, destinationPath);
    if (!node_fs_1.default.existsSync(destinationAbsolute)) {
        return (`Resolution record '${finding.findingId}' points to missing destination ` +
            `'${destinationPath}'.`);
    }
    const destinationText = node_fs_1.default.readFileSync(destinationAbsolute, "utf8");
    const currentRevision = sha256(destinationText);
    if (resolution.evidenceRevision !== currentRevision) {
        return (`Resolution record '${finding.findingId}' is stale for '${destinationPath}': ` +
            `recorded ${resolution.evidenceRevision}, current ${currentRevision}.`);
    }
    const destination = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(destinationText);
    if (!destination.hasFrontMatter) {
        return (`Resolution record '${finding.findingId}' destination '${destinationPath}' ` +
            "does not have valid planning front matter.");
    }
    const artifactType = (0, planning_document_utils_1.readTopLevelField)(destination.frontMatterLines, "artifact_type")?.trim() ?? null;
    if (artifactType === "implementation_summary") {
        const lifecycle = (0, task_lifecycle_1.projectImplementationSummaryLifecycle)(destinationText);
        const current = lifecycle.value.currentAttempt;
        const terminalAccepted = lifecycle.blockingFindings.length === 0 &&
            lifecycle.value.status === "completed" &&
            current?.implementationStatus === "completed" &&
            current.closedAt !== null &&
            (current.reviewDisposition === "accepted" ||
                current.reviewDisposition === "accepted_with_follow_up");
        if (!terminalAccepted) {
            return (`Resolution record '${finding.findingId}' destination '${destinationPath}' ` +
                "is not a current terminal accepted implementation summary.");
        }
        const acceptanceText = destination.sections.find((section) => section.heading === "Acceptance Criteria Results")?.contentText ?? "";
        if (!containsExactIdentity(acceptanceText, resolution.resolutionId)) {
            return (`Resolution record '${finding.findingId}' resolution_id ` +
                `'${resolution.resolutionId}' is absent from destination acceptance evidence.`);
        }
        return null;
    }
    if (artifactType === "reconciliation") {
        const terminalAccepted = (0, planning_document_utils_1.readTopLevelField)(destination.frontMatterLines, "reconciliation_status")?.trim() ===
            "completed" &&
            (0, planning_document_utils_1.readTopLevelField)(destination.frontMatterLines, "status")?.trim() ===
                "completed";
        if (!terminalAccepted) {
            return (`Resolution record '${finding.findingId}' destination '${destinationPath}' ` +
                "is not a terminal accepted reconciliation.");
        }
        if (!new RegExp(`(?:finding_id|resolution_id):\\s*${escapeRegExp(resolution.resolutionId)}(?:\\s|$)`).test(destinationText)) {
            return (`Resolution record '${finding.findingId}' resolution_id ` +
                `'${resolution.resolutionId}' is absent from destination finding evidence.`);
        }
        return null;
    }
    return (`Resolution record '${finding.findingId}' destination '${destinationPath}' uses ` +
        `unsupported artifact_type '${artifactType ?? "(missing)"}'.`);
}
function splitTopLevelBulletRecords(text) {
    const records = [];
    let current = null;
    for (const line of text.split(/\r?\n/)) {
        if (/^-\s+/.test(line)) {
            if (current !== null)
                records.push(current.join("\n").trimEnd());
            current = [line];
            continue;
        }
        if (current === null) {
            if (line.trim() !== "" && !/^<!--/.test(line.trim()))
                return [];
            continue;
        }
        current.push(line);
    }
    if (current !== null)
        records.push(current.join("\n").trimEnd());
    return records;
}
function parseRecordFields(record) {
    const fields = {};
    for (const line of record.split(/\r?\n/)) {
        const match = line.match(/^(?:-\s+|\s{2,})([a-z_]+):\s*(.+?)\s*$/);
        if (match !== null)
            fields[match[1]] = normalizeInlineEvidence(match[2]);
    }
    return fields;
}
function simpleBulletSummary(record) {
    const lines = record.split(/\r?\n/);
    if (lines.length !== 1)
        return null;
    const match = lines[0].match(/^-\s+(.+?)\s*$/);
    if (match === null || /^[a-z_]+:\s*/.test(match[1]))
        return null;
    return normalizeInlineEvidence(match[1]);
}
function stableFindingId(reconciliationPath, section, summary) {
    const subject = [
        reconciliationPath.replace(/\\/g, "/"),
        section,
        normalizeIdentitySubject(summary)
    ].join("\n");
    return `reconciliation-finding-${(0, node_crypto_1.createHash)("sha256")
        .update(subject)
        .digest("hex")
        .slice(0, 24)}`;
}
function normalizeIdentitySubject(value) {
    return normalizeInlineEvidence(value)
        .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 $2")
        .replace(/`([^`]+)`/g, "$1")
        .replace(/\s+/g, " ")
        .trim()
        .toLowerCase();
}
function normalizeInlineEvidence(value) {
    const trimmed = value.trim();
    if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
        (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
        return trimmed.slice(1, -1);
    }
    return trimmed;
}
function normalizeReferencePath(value) {
    const markdown = value.match(/^\[[^\]]+\]\(([^)]+)\)$/);
    const unwrapped = markdown?.[1] ?? value;
    return unwrapped.replace(/^`|`$/g, "");
}
function containsExactIdentity(text, identity) {
    return new RegExp(`(?:^|[^A-Za-z0-9._-])${escapeRegExp(identity)}(?:$|[^A-Za-z0-9._-])`).test(text);
}
function sha256(value) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(value).digest("hex")}`;
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function unique(values) {
    return values.filter((value, index) => values.indexOf(value) === index);
}
function arraysEqual(left, right) {
    return left.length === right.length && left.every((value, index) => value === right[index]);
}
function sameFinding(left, right) {
    return left.sourcePath === right.sourcePath &&
        left.section === right.section &&
        left.findingId === right.findingId &&
        left.rawText === right.rawText;
}
function classifyResolutionIssue(message) {
    return /prose-only|malformed|canonical finding_id/i.test(message)
        ? "RECONCILIATION_REVIEW_RESOLUTION_SHAPE_INVALID"
        : "RECONCILIATION_REVIEW_RESOLUTION_EVIDENCE_INVALID";
}
function uniqueContractIssues(issues) {
    const seen = new Set();
    return issues.filter((issue) => {
        const identity = `${issue.code}\n${issue.message}`;
        if (seen.has(identity))
            return false;
        seen.add(identity);
        return true;
    });
}
