"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveRegisteredSpecification = resolveRegisteredSpecification;
exports.validateWaiver = validateWaiver;
exports.requireNonEmpty = requireNonEmpty;
exports.optionalNonEmpty = optionalNonEmpty;
exports.requireTimestamp = requireTimestamp;
exports.resolveNow = resolveNow;
exports.posixJoin = posixJoin;
exports.comparePosix = comparePosix;
exports.sha256 = sha256;
exports.errorMessage = errorMessage;
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 canonical_write_target_1 = require("../paths/canonical-write-target");
const spec_registry_1 = require("../spec-registry");
const path_resolution_1 = require("../spec-registry/path-resolution");
const types_1 = require("./types");
const WAIVER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
function resolveRegisteredSpecification(repositoryRoot, requestedPath) {
    const root = requireRepositoryRoot(repositoryRoot);
    const specPath = (0, paths_1.parseRepositoryRelativePath)(requireNonEmpty(requestedPath, "Specification path")).normalizedPath;
    let registry;
    try {
        registry = (0, spec_registry_1.parseSpecIndexDocumentText)(node_fs_1.default.readFileSync((0, path_resolution_1.resolveSpecIndexAbsolutePath)(root), "utf8"));
    }
    catch (error) {
        throw new types_1.SpecificationReadinessError("invalid-input", errorMessage(error, "Specification registry is unreadable."));
    }
    const entries = registry.entries.filter((entry) => normalizePath(entry.spec_path) === specPath);
    if (entries.length !== 1) {
        throw new types_1.SpecificationReadinessError("spec-unregistered", entries.length === 0
            ? `Specification '${specPath}' is not canonically registered.`
            : `Specification '${specPath}' has ambiguous registry identity.`);
    }
    const specTarget = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(root, specPath);
    if (!specTarget.exists || !node_fs_1.default.statSync(specTarget.runtimePath).isFile()) {
        throw new types_1.SpecificationReadinessError("invalid-input", `Specification '${specPath}' is not a readable file.`);
    }
    return {
        repositoryRoot: root,
        specId: entries[0].id,
        specPath,
        specRevision: sha256(node_fs_1.default.readFileSync(specTarget.runtimePath)),
        specTarget,
        dispositionTarget: (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(root, posixJoin(node_path_1.default.posix.dirname(specPath), "planning-readiness.md"))
    };
}
function validateWaiver(input, specId, requireActive) {
    if (input === null || typeof input !== "object" || Array.isArray(input)) {
        throw new types_1.SpecificationReadinessError("waiver-invalid", "A complete planning-readiness waiver is required.");
    }
    const candidate = input;
    const waiverId = requireNonEmpty(candidate.waiver_id, "Waiver id", "waiver-invalid");
    if (!WAIVER_ID_PATTERN.test(waiverId)) {
        throw new types_1.SpecificationReadinessError("waiver-invalid", "Waiver id must be stable and contain only letters, numbers, dots, underscores, or hyphens.");
    }
    if (candidate.waiver_type !== "planning-readiness" || candidate.subject_id !== specId) {
        throw new types_1.SpecificationReadinessError("waiver-invalid", "Waiver type or subject does not match planning readiness for the selected specification.");
    }
    const status = candidate.status;
    if (status !== "active" && status !== "expired" && status !== "revoked") {
        throw new types_1.SpecificationReadinessError("waiver-invalid", "Waiver status must be active, expired, or revoked.");
    }
    if (requireActive && status !== "active") {
        throw new types_1.SpecificationReadinessError("waiver-invalid", "A new waived disposition requires an active waiver.");
    }
    const recordedAt = requireTimestamp(candidate.recorded_at, "Waiver time", "waiver-invalid");
    const expiresAt = optionalTimestamp(candidate.expires_at, "Waiver expiry", "waiver-invalid");
    if (requireActive && expiresAt !== null && Date.parse(expiresAt) <= Date.parse(recordedAt)) {
        throw new types_1.SpecificationReadinessError("waiver-invalid", "An active waiver expiry must be later than its recorded time.");
    }
    return {
        waiver_id: waiverId,
        waiver_type: "planning-readiness",
        subject_id: specId,
        attempt_id: optionalNullable(candidate.attempt_id, "Waiver attempt id", "waiver-invalid"),
        reason: requireNonEmpty(candidate.reason, "Waiver reason", "waiver-invalid"),
        recorded_by: requireNonEmpty(candidate.recorded_by, "Waiver actor", "waiver-invalid"),
        recorded_at: recordedAt,
        expires_at: expiresAt,
        authority_reference: optionalNullable(candidate.authority_reference, "Waiver authority reference", "waiver-invalid"),
        status
    };
}
function requireNonEmpty(value, label, code = "invalid-input") {
    if (typeof value !== "string" || value.trim() === "") {
        throw new types_1.SpecificationReadinessError(code, `${label} must be a non-empty string.`);
    }
    return value.trim();
}
function optionalNonEmpty(value, label) {
    return value === undefined ? undefined : requireNonEmpty(value, label);
}
function requireTimestamp(value, label, code = "invalid-input") {
    const timestamp = requireNonEmpty(value, label, code);
    const parsed = Date.parse(timestamp);
    if (!Number.isFinite(parsed))
        throw new types_1.SpecificationReadinessError(code, `${label} must be a valid ISO-8601 timestamp.`);
    return new Date(parsed).toISOString();
}
function resolveNow(value) {
    if (value === undefined)
        return new Date();
    const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
    if (!Number.isFinite(date.getTime())) {
        throw new types_1.SpecificationReadinessError("invalid-input", "Readiness resolution time must be valid.");
    }
    return date;
}
function posixJoin(...segments) {
    return node_path_1.default.posix.join(...segments);
}
function comparePosix(left, right) {
    return Buffer.from(left).compare(Buffer.from(right));
}
function sha256(contents) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(contents).digest("hex")}`;
}
function errorMessage(error, fallback) {
    return error instanceof Error && error.message.trim() !== "" ? error.message : fallback;
}
function requireRepositoryRoot(value) {
    const root = requireNonEmpty(value, "Repository root");
    const stats = node_fs_1.default.statSync(root, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isDirectory()) {
        throw new types_1.SpecificationReadinessError("invalid-input", "Repository root must be an existing directory.");
    }
    return node_fs_1.default.realpathSync(root);
}
function optionalTimestamp(value, label, code) {
    return value === undefined || value === null ? null : requireTimestamp(value, label, code);
}
function optionalNullable(value, label, code) {
    return value === undefined || value === null ? null : requireNonEmpty(value, label, code);
}
function normalizePath(value) {
    try {
        return (0, paths_1.parseRepositoryRelativePath)(value).normalizedPath;
    }
    catch {
        return value.replace(/\\/g, "/");
    }
}
