"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeSpecificationReviewResultSync = normalizeSpecificationReviewResultSync;
const spec_review_1 = require("../spec-review");
const repository_1 = require("./repository");
const types_1 = require("./types");
const PROTOCOL_V1_REVIEW_CONTRACT = [
    { category: "identity", code: "SPEC_REVIEW_IDENTITY" },
    { category: "registry-consistency", code: "SPEC_REVIEW_REGISTRY_CONSISTENCY" },
    { category: "problem", code: "SPEC_REVIEW_REQUIRED_PROBLEM" },
    { category: "goals", code: "SPEC_REVIEW_REQUIRED_GOALS" },
    { category: "non-goals", code: "SPEC_REVIEW_REQUIRED_NON_GOALS" },
    { category: "constraints", code: "SPEC_REVIEW_REQUIRED_CONSTRAINTS" },
    { category: "risks", code: "SPEC_REVIEW_REQUIRED_RISKS" },
    { category: "success-evidence", code: "SPEC_REVIEW_REQUIRED_SUCCESS_EVIDENCE" },
    { category: "placeholder-residue", code: "SPEC_REVIEW_PLACEHOLDER_RESIDUE" },
    { category: "unresolved-questions", code: "SPEC_REVIEW_UNRESOLVED_QUESTIONS" },
    { category: "source-traceability", code: "SPEC_REVIEW_SOURCE_TRACEABILITY" },
    { category: "planning-stability", code: "SPEC_REVIEW_PLANNING_STABILITY" },
    { category: "workflow-fit", code: "SPEC_REVIEW_WORKFLOW_FIT" }
];
const PROTOCOL_V2_CANONICAL_REVIEW_CONTRACT = [
    { category: "identity", code: "SPEC_REVIEW_IDENTITY" },
    { category: "registry-consistency", code: "SPEC_REVIEW_REGISTRY_CONSISTENCY" },
    { category: "specification-profile", code: "SPEC_REVIEW_SPECIFICATION_PROFILE" },
    { category: "purpose", code: "SPEC_REVIEW_REQUIRED_PURPOSE" },
    { category: "problem-statement", code: "SPEC_REVIEW_REQUIRED_PROBLEM_STATEMENT" },
    { category: "desired-outcome", code: "SPEC_REVIEW_REQUIRED_DESIRED_OUTCOME" },
    { category: "non-goals", code: "SPEC_REVIEW_REQUIRED_NON_GOALS" },
    { category: "scope", code: "SPEC_REVIEW_REQUIRED_SCOPE" },
    { category: "constraints", code: "SPEC_REVIEW_REQUIRED_CONSTRAINTS" },
    { category: "success-evidence", code: "SPEC_REVIEW_REQUIRED_SUCCESS_EVIDENCE" },
    { category: "placeholder-residue", code: "SPEC_REVIEW_PLACEHOLDER_RESIDUE" },
    { category: "unresolved-questions", code: "SPEC_REVIEW_UNRESOLVED_QUESTIONS" },
    { category: "source-traceability", code: "SPEC_REVIEW_SOURCE_TRACEABILITY" },
    { category: "planning-stability", code: "SPEC_REVIEW_PLANNING_STABILITY" },
    { category: "workflow-fit", code: "SPEC_REVIEW_WORKFLOW_FIT" }
];
const PROTOCOL_V2_LEGACY_REVIEW_CONTRACT = [
    { category: "identity", code: "SPEC_REVIEW_IDENTITY" },
    { category: "registry-consistency", code: "SPEC_REVIEW_REGISTRY_CONSISTENCY" },
    { category: "specification-profile", code: "SPEC_REVIEW_SPECIFICATION_PROFILE" },
    ...PROTOCOL_V1_REVIEW_CONTRACT.slice(2)
];
const PROTOCOL_V1_KEYS = [
    "review_id", "spec_id", "spec_path", "spec_revision", "protocol_version",
    "reviewed_at", "reviewed_by", "findings", "counts",
    "planning_readiness_recommendation", "workflow_level_recommendation",
    "unresolved_findings", "waived_findings", "read_only", "no_write_boundary"
];
const PROTOCOL_V2_KEYS = [...PROTOCOL_V1_KEYS, "specification_profile"];
const FINDING_KEYS = [
    "artifactPath", "category", "code", "detail", "evidence", "evidenceSource",
    "id", "level", "message", "noWriteBoundary", "path", "severity", "status", "summary"
];
/** Validates and canonicalizes one complete Task 01 review result without writing. */
function normalizeSpecificationReviewResultSync(repositoryRoot, specPath, input, callerDigest) {
    const specification = (0, repository_1.resolveRegisteredSpecification)(repositoryRoot, specPath);
    const result = cloneReviewResult(input);
    validateCompleteReviewResult(result, specification);
    const canonicalJson = `${(0, spec_review_1.serializeSpecificationReviewResultCanonical)(result)}\n`;
    const digest = (0, repository_1.sha256)(Buffer.from(canonicalJson));
    if (callerDigest !== undefined && callerDigest !== digest) {
        throw new types_1.SpecificationReadinessError("digest-mismatch", "Caller-supplied review digest does not match the complete normalized canonical review JSON.");
    }
    return { result, canonicalJson, digest };
}
function cloneReviewResult(input) {
    if (input === null || typeof input !== "object" || Array.isArray(input)) {
        throw new types_1.SpecificationReadinessError("review-invalid", "A complete structured Task 01 review result is required; digest-only evidence is insufficient.");
    }
    try {
        return JSON.parse(JSON.stringify(input));
    }
    catch {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review evidence must be JSON-serializable.");
    }
}
function validateCompleteReviewResult(result, specification) {
    if (!Number.isInteger(result.protocol_version)) {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review protocol version is missing or malformed.");
    }
    if (result.protocol_version !== types_1.LEGACY_SPECIFICATION_REVIEW_PROTOCOL_VERSION &&
        result.protocol_version !== spec_review_1.SPECIFICATION_REVIEW_PROTOCOL_VERSION) {
        throw new types_1.SpecificationReadinessError("unsupported-protocol", `Review protocol version '${String(result.protocol_version)}' is unsupported.`);
    }
    const reviewContract = resolveReviewContract(result);
    requireExactKeys(result, result.protocol_version === types_1.LEGACY_SPECIFICATION_REVIEW_PROTOCOL_VERSION
        ? PROTOCOL_V1_KEYS
        : PROTOCOL_V2_KEYS, `protocol-${result.protocol_version} review`);
    if (result.spec_id !== specification.specId ||
        result.spec_path !== specification.specPath ||
        result.spec_revision !== specification.specRevision) {
        throw new types_1.SpecificationReadinessError("review-mismatch", "Review specification identity, canonical path, or revision does not match the selected current specification.");
    }
    (0, repository_1.requireNonEmpty)(result.reviewed_by, "Review actor", "review-invalid");
    (0, repository_1.requireTimestamp)(result.reviewed_at, "Review time", "review-invalid");
    if (!Array.isArray(result.findings) || result.findings.length !== reviewContract.length) {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review evidence must contain every supported finding category exactly once.");
    }
    result.findings.forEach((finding, index) => validateFinding(finding, reviewContract[index], specification.specPath));
    const derivedUnresolved = result.findings.filter((finding) => finding.status === "unresolved");
    if (!Array.isArray(result.unresolved_findings) || !canonicalEqual(result.unresolved_findings, derivedUnresolved)) {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review unresolved findings do not match the complete findings collection.");
    }
    if (!Array.isArray(result.waived_findings) || result.waived_findings.length !== 0) {
        throw new types_1.SpecificationReadinessError("review-invalid", "Task 01 review evidence must preserve its complete empty waived-finding lane.");
    }
    const expectedCounts = {
        total: result.findings.length,
        error: result.findings.filter((finding) => finding.severity === "error").length,
        warning: result.findings.filter((finding) => finding.severity === "warning").length,
        informational: result.findings.filter((finding) => finding.severity === "informational").length,
        unresolved: derivedUnresolved.length,
        waived: 0
    };
    if (!canonicalEqual(result.counts, expectedCounts)) {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review severity and resolution counts are malformed or incomplete.");
    }
    const expectedPlanningStatus = derivedUnresolved.length === 0 ? "ready" : "refine";
    if (result.planning_readiness_recommendation === null ||
        result.planning_readiness_recommendation.status !== expectedPlanningStatus ||
        typeof result.planning_readiness_recommendation.reason !== "string" ||
        result.workflow_level_recommendation === null ||
        !["basic", "structured", "governed"].includes(result.workflow_level_recommendation.level) ||
        result.workflow_level_recommendation.advisory !== true ||
        !Array.isArray(result.workflow_level_recommendation.reasons) ||
        !result.workflow_level_recommendation.reasons.every((entry) => typeof entry === "string" && entry.trim() !== "") ||
        result.read_only !== true ||
        typeof result.no_write_boundary !== "string" ||
        result.no_write_boundary.trim() === "") {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review recommendations or no-write evidence are malformed or incomplete.");
    }
    const { review_id: _ignored, ...identityEvidence } = result;
    if (result.review_id !== (0, spec_review_1.computeSpecificationReviewId)(identityEvidence)) {
        throw new types_1.SpecificationReadinessError("review-invalid", "Review identity is not canonical for the complete normalized evidence.");
    }
}
function validateFinding(finding, expected, specPath) {
    const evidence = finding?.evidence;
    if (finding === null || typeof finding !== "object" ||
        !hasExactKeys(finding, FINDING_KEYS) ||
        finding.category !== expected.category ||
        finding.code !== expected.code ||
        finding.id !== finding.code ||
        !["error", "warning", "informational"].includes(finding.severity) ||
        finding.level !== finding.severity ||
        !["resolved", "unresolved"].includes(finding.status) ||
        typeof finding.message !== "string" || finding.message.trim() === "" ||
        finding.summary !== finding.message || finding.detail !== finding.message ||
        finding.path !== specPath || finding.artifactPath !== specPath ||
        finding.evidenceSource !== "spec review" ||
        evidence?.category !== "command-specific" ||
        !hasExactKeys((evidence?.details ?? {}), ["reviewCategory", "status"]) ||
        evidence?.details?.reviewCategory !== expected.category ||
        evidence?.details?.status !== finding.status ||
        typeof finding.noWriteBoundary !== "string" || finding.noWriteBoundary.trim() === "") {
        throw new types_1.SpecificationReadinessError("review-invalid", `Review finding '${expected.category}' is malformed or noncanonical.`);
    }
}
function resolveReviewContract(result) {
    const record = result;
    if (result.protocol_version === types_1.LEGACY_SPECIFICATION_REVIEW_PROTOCOL_VERSION) {
        if (Object.prototype.hasOwnProperty.call(record, "specification_profile")) {
            throw new types_1.SpecificationReadinessError("review-invalid", "Protocol-1 review evidence must not contain protocol-2 specification profile evidence.");
        }
        return PROTOCOL_V1_REVIEW_CONTRACT;
    }
    const profile = record.specification_profile;
    if (profile === "day-shift-0.2.0-legacy")
        return PROTOCOL_V2_LEGACY_REVIEW_CONTRACT;
    if (profile === "canonical" || profile === "unrecognized") {
        return PROTOCOL_V2_CANONICAL_REVIEW_CONTRACT;
    }
    throw new types_1.SpecificationReadinessError("review-invalid", "Protocol-2 review evidence requires one explicit supported specification profile.");
}
function requireExactKeys(value, expected, label) {
    if (!hasExactKeys(value, expected)) {
        throw new types_1.SpecificationReadinessError("review-invalid", `The ${label} does not match its exact field contract.`);
    }
}
function hasExactKeys(value, expected) {
    const actual = Object.keys(value).sort();
    const required = [...expected].sort();
    return actual.length === required.length && actual.every((key, index) => key === required[index]);
}
function canonicalEqual(left, right) {
    return canonicalJson(left) === canonicalJson(right);
}
function canonicalJson(value) {
    if (Array.isArray(value))
        return `[${value.map(canonicalJson).join(",")}]`;
    if (value !== null && typeof value === "object") {
        return `{${Object.entries(value)
            .sort(([left], [right]) => left.localeCompare(right))
            .map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
            .join(",")}}`;
    }
    return JSON.stringify(value);
}
