"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveSpecificationPlanningReadinessSync = resolveSpecificationPlanningReadinessSync;
const node_fs_1 = __importDefault(require("node:fs"));
const planning_document_utils_1 = require("../planning/planning-document-utils");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const review_normalization_1 = require("./review-normalization");
const repository_1 = require("./repository");
const types_1 = require("./types");
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
/** Resolves current planning eligibility from repository evidence without mutation. */
function resolveSpecificationPlanningReadinessSync(repositoryRoot, specPath, options = {}) {
    const specification = (0, repository_1.resolveRegisteredSpecification)(repositoryRoot, specPath);
    const base = {
        specId: specification.specId,
        specPath: specification.specPath,
        currentSpecRevision: specification.specRevision,
        dispositionPath: specification.dispositionTarget.repositoryRelativePath
    };
    if (!specification.dispositionTarget.exists) {
        return resolution(base, "missing", "No planning-readiness disposition is recorded.");
    }
    let parsed;
    try {
        parsed = parseDispositionDocument(node_fs_1.default.readFileSync(specification.dispositionTarget.runtimePath, "utf8"));
    }
    catch (error) {
        return resolution(base, "invalid", (0, repository_1.errorMessage)(error, "Planning-readiness evidence is unreadable."));
    }
    if (parsed.schemaVersion !== types_1.SPECIFICATION_PLANNING_READINESS_SCHEMA_VERSION) {
        return resolution(base, "unsupported", `Planning-readiness schema version '${parsed.schemaVersion}' is unsupported.`, parsed);
    }
    if (parsed.artifactType !== "spec_planning_readiness" || parsed.specId !== specification.specId) {
        return resolution(base, "invalid", "Planning-readiness artifact identity does not match the selected specification.", parsed);
    }
    if (!SHA256_PATTERN.test(parsed.specRevision)) {
        return resolution(base, "invalid", "Planning-readiness evidence has an invalid specification revision.", parsed);
    }
    if (parsed.specRevision !== specification.specRevision) {
        return resolution(base, "stale", "Planning-readiness evidence is bound to an earlier specification revision.", parsed);
    }
    if (parsed.recordedAt === undefined || !Number.isFinite(Date.parse(parsed.recordedAt)) ||
        parsed.recordedBy === undefined || parsed.recordedBy.trim() === "") {
        return resolution(base, "invalid", "Planning-readiness evidence is missing a valid disposition actor or time.", parsed);
    }
    if (parsed.planningReadiness === "revoked") {
        if (parsed.reason === undefined || parsed.reason.trim() === "") {
            return resolution(base, "invalid", "Revoked readiness evidence is missing reason, actor, or time.", parsed);
        }
        return resolution(base, "revoked", "Planning readiness was explicitly revoked.", parsed);
    }
    if (parsed.planningReadiness === "waived") {
        let waiver;
        try {
            waiver = (0, repository_1.validateWaiver)(parsed.waiver, specification.specId, false);
        }
        catch (error) {
            return resolution(base, "invalid", (0, repository_1.errorMessage)(error, "Planning-readiness waiver is invalid."), parsed);
        }
        if (waiver.status === "revoked") {
            return resolution(base, "revoked", "The planning-readiness waiver was revoked.", parsed);
        }
        const now = (0, repository_1.resolveNow)(options.now);
        if (waiver.status === "expired" ||
            (waiver.expires_at != null && Date.parse(waiver.expires_at) <= now.getTime())) {
            return resolution(base, "expired", "The planning-readiness waiver is expired.", parsed);
        }
        return resolution(base, "current-waived", "A current active planning-readiness waiver authorizes planning.", parsed);
    }
    if (parsed.planningReadiness === "ready") {
        return resolveReadyEvidence(specification, base, parsed);
    }
    return resolution(base, "unsupported", `Planning-readiness disposition '${parsed.planningReadiness}' is unsupported.`, parsed);
}
function resolveReadyEvidence(specification, base, parsed) {
    if (parsed.review === null) {
        return resolution(base, "invalid", "Ready evidence is missing its canonical review reference.", parsed);
    }
    if (!types_1.SUPPORTED_SPECIFICATION_REVIEW_PROTOCOL_VERSIONS.includes(parsed.review.protocolVersion)) {
        return resolution(base, "unsupported", `Review protocol version '${parsed.review.protocolVersion}' is unsupported.`, parsed);
    }
    const expectedRef = `reviews/${parsed.review.id}.json`;
    if (parsed.review.ref !== expectedRef) {
        return resolution(base, "invalid", "Ready evidence contains a noncanonical review reference.", parsed);
    }
    const reviewPath = (0, repository_1.posixJoin)(specification.specPath, "..", parsed.review.ref);
    let reviewInput;
    try {
        const target = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(specification.repositoryRoot, reviewPath);
        if (!target.exists || !node_fs_1.default.statSync(target.runtimePath).isFile()) {
            return resolution(base, "invalid", "The referenced canonical review JSON is missing.", parsed);
        }
        reviewInput = JSON.parse(node_fs_1.default.readFileSync(target.runtimePath, "utf8"));
    }
    catch (error) {
        return resolution(base, "invalid", (0, repository_1.errorMessage)(error, "The referenced canonical review JSON is unreadable."), parsed);
    }
    try {
        const normalized = (0, review_normalization_1.normalizeSpecificationReviewResultSync)(specification.repositoryRoot, specification.specPath, reviewInput, parsed.review.digest);
        if (normalized.result.review_id !== parsed.review.id ||
            normalized.result.protocol_version !== parsed.review.protocolVersion ||
            normalized.result.reviewed_at !== parsed.review.reviewedAt ||
            normalized.result.reviewed_by !== parsed.review.reviewedBy ||
            normalized.result.spec_revision !== parsed.specRevision ||
            normalized.result.planning_readiness_recommendation.status !== "ready" ||
            normalized.result.counts.unresolved !== 0) {
            return resolution(base, "invalid", "Canonical review evidence does not match its readiness reference.", parsed);
        }
    }
    catch (error) {
        if (error instanceof types_1.SpecificationReadinessError && error.code === "unsupported-protocol") {
            return resolution(base, "unsupported", error.message, parsed);
        }
        return resolution(base, "invalid", (0, repository_1.errorMessage)(error, "Canonical review evidence is invalid."), parsed);
    }
    return resolution(base, "current-ready", "A complete current review authorizes planning.", parsed);
}
function parseDispositionDocument(contents) {
    const parsed = (0, planning_document_utils_1.parseMarkdownFrontMatter)(contents);
    if (!parsed.hasFrontMatter)
        throw new Error("Planning-readiness evidence has no YAML front matter.");
    const top = (key, required = true) => {
        const values = readScalarFields(parsed.frontMatterLines, 0, key);
        if (values.length > 1)
            throw new Error(`Planning-readiness field '${key}' is duplicated.`);
        if (required && values.length === 0)
            throw new Error(`Planning-readiness field '${key}' is missing.`);
        return values[0];
    };
    const nested = (parent, key, required = true) => {
        const values = readNestedScalarFields(parsed.frontMatterLines, parent, key);
        if (values.length > 1)
            throw new Error(`Planning-readiness field '${parent}.${key}' is duplicated.`);
        if (required && values.length === 0)
            throw new Error(`Planning-readiness field '${parent}.${key}' is missing.`);
        return values[0];
    };
    const schemaVersion = Number(top("artifact_schema_version"));
    if (!Number.isInteger(schemaVersion))
        throw new Error("Planning-readiness schema version is invalid.");
    const hasReview = readScalarFields(parsed.frontMatterLines, 0, "review").length > 0;
    const hasWaiver = readScalarFields(parsed.frontMatterLines, 0, "waiver").length > 0;
    return {
        schemaVersion,
        artifactType: top("artifact_type"),
        specId: top("spec_id"),
        planningReadiness: top("planning_readiness"),
        specRevision: top("spec_revision"),
        recordedAt: top("recorded_at", false),
        recordedBy: top("recorded_by", false),
        reason: top("reason", false),
        review: hasReview ? {
            id: nested("review", "id"),
            ref: nested("review", "ref"),
            digest: nested("review", "digest"),
            protocolVersion: Number(nested("review", "protocol_version")),
            reviewedAt: nested("review", "reviewed_at"),
            reviewedBy: nested("review", "reviewed_by")
        } : null,
        waiver: hasWaiver ? {
            waiver_id: nested("waiver", "waiver_id"),
            waiver_type: nested("waiver", "waiver_type"),
            subject_id: nested("waiver", "subject_id"),
            attempt_id: nullableScalar(nested("waiver", "attempt_id")),
            reason: nested("waiver", "reason"),
            recorded_by: nested("waiver", "recorded_by"),
            recorded_at: nested("waiver", "recorded_at"),
            expires_at: nullableScalar(nested("waiver", "expires_at")),
            authority_reference: nullableScalar(nested("waiver", "authority_reference")),
            status: nested("waiver", "status")
        } : null
    };
}
function readScalarFields(lines, indent, key) {
    const prefix = " ".repeat(indent);
    const pattern = new RegExp(`^${prefix}${escapeRegExp(key)}:\\s*(.*)$`);
    return lines.flatMap((line) => {
        const match = line.match(pattern);
        return match === null ? [] : [parseYamlScalar(match[1])];
    });
}
function readNestedScalarFields(lines, parent, key) {
    const start = lines.findIndex((line) => line === `${parent}:`);
    if (start < 0)
        return [];
    const block = [];
    for (let index = start + 1; index < lines.length; index += 1) {
        if (/^[^\s]/.test(lines[index]))
            break;
        block.push(lines[index]);
    }
    return readScalarFields(block, 2, key);
}
function resolution(base, status, reason, parsed) {
    const disposition = parsed?.planningReadiness;
    return {
        ...base,
        status,
        eligible: status === "current-ready" || status === "current-waived",
        reason,
        recordedSpecRevision: parsed?.specRevision ?? null,
        planningReadiness: disposition === "ready" || disposition === "waived" || disposition === "revoked"
            ? disposition
            : null,
        reviewPath: parsed?.review === null || parsed?.review === undefined
            ? null
            : (0, repository_1.posixJoin)(base.specPath, "..", parsed.review.ref),
        waiverId: parsed?.waiver !== null && typeof parsed?.waiver === "object"
            ? String(parsed.waiver.waiver_id ?? "") || null
            : null
    };
}
function parseYamlScalar(raw) {
    const value = raw.trim();
    if (value.startsWith("\"") && value.endsWith("\"")) {
        try {
            const parsed = JSON.parse(value);
            if (typeof parsed === "string")
                return parsed;
        }
        catch {
            throw new Error("Planning-readiness evidence contains an invalid quoted scalar.");
        }
    }
    return value;
}
function nullableScalar(value) {
    return value === "null" ? null : value;
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
