"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Patch = void 0;
const tslib_1 = require("tslib");
const operations = tslib_1.__importStar(require("./operations"));
const clock_1 = require("./clock");
const printTree_1 = require("tree-dump/lib/printTree");
const binary_1 = require("./codec/binary");
/**
 * Represents a JSON CRDT patch.
 *
 * Normally, you would create a new patch using the {@link PatchBuilder} class.
 *
 * ```ts
 * import {Patch, PatchBuilder, LogicalClock} from 'json-joy/lib/json-crdt-patch';
 *
 * const clock = new LogicalClock(3, 100);
 * const builder = new PatchBuilder(clock);
 * const patch = builder.flush();
 * ```
 *
 * Save patch to a binary representation:
 *
 * ```ts
 * const binary = patch.toBinary();
 * ```
 *
 * Load patch from a binary representation:
 *
 * ```ts
 * const patch = Patch.fromBinary(binary);
 * ```
 *
 * @category Patch
 */
class Patch {
    constructor() {
        /**
         * A list of operations in the patch.
         */
        this.ops = [];
        /**
         * Arbitrary metadata associated with the patch, which is not used by the
         * library.
         */
        this.meta = undefined;
    }
    /**
     * Un-marshals a JSON CRDT patch from a binary representation.
     */
    static fromBinary(data) {
        return (0, binary_1.decode)(data);
    }
    /**
     * Returns the patch ID, which is equal to the ID of the first operation
     * in the patch.
     *
     * @returns The ID of the first operation in the patch.
     */
    getId() {
        const op = this.ops[0];
        if (!op)
            return undefined;
        return op.id;
    }
    /**
     * Returns the total time span of the patch, which is the sum of all
     * operation spans.
     *
     * @returns The length of the patch.
     */
    span() {
        let span = 0;
        for (const op of this.ops)
            span += op.span();
        return span;
    }
    /**
     * Returns the expected time of the next inserted operation.
     */
    nextTime() {
        const ops = this.ops;
        const length = ops.length;
        if (!length)
            return 0;
        const lastOp = ops[length - 1];
        return lastOp.id.time + lastOp.span();
    }
    /**
     * Creates a new patch where all timestamps are transformed using the
     * provided function.
     *
     * @param ts Timestamp transformation function.
     * @returns A new patch with transformed timestamps.
     */
    rewriteTime(ts) {
        const patch = new Patch();
        const ops = this.ops;
        const length = ops.length;
        const patchOps = patch.ops;
        for (let i = 0; i < length; i++) {
            const op = ops[i];
            if (op instanceof operations.DelOp)
                patchOps.push(new operations.DelOp(ts(op.id), ts(op.obj), op.what));
            else if (op instanceof operations.NewConOp)
                patchOps.push(new operations.NewConOp(ts(op.id), op.val instanceof clock_1.Timestamp ? ts(op.val) : op.val));
            else if (op instanceof operations.NewVecOp)
                patchOps.push(new operations.NewVecOp(ts(op.id)));
            else if (op instanceof operations.NewValOp)
                patchOps.push(new operations.NewValOp(ts(op.id)));
            else if (op instanceof operations.NewObjOp)
                patchOps.push(new operations.NewObjOp(ts(op.id)));
            else if (op instanceof operations.NewStrOp)
                patchOps.push(new operations.NewStrOp(ts(op.id)));
            else if (op instanceof operations.NewBinOp)
                patchOps.push(new operations.NewBinOp(ts(op.id)));
            else if (op instanceof operations.NewArrOp)
                patchOps.push(new operations.NewArrOp(ts(op.id)));
            else if (op instanceof operations.InsArrOp)
                patchOps.push(new operations.InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts)));
            else if (op instanceof operations.UpdArrOp)
                patchOps.push(new operations.UpdArrOp(ts(op.id), ts(op.obj), ts(op.ref), ts(op.val)));
            else if (op instanceof operations.InsStrOp)
                patchOps.push(new operations.InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data));
            else if (op instanceof operations.InsBinOp)
                patchOps.push(new operations.InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data));
            else if (op instanceof operations.InsValOp)
                patchOps.push(new operations.InsValOp(ts(op.id), ts(op.obj), ts(op.val)));
            else if (op instanceof operations.InsObjOp)
                patchOps.push(new operations.InsObjOp(ts(op.id), ts(op.obj), op.data.map(([key, value]) => [key, ts(value)])));
            else if (op instanceof operations.InsVecOp)
                patchOps.push(new operations.InsVecOp(ts(op.id), ts(op.obj), op.data.map(([key, value]) => [key, ts(value)])));
            else if (op instanceof operations.NopOp)
                patchOps.push(new operations.NopOp(ts(op.id), op.len));
        }
        return patch;
    }
    /**
     * The `.rebase()` operation is meant to be applied to patches which have not
     * yet been advertised to the server (other peers), or when
     * the server clock is used and concurrent change on the server happened.
     *
     * The .rebase() operation returns a new `Patch` with the IDs recalculated
     * such that the first operation has the `time` equal to `newTime`.
     *
     * @param newTime Time where the patch ID should begin (ID of the first operation).
     * @param transformAfter Time after (and including) which the IDs should be
     *     transformed. If not specified, equals to the time of the first operation.
     */
    rebase(newTime, transformAfter) {
        const id = this.getId();
        if (!id)
            throw new Error('EMPTY_PATCH');
        const sid = id.sid;
        const patchStartTime = id.time;
        transformAfter ?? (transformAfter = patchStartTime);
        if (patchStartTime === newTime)
            return this;
        const delta = newTime - patchStartTime;
        return this.rewriteTime((id) => {
            if (id.sid !== sid)
                return id;
            const time = id.time;
            if (time < transformAfter)
                return id;
            return (0, clock_1.ts)(sid, time + delta);
        });
    }
    /**
     * Creates a deep clone of the patch.
     *
     * @returns A deep clone of the patch.
     */
    clone() {
        return this.rewriteTime((id) => id);
    }
    /**
     * Marshals the patch into a binary representation.
     *
     * @returns A binary representation of the patch.
     */
    toBinary() {
        return (0, binary_1.encode)(this);
    }
    // ---------------------------------------------------------------- Printable
    /**
     * Returns a textual human-readable representation of the patch. This can be
     * used for debugging purposes.
     *
     * @param tab Start string for each line.
     * @returns Text representation of the patch.
     */
    toString(tab = '') {
        const id = this.getId();
        const header = `Patch ${id ? (0, clock_1.printTs)(id) : '(nil)'}!${this.span()}`;
        return (header +
            (0, printTree_1.printTree)(tab, this.ops.map((op) => (tab) => op.toString(tab))));
    }
}
exports.Patch = Patch;
//# sourceMappingURL=Patch.js.map