"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemplateReconciliationTransactionError = exports.TEMPLATE_RECONCILIATION_DECISIONS = void 0;
exports.createTemplateReconciliationPlan = createTemplateReconciliationPlan;
exports.applyTemplateReconciliationPlanSync = applyTemplateReconciliationPlanSync;
const node_fs_1 = __importDefault(require("node:fs"));
const node_crypto_1 = require("node:crypto");
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 default_assets_1 = require("../workspace-init/default-assets");
const path_resolution_1 = require("./path-resolution");
exports.TEMPLATE_RECONCILIATION_DECISIONS = ["preserve", "replace"];
class TemplateReconciliationTransactionError extends Error {
    result;
    constructor(message, result) {
        super(message);
        this.name = "TemplateReconciliationTransactionError";
        this.result = result;
    }
}
exports.TemplateReconciliationTransactionError = TemplateReconciliationTransactionError;
function createTemplateReconciliationPlan(options) {
    const requestedTemplatePath = (0, paths_1.parseRepositoryRelativePath)(options.requestedTemplatePath).normalizedPath;
    const prefix = `${path_resolution_1.CANONICAL_TEMPLATE_ROOT}/`;
    if (!requestedTemplatePath.startsWith(prefix)) {
        throw new TypeError(`Template reconciliation accepts packaged workspace templates below '${path_resolution_1.CANONICAL_TEMPLATE_ROOT}/'.`);
    }
    const packaged = (0, default_assets_1.getDefaultSharedAuthoringAssets)().find((asset) => asset.path === requestedTemplatePath);
    if (packaged === undefined) {
        throw new TypeError(`Template reconciliation has no packaged source for '${requestedTemplatePath}'.`);
    }
    const target = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(options.repositoryRoot, requestedTemplatePath);
    if (target.repositoryRelativePath !== requestedTemplatePath) {
        throw new TypeError(`Template reconciliation target '${requestedTemplatePath}' resolves through an alias or symbolic link.`);
    }
    let workspaceContents = null;
    if (target.exists) {
        const stats = node_fs_1.default.lstatSync(target.runtimePath);
        if (stats.isSymbolicLink() || !stats.isFile()) {
            throw new TypeError(`Template reconciliation target '${requestedTemplatePath}' must be a regular non-symbolic file.`);
        }
        workspaceContents = node_fs_1.default.readFileSync(target.runtimePath);
    }
    const workspaceSha256 = workspaceContents === null ? null : hashContents(workspaceContents);
    const packagedSha256 = hashContents(packaged.contents);
    const state = workspaceSha256 === null
        ? "missing"
        : workspaceSha256 === packagedSha256
            ? "current"
            : "workspace-drift";
    const replacementNeeded = state !== "current";
    const allowedDecisions = replacementNeeded
        ? ["preserve", "replace"]
        : ["preserve"];
    const previewHash = hashEvidence({
        schemaVersion: 1,
        requestedTemplatePath,
        templatePath: target.repositoryRelativePath,
        packagedSourcePath: packaged.path,
        workspaceSha256,
        packagedSha256,
        state,
        replacementNeeded
    });
    return {
        requestedTemplatePath,
        templatePath: target.repositoryRelativePath,
        packagedSourcePath: packaged.path,
        classification: {
            state,
            replacementNeeded,
            workspaceSha256,
            packagedSha256
        },
        allowedDecisions,
        previewHash,
        writeApplied: false
    };
}
function applyTemplateReconciliationPlanSync(options) {
    assertDecision(options.decision);
    if (!/^[a-f0-9]{64}$/u.test(options.previewHash)) {
        throw new TypeError("Template reconciliation preview hash must be 64 lowercase hexadecimal characters.");
    }
    const plan = createTemplateReconciliationPlan(options);
    if (plan.previewHash !== options.previewHash) {
        throw new Error("Template reconciliation preview is stale; rerun preview before apply.");
    }
    if (!plan.allowedDecisions.includes(options.decision)) {
        throw new Error(`Template reconciliation decision '${options.decision}' is not allowed with the current evidence.`);
    }
    if (options.decision === "preserve") {
        return {
            plan,
            decision: options.decision,
            writeApplied: false,
            updatedPaths: [],
            transaction: null
        };
    }
    const packaged = (0, default_assets_1.getDefaultSharedAuthoringAssets)().find((asset) => asset.path === plan.packagedSourcePath);
    if (packaged === undefined
        || hashContents(packaged.contents) !== plan.classification.packagedSha256) {
        throw new Error("Template reconciliation packaged source changed after preview; rerun preview before apply.");
    }
    const target = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(options.repositoryRoot, plan.templatePath);
    const preconditions = [
        (0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(target)
    ];
    let mode = 0o644;
    if (target.exists) {
        const stats = node_fs_1.default.lstatSync(target.runtimePath);
        if (stats.isSymbolicLink() || !stats.isFile()) {
            throw new Error(`Template reconciliation target '${plan.templatePath}' changed type after preview.`);
        }
        preconditions.push((0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target));
        mode = stats.mode & 0o777;
    }
    const revalidatedPlan = createTemplateReconciliationPlan(options);
    if (revalidatedPlan.previewHash !== options.previewHash) {
        throw new Error("Template reconciliation preview became stale before apply; no files were written.");
    }
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(options.repositoryRoot, {
        transactionId: `template-reconcile-${plan.previewHash.slice(0, 24)}`,
        writes: [{ path: plan.templatePath, contents: packaged.contents, mode }],
        preconditions
    });
    if (transaction.outcome !== "committed") {
        throw new TemplateReconciliationTransactionError(transaction.outcome === "conflict"
            ? "Template reconciliation evidence changed before commit; rerun preview."
            : "Template reconciliation did not complete its selected template write.", transaction);
    }
    return {
        plan,
        decision: options.decision,
        writeApplied: true,
        updatedPaths: [plan.templatePath],
        transaction
    };
}
function hashContents(contents) {
    return (0, node_crypto_1.createHash)("sha256").update(contents).digest("hex");
}
function hashEvidence(value) {
    return hashContents(JSON.stringify(value));
}
function assertDecision(decision) {
    if (!exports.TEMPLATE_RECONCILIATION_DECISIONS.includes(decision)) {
        throw new TypeError(`Unsupported template reconciliation decision '${decision}'.`);
    }
}
