"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readSpecRegistrySnapshotSync = readSpecRegistrySnapshotSync;
exports.mutateSpecRegistrySync = mutateSpecRegistrySync;
exports.commitSpecRegistryDocumentSync = commitSpecRegistryDocumentSync;
exports.planSpecRegistryDocumentWriteSync = planSpecRegistryDocumentWriteSync;
exports.recoverSpecRegistryTransactionSync = recoverSpecRegistryTransactionSync;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_crypto_1 = require("node:crypto");
const cli_constants_1 = require("../config/cli-constants");
const atomic_write_1 = require("../filesystem/atomic-write");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const document_io_1 = require("./document-io");
function readSpecRegistrySnapshotSync(repositoryRoot) {
    const registryPath = resolveRegistryPath(repositoryRoot);
    const source = node_fs_1.default.readFileSync(registryPath);
    const sourceText = source.toString("utf8");
    return (0, document_io_1.buildSpecIndexDocumentSnapshot)({
        path: cli_constants_1.CLI_SPEC_INDEX_PATH,
        revision: sha256(source),
        sourceText
    });
}
function mutateSpecRegistrySync(repositoryRoot, mutate, options = {}) {
    if (typeof mutate !== "function") {
        throw new TypeError("Spec registry mutation requires a document mutator.");
    }
    const snapshot = readSpecRegistrySnapshotSync(repositoryRoot);
    return commitSpecRegistryDocumentSync(repositoryRoot, {
        expectedRevision: snapshot.revision,
        document: mutate(snapshot.document)
    }, options);
}
function commitSpecRegistryDocumentSync(repositoryRoot, request, options = {}) {
    const plan = planSpecRegistryDocumentWriteSync(repositoryRoot, request);
    const registryPath = resolveRegistryPath(repositoryRoot);
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(repositoryRoot, {
        transactionId: request.transactionId,
        writes: [plan.write],
        preconditions: plan.preconditions
    }, options);
    const observedRevision = observeRegistryRevision(registryPath);
    const retainedRecoveryPath = transactionEvidenceExists(repositoryRoot, transaction.transactionId)
        ? transaction.transactionPath
        : null;
    if (transaction.outcome === "committed") {
        requireValidRegistryRevision(registryPath, plan.intendedRevision);
        return mutationResult({
            outcome: retainedRecoveryPath === null ? "committed" : "incomplete",
            transactionId: transaction.transactionId,
            expectedRevision: plan.expectedRevision,
            intendedRevision: plan.intendedRevision,
            observedRevision,
            changedPaths: [cli_constants_1.CLI_SPEC_INDEX_PATH],
            recoveryPath: retainedRecoveryPath,
            failure: transaction.cleanupErrors[0] ?? null
        });
    }
    if (transaction.outcome === "conflict") {
        const stale = transaction.conflicts.find((candidate) => candidate.path === cli_constants_1.CLI_SPEC_INDEX_PATH
            && candidate.expectationKind === "content-revision");
        return mutationResult({
            outcome: "conflict",
            transactionId: transaction.transactionId,
            expectedRevision: plan.expectedRevision,
            intendedRevision: plan.intendedRevision,
            observedRevision,
            conflict: createConflict(stale === undefined ? "write-lock" : "stale-revision", plan.expectedRevision, observedRevision, stale?.guidance ?? transaction.failure ?? "The registry write lock is not available."),
            failure: transaction.failure
        });
    }
    const outcome = transaction.outcome === "incomplete" || retainedRecoveryPath !== null
        ? "incomplete"
        : "no-write";
    return mutationResult({
        outcome,
        transactionId: transaction.transactionId,
        expectedRevision: plan.expectedRevision,
        intendedRevision: plan.intendedRevision,
        observedRevision,
        recoveryPath: retainedRecoveryPath,
        failure: transaction.failure ?? transaction.cleanupErrors[0] ?? null
    });
}
function planSpecRegistryDocumentWriteSync(repositoryRoot, request) {
    validateRevision(request.expectedRevision, "Expected spec registry revision");
    const registryPath = resolveRegistryPath(repositoryRoot);
    const contents = (0, document_io_1.renderValidatedSpecIndexDocument)(request.document);
    return {
        expectedRevision: request.expectedRevision,
        intendedRevision: sha256(Buffer.from(contents)),
        write: {
            path: cli_constants_1.CLI_SPEC_INDEX_PATH,
            contents
        },
        preconditions: [
            {
                kind: "destination-presence",
                repositoryRelativePath: cli_constants_1.CLI_SPEC_INDEX_PATH,
                runtimePath: registryPath,
                expectedPresence: "present"
            },
            {
                kind: "content-revision",
                repositoryRelativePath: cli_constants_1.CLI_SPEC_INDEX_PATH,
                runtimePath: registryPath,
                expectedRevision: request.expectedRevision
            }
        ]
    };
}
function recoverSpecRegistryTransactionSync(repositoryRoot, request, options = {}) {
    validateTransactionId(request.transactionId);
    validateRevision(request.expectedRevision, "Expected pre-transaction registry revision");
    validateRevision(request.intendedRevision, "Expected intended registry revision");
    const registryPath = resolveRegistryPath(repositoryRoot);
    let lock;
    try {
        lock = (0, repository_transaction_1.acquireRepositoryWriteLockSync)(repositoryRoot, request.transactionId, options);
    }
    catch (error) {
        if (!(error instanceof repository_transaction_1.RepositoryWriteLockConflictError))
            throw error;
        const observedRevision = observeRegistryRevision(registryPath);
        return recoveryResult({
            outcome: "conflict",
            request,
            observedRevision,
            conflict: createConflict("write-lock", request.expectedRevision, observedRevision, error.message),
            recoveryPath: transactionEvidenceExists(repositoryRoot, request.transactionId)
                ? `.day-shift/state/transactions/${request.transactionId}`
                : null,
            failure: error.message
        });
    }
    let outcome = "incomplete";
    let observedRevision = observeRegistryRevision(registryPath);
    let changedPaths = [];
    let conflict = null;
    let failure = null;
    let recoveryPath = `.day-shift/state/transactions/${request.transactionId}`;
    const transactionDirectory = (0, repository_transaction_1.repositoryTransactionDirectory)(repositoryRoot, request.transactionId);
    try {
        const evidence = readRecoveryEvidence(transactionDirectory, request.transactionId);
        if (evidence === null) {
            if (observedRevision === request.intendedRevision) {
                requireValidRegistryRevision(registryPath, request.intendedRevision);
                outcome = "recovered";
                recoveryPath = null;
            }
            else {
                outcome = "conflict";
                recoveryPath = null;
                conflict = createConflict("recovery-evidence-mismatch", request.intendedRevision, observedRevision, "Registry recovery evidence is absent and the intended revision is not committed.");
            }
        }
        else {
            const entry = selectRegistryRecoveryEntry(evidence.manifest, request);
            if (observedRevision === request.intendedRevision) {
                requireValidRegistryRevision(registryPath, request.intendedRevision);
            }
            else if (observedRevision === request.expectedRevision) {
                const stagedPath = resolveStagedRegistryPath(transactionDirectory, entry.stagedPath);
                const stagedContents = node_fs_1.default.readFileSync(stagedPath);
                if (sha256(stagedContents) !== request.intendedRevision) {
                    throw new RegistryRecoveryEvidenceError("Retained registry staging bytes do not match the intended revision.");
                }
                (0, document_io_1.parseSpecIndexDocumentText)(stagedContents.toString("utf8"));
                (0, atomic_write_1.atomicWriteFileSync)(registryPath, stagedContents, { mode: entry.mode });
                requireValidRegistryRevision(registryPath, request.intendedRevision);
                observedRevision = request.intendedRevision;
                changedPaths = [cli_constants_1.CLI_SPEC_INDEX_PATH];
            }
            else {
                outcome = "conflict";
                conflict = createConflict("diverged-registry", request.expectedRevision, observedRevision, "The registry changed after the interrupted transaction; recovery preserved the current registry.");
            }
            if (outcome !== "conflict") {
                node_fs_1.default.rmSync(transactionDirectory, { recursive: true, force: true });
                outcome = "recovered";
                recoveryPath = null;
            }
        }
    }
    catch (error) {
        if (error instanceof RegistryRecoveryEvidenceError) {
            outcome = "conflict";
            conflict = createConflict("recovery-evidence-mismatch", request.intendedRevision, observedRevision, error.message);
        }
        else {
            outcome = "incomplete";
            failure = error instanceof Error ? error.message : "Spec registry recovery failed.";
        }
    }
    try {
        (0, repository_transaction_1.releaseRepositoryWriteLockSync)(lock);
    }
    catch (error) {
        outcome = "incomplete";
        recoveryPath = transactionEvidenceExists(repositoryRoot, request.transactionId)
            ? `.day-shift/state/transactions/${request.transactionId}`
            : null;
        failure = error instanceof Error ? error.message : "Spec registry recovery lock release failed.";
    }
    return recoveryResult({
        outcome,
        request,
        observedRevision,
        changedPaths,
        conflict,
        recoveryPath,
        failure
    });
}
class RegistryRecoveryEvidenceError extends Error {
}
function readRecoveryEvidence(transactionDirectory, transactionId) {
    if (!node_fs_1.default.statSync(transactionDirectory, { throwIfNoEntry: false })?.isDirectory())
        return null;
    try {
        const manifest = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(transactionDirectory, "manifest.json"), "utf8"));
        const status = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(transactionDirectory, "status"), "utf8"));
        if (manifest.schemaVersion !== 1
            || manifest.transactionId !== transactionId
            || manifest.transactionKind !== "generic"
            || manifest.durableRecordPath !== null
            || status.schemaVersion !== 1
            || status.transactionId !== transactionId
            || !["staged", "applying", "committed", "no-write", "incomplete"].includes(status.state)) {
            throw new RegistryRecoveryEvidenceError("Retained registry transaction identity is invalid.");
        }
        return { manifest, status };
    }
    catch (error) {
        if (error instanceof RegistryRecoveryEvidenceError)
            throw error;
        throw new RegistryRecoveryEvidenceError("Retained registry transaction evidence is unreadable.");
    }
}
function selectRegistryRecoveryEntry(manifest, request) {
    const entry = manifest.entries[0];
    if (manifest.entries.length !== 1
        || entry?.operation !== "write"
        || entry.path !== cli_constants_1.CLI_SPEC_INDEX_PATH
        || entry.beforeRevision !== request.expectedRevision
        || entry.intendedRevision !== request.intendedRevision) {
        throw new RegistryRecoveryEvidenceError("Retained registry transaction revisions do not match the recovery request.");
    }
    return entry;
}
function resolveStagedRegistryPath(transactionDirectory, stagedPath) {
    if (!/^staged\/\d{6}$/.test(stagedPath)) {
        throw new RegistryRecoveryEvidenceError("Retained registry staging path is invalid.");
    }
    return node_path_1.default.join(transactionDirectory, ...stagedPath.split("/"));
}
function resolveRegistryPath(repositoryRoot) {
    if (typeof repositoryRoot !== "string" || repositoryRoot.trim() === "") {
        throw new TypeError("A non-empty repository root is required.");
    }
    const resolvedRoot = node_fs_1.default.realpathSync(node_path_1.default.resolve(repositoryRoot));
    const marker = node_path_1.default.join(resolvedRoot, ".day-shift", "config.toml");
    if (!node_fs_1.default.statSync(marker, { throwIfNoEntry: false })?.isFile()) {
        throw new Error(`Repository root '${repositoryRoot}' has no .day-shift/config.toml marker.`);
    }
    return node_path_1.default.join(resolvedRoot, ...cli_constants_1.CLI_SPEC_INDEX_PATH.split("/"));
}
function observeRegistryRevision(registryPath) {
    const stats = node_fs_1.default.statSync(registryPath, { throwIfNoEntry: false });
    return stats?.isFile() ? sha256(node_fs_1.default.readFileSync(registryPath)) : null;
}
function requireValidRegistryRevision(registryPath, expectedRevision) {
    const contents = node_fs_1.default.readFileSync(registryPath);
    if (sha256(contents) !== expectedRevision) {
        throw new Error("Committed spec registry revision does not match its intended content.");
    }
    (0, document_io_1.parseSpecIndexDocumentText)(contents.toString("utf8"));
}
function transactionEvidenceExists(repositoryRoot, transactionId) {
    return node_fs_1.default.statSync((0, repository_transaction_1.repositoryTransactionDirectory)(repositoryRoot, transactionId), { throwIfNoEntry: false })?.isDirectory() ?? false;
}
function mutationResult(input) {
    return {
        outcome: input.outcome,
        transactionId: input.transactionId,
        expectedRevision: input.expectedRevision,
        intendedRevision: input.intendedRevision,
        observedRevision: input.observedRevision,
        changedPaths: input.changedPaths ?? [],
        conflict: input.conflict ?? null,
        recoveryPath: input.recoveryPath ?? null,
        failure: input.failure ?? null
    };
}
function recoveryResult(input) {
    return {
        outcome: input.outcome,
        transactionId: input.request.transactionId,
        expectedRevision: input.request.expectedRevision,
        intendedRevision: input.request.intendedRevision,
        observedRevision: input.observedRevision,
        changedPaths: input.changedPaths ?? [],
        conflict: input.conflict ?? null,
        recoveryPath: input.recoveryPath ?? null,
        failure: input.failure ?? null
    };
}
function createConflict(reason, expectedRevision, observedRevision, message) {
    return {
        reason,
        path: cli_constants_1.CLI_SPEC_INDEX_PATH,
        expectedRevision,
        observedRevision,
        refreshRequired: true,
        message
    };
}
function validateRevision(value, label) {
    if (!/^[a-f0-9]{64}$/.test(value)) {
        throw new TypeError(`${label} must be a SHA-256 content revision.`);
    }
}
function validateTransactionId(value) {
    if (!/^[A-Za-z0-9][A-Za-z0-9._-]{2,127}$/.test(value)) {
        throw new TypeError("Spec registry transaction identity must be a safe repository-local name.");
    }
}
function sha256(contents) {
    return (0, node_crypto_1.createHash)("sha256").update(contents).digest("hex");
}
