"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Encoder = void 0;
const tslib_1 = require("tslib");
const nodes = tslib_1.__importStar(require("../../../nodes"));
const ClockEncoder_1 = require("../../../../json-crdt-patch/codec/clock/ClockEncoder");
const clock_1 = require("../../../../json-crdt-patch/clock");
class Encoder {
    encode(model) {
        this.model = model;
        const isServerTime = model.clock.sid === 1 /* SESSION.SERVER */;
        const clock = model.clock;
        if (isServerTime) {
            this.time = clock.time;
        }
        else {
            this.clock = new ClockEncoder_1.ClockEncoder();
            this.clock.reset(model.clock);
        }
        const root = model.root;
        const doc = [0, !root.val.time ? 0 : this.cNode(root.node())];
        doc[0] = isServerTime ? this.time : this.clock.toJson();
        return doc;
    }
    ts(ts) {
        switch (ts.sid) {
            case 0 /* SESSION.SYSTEM */:
                return [ts.sid, ts.time];
            case 1 /* SESSION.SERVER */:
                return this.time - ts.time;
            default: {
                const relativeId = this.clock.append(ts);
                return [-relativeId.sessionIndex, relativeId.timeDiff];
            }
        }
    }
    cNode(node) {
        // TODO: PERF: use switch with `node.constructor`.
        if (node instanceof nodes.ObjNode)
            return this.cObj(node);
        else if (node instanceof nodes.ArrNode)
            return this.cArr(node);
        else if (node instanceof nodes.StrNode)
            return this.cStr(node);
        else if (node instanceof nodes.ValNode)
            return this.cVal(node);
        else if (node instanceof nodes.VecNode)
            return this.cVec(node);
        else if (node instanceof nodes.ConNode)
            return this.cCon(node);
        else if (node instanceof nodes.BinNode)
            return this.cBin(node);
        throw new Error('UNKNOWN_NODE');
    }
    cObj(obj) {
        const map = {};
        obj.nodes((child, key) => (map[key] = this.cNode(child)));
        const res = [2 /* JsonCrdtDataType.obj */, this.ts(obj.id), map];
        return res;
    }
    cVec(vec) {
        const elements = vec.elements;
        const length = elements.length;
        const index = this.model.index;
        const map = [];
        for (let i = 0; i < length; i++) {
            const elementId = elements[i];
            if (!elementId)
                map.push(0);
            else {
                const node = index.get(elementId);
                map.push(this.cNode(node));
            }
        }
        const res = [3 /* JsonCrdtDataType.vec */, this.ts(vec.id), map];
        return res;
    }
    cArr(node) {
        const chunks = [];
        const index = this.model.index;
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            const deleted = chunk.del;
            const span = chunk.span;
            const chunkIdEncoded = this.ts(chunk.id);
            if (deleted) {
                chunks.push([chunkIdEncoded, span]);
            }
            else {
                const nodeIds = chunk.data;
                const nodes = [];
                for (let i = 0; i < span; i++)
                    nodes.push(this.cNode(index.get(nodeIds[i])));
                chunks.push([chunkIdEncoded, nodes]);
            }
        }
        const res = [6 /* JsonCrdtDataType.arr */, this.ts(node.id), chunks];
        return res;
    }
    cStr(node) {
        const chunks = [];
        for (let chunk = node.first(); chunk; chunk = node.next(chunk))
            chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data]);
        const res = [4 /* JsonCrdtDataType.str */, this.ts(node.id), chunks];
        return res;
    }
    cBin(node) {
        const chunks = [];
        for (let chunk = node.first(); chunk; chunk = node.next(chunk))
            chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data]);
        const res = [5 /* JsonCrdtDataType.bin */, this.ts(node.id), chunks];
        return res;
    }
    cVal(node) {
        const res = [1 /* JsonCrdtDataType.val */, this.ts(node.id), this.cNode(node.node())];
        return res;
    }
    cCon(node) {
        const val = node.val;
        const res = [0 /* JsonCrdtDataType.con */, this.ts(node.id), val];
        if (val instanceof clock_1.Timestamp) {
            res[2] = 0;
            const specialData = this.ts(val);
            res.push(specialData);
        }
        else if (val === undefined) {
            res[2] = 0;
            res.push(0);
        }
        return res;
    }
}
exports.Encoder = Encoder;
//# sourceMappingURL=Encoder.js.map