"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TASK_WAIVER_STATUSES = exports.TASK_WAIVER_TYPES = void 0;
exports.taskWaiverAuthorityRequired = taskWaiverAuthorityRequired;
exports.readTaskWaivers = readTaskWaivers;
exports.evaluateTaskWaiver = evaluateTaskWaiver;
exports.findApplicableTaskWaiver = findApplicableTaskWaiver;
exports.previewTaskWaiverRecordSync = previewTaskWaiverRecordSync;
exports.previewTaskWaiverRevokeSync = previewTaskWaiverRevokeSync;
exports.applyTaskWaiverMutationSync = applyTaskWaiverMutationSync;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_util_1 = require("node:util");
const mutation_preconditions_1 = require("../filesystem/mutation-preconditions");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const paths_1 = require("../paths");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const planning_document_utils_1 = require("./planning-document-utils");
const task_lifecycle_1 = require("./task-lifecycle");
const task_lifecycle_timestamps_1 = require("./task-lifecycle-timestamps");
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
exports.TASK_WAIVER_TYPES = [
    "readiness", "validation", "review", "scope_deviation", "administrative"
];
exports.TASK_WAIVER_STATUSES = ["active", "expired", "revoked"];
const plans = new WeakMap();
function taskWaiverAuthorityRequired(type) {
    return type === "readiness" || type === "review" || type === "administrative";
}
function readTaskWaivers(artifactPath, summaryText) {
    const lines = (0, planning_document_utils_1.parseMarkdownFrontMatter)(summaryText).frontMatterLines;
    const waivers = [];
    for (const [index, line] of lines.entries()) {
        const topLevel = line.match(/^task_waivers:\s*(.*)$/);
        if (topLevel !== null) {
            for (const waiver of decodeWaiverCollection(topLevel[1], artifactPath, "task_waivers")) {
                if (waiver.waiverType !== "readiness" || waiver.attemptId !== null) {
                    throw new TypeError(`Readiness transition waiver '${waiver.waiverId}' has the wrong owner at '${artifactPath}'.`);
                }
                waivers.push(waiver);
            }
            continue;
        }
        const attemptOwned = line.match(/^ {4}waivers:\s*(.*)$/);
        if (attemptOwned === null)
            continue;
        const attemptId = owningAttemptId(lines, index);
        if (attemptId === null)
            throw new TypeError(`Attempt waiver evidence is malformed at '${artifactPath}'.`);
        for (const waiver of decodeWaiverCollection(attemptOwned[1], artifactPath, "waivers")) {
            if (waiver.waiverType === "readiness" || waiver.attemptId !== attemptId) {
                throw new TypeError(`Waiver '${waiver.waiverId}' does not match its attempt owner at '${artifactPath}'.`);
            }
            waivers.push(waiver);
        }
    }
    const identities = new Set();
    for (const waiver of waivers) {
        if (identities.has(waiver.waiverId)) {
            throw new TypeError(`Waiver identity '${waiver.waiverId}' is duplicated at '${artifactPath}'.`);
        }
        identities.add(waiver.waiverId);
    }
    return waivers;
}
function evaluateTaskWaiver(waiver, request) {
    const evaluatedAt = requireTimestamp(request.evaluatedAt, "Waiver evaluation time");
    const effectiveStatus = waiver.status === "active"
        && waiver.expiresAt !== null
        && Date.parse(waiver.expiresAt) <= Date.parse(evaluatedAt)
        ? "expired"
        : waiver.status;
    const expectedAttempt = request.attemptId ?? null;
    let reason = "Waiver matches subject, type, attempt, time, and authority requirements.";
    if (request.waiverId !== undefined && request.waiverId !== null && waiver.waiverId !== request.waiverId) {
        reason = "Waiver identity does not match.";
    }
    else if (waiver.waiverType !== request.waiverType) {
        reason = "Waiver type does not match.";
    }
    else if (waiver.subjectId !== request.subjectId) {
        reason = "Waiver subject does not match.";
    }
    else if (waiver.attemptId !== expectedAttempt) {
        reason = "Waiver attempt binding does not match.";
    }
    else if (effectiveStatus !== "active") {
        reason = `Waiver is ${effectiveStatus}.`;
    }
    else if ((request.authorityRequired === true || taskWaiverAuthorityRequired(request.waiverType))
        && waiver.authorityReference === null) {
        reason = "Waiver lacks the required authority reference.";
    }
    return { waiver: { ...waiver, status: effectiveStatus }, applicable: reason.startsWith("Waiver matches"), effectiveStatus, reason };
}
function findApplicableTaskWaiver(artifactPath, summaryText, request) {
    const candidates = readTaskWaivers(artifactPath, summaryText)
        .filter((waiver) => request.waiverId === undefined || request.waiverId === null || waiver.waiverId === request.waiverId)
        .map((waiver) => evaluateTaskWaiver(waiver, request));
    return candidates.find((entry) => entry.applicable) ?? candidates[0] ?? null;
}
function previewTaskWaiverRecordSync(request) {
    const pair = resolvePair(request.repositoryRoot, request.taskDefinitionPath);
    const taskText = readArtifact(pair.task.runtimePath, pair.task.repositoryRelativePath);
    const summaryText = readArtifact(pair.summary.runtimePath, pair.summary.repositoryRelativePath);
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: taskText, implementationSummary: summaryText });
    if (projection.blockingFindings.length > 0) {
        throw new TypeError(`Waiver recording requires a valid task pair: ${projection.blockingFindings.map((entry) => entry.message).join(" ")}`);
    }
    const taskId = projection.task.taskId;
    if (taskId === null || projection.task.status !== "draft") {
        throw new TypeError("Waiver recording requires a canonical draft task pair.");
    }
    const waiverType = requireToken(request.waiverType, exports.TASK_WAIVER_TYPES, "waiver type");
    const waiverId = requireIdentity(request.waiverId, "Waiver identity");
    const subjectId = requireIdentity(request.subjectId, "Waiver subject");
    const recordedAt = requireEventTimestamp(request.recordedAt, "recorded_at", "Waiver recorded time");
    const expiresAt = optionalTimestamp(request.expiresAt, "Waiver expiry time");
    if (expiresAt !== null && Date.parse(expiresAt) <= Date.parse(recordedAt)) {
        throw new TypeError("Waiver expiry must be later than its record time.");
    }
    const authorityReference = optionalText(request.authorityReference);
    if (taskWaiverAuthorityRequired(waiverType) && authorityReference === null) {
        throw new TypeError(`${waiverType} waiver recording requires an authority reference.`);
    }
    const attemptId = optionalIdentity(request.attemptId, "Waiver attempt identity");
    const current = projection.summary?.currentAttempt ?? null;
    if (waiverType === "readiness") {
        if (attemptId !== null || subjectId !== taskId || current !== null) {
            throw new TypeError("A readiness waiver is task-owned transition evidence recorded before an attempt opens.");
        }
    }
    else if (attemptId === null || current?.attemptId !== attemptId || current.closedAt !== null) {
        throw new TypeError("Non-readiness waivers must bind the open current attempt.");
    }
    if (attemptId !== null && current?.openedAt !== null && current?.openedAt !== undefined
        && Date.parse(recordedAt) < Date.parse(current.openedAt)) {
        throw new TypeError("Attempt-owned waiver evidence cannot precede its attempt opening.");
    }
    const existing = readTaskWaivers(pair.summary.repositoryRelativePath, summaryText);
    if (existing.some((entry) => entry.waiverId === waiverId)) {
        throw new TypeError(`Waiver identity '${waiverId}' already exists; waiver identities are append-only.`);
    }
    const waiver = {
        schemaVersion: 1,
        waiverId,
        waiverType,
        subjectId,
        attemptId,
        reason: requireText(request.reason, "Waiver reason"),
        recordedBy: requireText(request.recordedBy, "Waiver actor"),
        recordedAt,
        expiresAt,
        authorityReference,
        status: "active",
        revokedBy: null,
        revokedAt: null,
        revocationReason: null
    };
    const nextSummary = waiverType === "readiness"
        ? appendTopLevelWaiver(summaryText, waiver)
        : replaceAttemptWaivers(summaryText, attemptId, [...existing.filter((entry) => entry.attemptId === attemptId), waiver]);
    const nextTask = waiverType === "readiness"
        ? updateTopLevelFields(taskText, { readiness: "ready_to_implement", readiness_waiver_id: waiverId })
        : taskText;
    return planMutation(pair, taskText, summaryText, nextTask, nextSummary, "record", waiver);
}
function previewTaskWaiverRevokeSync(request) {
    const pair = resolvePair(request.repositoryRoot, request.taskDefinitionPath);
    const taskText = readArtifact(pair.task.runtimePath, pair.task.repositoryRelativePath);
    const summaryText = readArtifact(pair.summary.runtimePath, pair.summary.repositoryRelativePath);
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: taskText, implementationSummary: summaryText });
    if (projection.blockingFindings.length > 0 || projection.task.status !== "draft") {
        throw new TypeError("Waiver revocation requires a valid canonical draft task pair.");
    }
    const waiverId = requireIdentity(request.waiverId, "Waiver identity");
    const waivers = readTaskWaivers(pair.summary.repositoryRelativePath, summaryText);
    const existing = waivers.find((entry) => entry.waiverId === waiverId);
    if (existing === undefined)
        throw new TypeError(`Waiver '${waiverId}' does not exist.`);
    if (existing.status !== "active")
        throw new TypeError(`Waiver '${waiverId}' is already ${existing.status}.`);
    const revokedAt = requireEventTimestamp(request.revokedAt, "revoked_at", "Waiver revocation time");
    if (Date.parse(revokedAt) < Date.parse(existing.recordedAt)) {
        throw new TypeError("Waiver revocation cannot precede its record time.");
    }
    const revoked = {
        ...existing,
        status: "revoked",
        revokedBy: requireText(request.revokedBy, "Waiver revocation actor"),
        revokedAt,
        revocationReason: requireText(request.reason, "Waiver revocation reason")
    };
    let nextSummary;
    if (existing.attemptId === null) {
        nextSummary = replaceTopLevelWaivers(summaryText, waivers.filter((entry) => entry.attemptId === null).map((entry) => entry.waiverId === waiverId ? revoked : entry));
    }
    else {
        nextSummary = replaceAttemptWaivers(summaryText, existing.attemptId, waivers.filter((entry) => entry.attemptId === existing.attemptId).map((entry) => entry.waiverId === waiverId ? revoked : entry));
    }
    return planMutation(pair, taskText, summaryText, taskText, nextSummary, "revoke", revoked);
}
function applyTaskWaiverMutationSync(preview, options = {}) {
    const plan = plans.get(preview);
    if (plan === undefined)
        throw new TypeError("Waiver apply requires its original in-process preview.");
    (0, task_lifecycle_timestamps_1.revalidateObservedLifecycleTimestamp)(plan.lifecycleTimestamp.value, plan.lifecycleTimestamp.field, plan.lifecycleTimestamp.label);
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(plan.repositoryRoot, {
        writes: plan.writes,
        preconditions: plan.preconditions
    }, options.transaction);
    return {
        ...cloneResult(plan.snapshot),
        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 ?? (transaction.cleanupErrors.length > 0 ? transaction.cleanupErrors.join(" ") : null)
    };
}
function planMutation(pair, taskText, summaryText, nextTask, nextSummary, operation, waiver) {
    // Pair validation deliberately ignores waiver semantics; this service owns their strict shape.
    const proposed = (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: nextTask, implementationSummary: nextSummary, evaluatedAt: waiver.recordedAt });
    if (operation === "record" && proposed.blockingFindings.length > 0) {
        throw new TypeError(`Waiver mutation would violate task-pair invariants: ${proposed.blockingFindings.map((entry) => entry.message).join(" ")}`);
    }
    const preconditions = [pair.task, pair.summary].flatMap((target) => [
        (0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(target),
        (0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target)
    ]);
    const writes = [
        ...(nextTask === taskText ? [] : [{ path: pair.task.repositoryRelativePath, contents: nextTask }]),
        ...(nextSummary === summaryText ? [] : [{ path: pair.summary.repositoryRelativePath, contents: nextSummary }])
    ].sort((left, right) => left.path.localeCompare(right.path));
    const snapshot = {
        operation,
        taskDefinitionPath: pair.task.repositoryRelativePath,
        implementationSummaryPath: pair.summary.repositoryRelativePath,
        waiver,
        intendedPaths: writes.map((entry) => entry.path),
        changedPaths: [],
        expected: preconditions.map((entry) => ({
            path: entry.repositoryRelativePath,
            kind: entry.kind,
            expectedState: entry.kind === "content-revision" ? entry.expectedRevision : entry.expectedPresence
        })),
        writeOutcome: "previewed",
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null
    };
    plans.set(snapshot, {
        repositoryRoot: pair.repositoryRoot,
        writes,
        preconditions,
        snapshot: cloneResult(snapshot),
        lifecycleTimestamp: operation === "record"
            ? { value: waiver.recordedAt, field: "recorded_at", label: "Waiver recorded time" }
            : { value: waiver.revokedAt, field: "revoked_at", label: "Waiver revocation time" }
    });
    return snapshot;
}
function appendTopLevelWaiver(summaryText, waiver) {
    const existing = readTaskWaivers("implementation-summary.md", summaryText).filter((entry) => entry.attemptId === null);
    return replaceTopLevelWaivers(summaryText, [...existing, waiver]);
}
function replaceTopLevelWaivers(summaryText, waivers) {
    const newline = summaryText.includes("\r\n") ? "\r\n" : "\n";
    const lines = summaryText.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === "---");
    if (lines[0] !== "---" || closing < 0)
        throw new TypeError("Implementation summary has malformed front matter.");
    const rendered = `task_waivers: ${encodeWaiverCollection(waivers)}`;
    const current = lines.findIndex((line, index) => index > 0 && index < closing && /^task_waivers:/.test(line));
    if (current >= 0)
        lines[current] = rendered;
    else {
        const upstream = lines.findIndex((line, index) => index > 0 && index < closing && line === "upstream:");
        lines.splice(upstream >= 0 ? upstream : closing, 0, rendered);
    }
    return lines.join(newline);
}
function replaceAttemptWaivers(summaryText, attemptId, waivers) {
    const newline = summaryText.includes("\r\n") ? "\r\n" : "\n";
    const lines = summaryText.split(/\r?\n/);
    const range = locateAttempt(lines, attemptId);
    if (range === null)
        throw new TypeError(`Attempt '${attemptId}' is not materialized.`);
    const index = lines.findIndex((line, lineIndex) => lineIndex > range.start && lineIndex < range.end && /^ {4}waivers:/.test(line));
    if (index < 0)
        throw new TypeError(`Attempt '${attemptId}' has no waiver owner.`);
    lines[index] = `    waivers: ${encodeWaiverCollection(waivers)}`;
    return lines.join(newline);
}
function updateTopLevelFields(markdown, updates) {
    const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdown.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === "---");
    if (lines[0] !== "---" || closing < 0)
        throw new TypeError("Task definition has malformed front matter.");
    let insertion = lines.findIndex((line, index) => index > 0 && index < closing && line === "upstream:");
    if (insertion < 0)
        insertion = closing;
    for (const [field, value] of Object.entries(updates)) {
        const index = lines.findIndex((line, lineIndex) => lineIndex > 0 && lineIndex < closing && new RegExp(`^${field}:`).test(line));
        if (index >= 0)
            lines[index] = `${field}: ${value}`;
        else {
            lines.splice(insertion, 0, `${field}: ${JSON.stringify(value)}`);
            insertion += 1;
        }
    }
    return lines.join(newline);
}
function decodeWaiverCollection(raw, artifactPath, field) {
    if (raw.trim() === "[]")
        return [];
    try {
        const scalar = JSON.parse(raw.trim());
        if (typeof scalar !== "string")
            throw new TypeError("not a string scalar");
        const decoded = JSON.parse(scalar);
        if (!Array.isArray(decoded))
            throw new TypeError("not an array");
        return decoded.map((entry) => validateStoredWaiver(entry));
    }
    catch (error) {
        throw new TypeError(`Waiver evidence '${field}' is malformed at '${artifactPath}': ${error instanceof Error ? error.message : String(error)}`);
    }
}
function encodeWaiverCollection(waivers) {
    return JSON.stringify(JSON.stringify(waivers));
}
function validateStoredWaiver(value) {
    if (value === null || typeof value !== "object" || Array.isArray(value))
        throw new TypeError("waiver must be an object");
    const entry = value;
    const waiverType = requireToken(entry.waiverType, exports.TASK_WAIVER_TYPES, "stored waiver type");
    const status = requireToken(entry.status, exports.TASK_WAIVER_STATUSES, "stored waiver status");
    const waiver = {
        schemaVersion: entry.schemaVersion === 1 ? 1 : (() => { throw new TypeError("unsupported waiver schema"); })(),
        waiverId: requireIdentity(entry.waiverId, "Stored waiver identity"),
        waiverType,
        subjectId: requireIdentity(entry.subjectId, "Stored waiver subject"),
        attemptId: optionalIdentity(entry.attemptId, "Stored waiver attempt"),
        reason: requireText(entry.reason, "Stored waiver reason"),
        recordedBy: requireText(entry.recordedBy, "Stored waiver actor"),
        recordedAt: requireTimestamp(entry.recordedAt, "Stored waiver time"),
        expiresAt: optionalTimestamp(entry.expiresAt, "Stored waiver expiry"),
        authorityReference: optionalText(entry.authorityReference),
        status,
        revokedBy: optionalText(entry.revokedBy),
        revokedAt: optionalTimestamp(entry.revokedAt, "Stored waiver revocation time"),
        revocationReason: optionalText(entry.revocationReason)
    };
    if (taskWaiverAuthorityRequired(waiverType) && waiver.authorityReference === null)
        throw new TypeError("required authority is missing");
    if (status === "revoked" && (waiver.revokedBy === null || waiver.revokedAt === null || waiver.revocationReason === null)) {
        throw new TypeError("revoked waiver lacks audit evidence");
    }
    return waiver;
}
function owningAttemptId(lines, fieldIndex) {
    for (let index = fieldIndex - 1; index >= 0; index -= 1) {
        const match = lines[index].match(/^ {2}- attempt_id:\s*(.*)$/);
        if (match !== null)
            return (0, planning_document_utils_1.parseScalar)(match[1]);
        if (/^[^\s]/.test(lines[index]))
            return null;
    }
    return null;
}
function locateAttempt(lines, attemptId) {
    for (let index = 0; index < lines.length; index += 1) {
        const match = lines[index].match(/^ {2}- attempt_id:\s*(.*)$/);
        if (match === null)
            continue;
        let end = index + 1;
        while (end < lines.length && !/^ {2}-\s+/.test(lines[end]) && /^\s/.test(lines[end]))
            end += 1;
        if ((0, planning_document_utils_1.parseScalar)(match[1]) === attemptId)
            return { start: index, end };
        index = end - 1;
    }
    return null;
}
function resolvePair(repositoryRoot, taskDefinitionPath) {
    const root = node_fs_1.default.realpathSync(node_path_1.default.resolve(requireText(repositoryRoot, "Repository root")));
    const parsed = (0, paths_1.parseRepositoryRelativePath)(taskDefinitionPath);
    if (parsed.segments.at(-1) !== "task-definition.md")
        throw new TypeError("Task waiver paths must end in task-definition.md.");
    const task = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(root, parsed.normalizedPath);
    const summary = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(root, [...parsed.segments.slice(0, -1), "implementation-summary.md"].join("/"));
    if (!task.exists || !summary.exists)
        throw new TypeError("Task waiver mutation requires an existing canonical task pair.");
    return { repositoryRoot: root, task, summary };
}
function readArtifact(runtimePath, repositoryRelativePath) {
    const stats = node_fs_1.default.statSync(runtimePath, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isFile())
        throw new TypeError(`Waiver artifact '${repositoryRelativePath}' must be a regular file.`);
    return new node_util_1.TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(node_fs_1.default.readFileSync(runtimePath));
}
function requireIdentity(value, label) {
    const identity = requireText(value, label);
    if (!IDENTITY_PATTERN.test(identity))
        throw new TypeError(`${label} must use 3-128 machine-safe identity characters.`);
    return identity;
}
function optionalIdentity(value, label) {
    return value === undefined || value === null || value === "" ? null : requireIdentity(value, label);
}
function requireTimestamp(value, label) {
    const timestamp = requireText(value, label);
    if (!(0, task_lifecycle_1.isTaskLifecycleTimestamp)(timestamp))
        throw new TypeError(`${label} must be an ISO-8601 timestamp with explicit timezone.`);
    return timestamp;
}
function requireEventTimestamp(value, field, label) {
    return (0, task_lifecycle_timestamps_1.requireObservedLifecycleTimestamp)({
        value: requireTimestamp(value, label),
        field,
        label
    });
}
function optionalTimestamp(value, label) {
    return value === undefined || value === null || value === "" ? null : requireTimestamp(value, label);
}
function requireToken(value, values, label) {
    if (typeof value !== "string" || !values.includes(value))
        throw new TypeError(`Unsupported ${label}.`);
    return value;
}
function requireText(value, label) {
    if (typeof value !== "string" || value.trim() === "")
        throw new TypeError(`${label} must be non-empty.`);
    return value.trim();
}
function optionalText(value) {
    return value === undefined || value === null || value === "" ? null : requireText(value, "Optional waiver value");
}
function cloneResult(result) {
    return {
        ...result,
        waiver: { ...result.waiver },
        intendedPaths: [...result.intendedPaths],
        changedPaths: [...result.changedPaths],
        expected: result.expected.map((entry) => ({ ...entry })),
        conflicts: result.conflicts.map((entry) => ({ ...entry }))
    };
}
