"use strict";
/**
 * Pure helpers for the shim-owned write_neo4j_cypher path.
 * Extracted from index.ts so the executeWrite tx body, response synthesis,
 * and value serialization can be unit-tested without the shim's top-level
 * process bootstrap (which spawns uvx and opens stdin/stdout pipes).
 *
 * The shim's index.ts wires these up: rewriter → shared driver → session.
 * executeWrite(tx => runWriteTxBody(...)) → response synth → stdout.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrphanRollbackError = exports.ORPHAN_CHECK_CYPHER = void 0;
exports.serializeValue = serializeValue;
exports.runWriteTxBody = runWriteTxBody;
exports.synthesiseWriteResponse = synthesiseWriteResponse;
exports.synthesiseOrphanRollback = synthesiseOrphanRollback;
exports.synthesiseShimError = synthesiseShimError;
exports.ORPHAN_CHECK_CYPHER = "MATCH (n) WHERE n.createdBySession = $__autoSession " +
    "AND n.createdAt >= $__autoStartTimestamp " +
    "AND NOT (n)--() " +
    "RETURN collect(elementId(n)) AS orphanIds, " +
    "collect(distinct labels(n)) AS labelSets";
class OrphanRollbackError extends Error {
    orphanIds;
    sampleLabels;
    constructor(orphanIds, sampleLabels) {
        super(`orphan rollback: ${orphanIds.length} unattached node(s)`);
        this.orphanIds = orphanIds;
        this.sampleLabels = sampleLabels;
    }
}
exports.OrphanRollbackError = OrphanRollbackError;
function serializeValue(v) {
    if (v === null || v === undefined)
        return v;
    if (typeof v === "string" || typeof v === "boolean" || typeof v === "number") {
        return v;
    }
    if (Array.isArray(v))
        return v.map(serializeValue);
    if (typeof v === "object") {
        const obj = v;
        if ("low" in obj &&
            "high" in obj &&
            typeof obj.low === "number" &&
            typeof obj.high === "number" &&
            Object.keys(obj).length === 2) {
            if (obj.high === 0 || obj.high === -1)
                return obj.low;
            return String(obj);
        }
        if ("properties" in obj && "labels" in obj) {
            return {
                labels: obj.labels,
                elementId: obj.elementId,
                properties: serializeValue(obj.properties),
            };
        }
        if ("properties" in obj && "type" in obj && "elementId" in obj) {
            return {
                type: obj.type,
                elementId: obj.elementId,
                properties: serializeValue(obj.properties),
            };
        }
        if (obj.constructor &&
            obj.constructor.name &&
            obj.constructor.name !== "Object" &&
            obj.constructor.name !== "Array") {
            return String(obj);
        }
        const out = {};
        for (const [k, val] of Object.entries(obj)) {
            out[k] = serializeValue(val);
        }
        return out;
    }
    return v;
}
/**
 * Body of the executeWrite tx. Runs the rewritten cypher, captures counters,
 * runs the orphan check, throws OrphanRollbackError if any orphans remain
 * (executeWrite catches the throw and rolls back the entire tx).
 *
 * Caller is responsible for: (a) opening the session, (b) wrapping this in
 * `session.executeWrite(tx => runWriteTxBody(tx, ...))`, (c) calling close()
 * in finally, (d) translating the outcome / OrphanRollbackError into the
 * synthesised MCP response and audit log lines.
 */
async function runWriteTxBody(tx, rewrittenCypher, operatorParams, autoCtx) {
    const tsRes = await tx.run("RETURN datetime() AS now");
    const startTs = tsRes.records[0].get("now");
    const merged = {
        ...operatorParams,
        __autoStartTimestamp: startTs,
        __autoAgent: autoCtx.agent,
        __autoSession: autoCtx.session,
    };
    const writeRes = await tx.run(rewrittenCypher, merged);
    const counters = writeRes.summary.counters.updates();
    const nodesCreated = counters.nodesCreated;
    const relsCreated = counters.relationshipsCreated;
    const propertiesSet = counters.propertiesSet;
    const keys = writeRes.records.length > 0 ? writeRes.records[0].keys.map(String) : [];
    const serializedRecords = writeRes.records.map((rec) => {
        const out = {};
        for (const k of keys) {
            out[k] = serializeValue(rec.get(k));
        }
        return out;
    });
    const orphanRes = await tx.run(exports.ORPHAN_CHECK_CYPHER, merged);
    const orphanIds = orphanRes.records[0].get("orphanIds") ?? [];
    const labelSets = orphanRes.records[0].get("labelSets") ?? [];
    if (orphanIds.length > 0) {
        const flatLabels = Array.from(new Set(labelSets.flat()));
        throw new OrphanRollbackError(orphanIds, flatLabels);
    }
    return { nodesCreated, relsCreated, propertiesSet, serializedRecords };
}
function synthesiseWriteResponse(id, w) {
    const parts = [];
    if (w.records.length > 0) {
        parts.push(`${w.records.length} record${w.records.length === 1 ? "" : "s"} returned`);
    }
    parts.push(`${w.nodesCreated} nodes created`);
    parts.push(`${w.relsCreated} relationships created`);
    if (w.propertiesSet > 0)
        parts.push(`${w.propertiesSet} properties set`);
    if (w.records.length > 0) {
        parts.push("");
        parts.push(JSON.stringify(w.records, null, 2));
    }
    return JSON.stringify({
        jsonrpc: "2.0",
        id,
        result: {
            content: [{ type: "text", text: parts.join("\n") }],
        },
    });
}
function synthesiseOrphanRollback(id, orphanCount, sampleLabels) {
    const labelLine = sampleLabels.length > 0
        ? sampleLabels.slice(0, 5).join(", ")
        : "(unknown — orphans had no labels)";
    const text = `orphan rollback — ${orphanCount} node(s) created without an edge in the same transaction.\n` +
        `Sample labels: ${labelLine}.\n\n` +
        `The transaction was aborted; nothing was persisted. Anchor each new node to its semantic parent in the same statement (Graph Stewardship Doctrine Rule 1 in database-operator.md).`;
    return JSON.stringify({
        jsonrpc: "2.0",
        id,
        result: {
            content: [{ type: "text", text }],
            isError: true,
        },
    });
}
function synthesiseShimError(id, prefix, errMsg) {
    return JSON.stringify({
        jsonrpc: "2.0",
        id,
        result: {
            content: [{ type: "text", text: `${prefix}: ${errMsg}` }],
            isError: true,
        },
    });
}
//# sourceMappingURL=cypher-shim-write.js.map