"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Slices = void 0;
const tslib_1 = require("tslib");
const AvlMap_1 = require("sonic-forest/lib/avl/AvlMap");
const printTree_1 = require("tree-dump/lib/printTree");
const PersistedSlice_1 = require("./PersistedSlice");
const clock_1 = require("../../../json-crdt-patch/clock");
const hash_1 = require("../../../json-crdt/hash");
const hash_2 = require("../../../json-hash/hash");
const constants_1 = require("./constants");
const MarkerSlice_1 = require("./MarkerSlice");
const nodes_1 = require("../../../json-crdt/nodes");
const constants_2 = require("../constants");
const constants_3 = require("../rga/constants");
const iterator_1 = require("../../../util/iterator");
const schema = tslib_1.__importStar(require("./schema"));
class Slices {
    constructor(
    /** The text RGA. */
    txt, 
    /** The `arr` node, used as a set, where slices are stored. */
    set) {
        this.txt = txt;
        this.set = set;
        this.list = new AvlMap_1.AvlMap(clock_1.compare);
        // ----------------------------------------------------------------- Stateful
        this._topologyHash = 0;
        this.hash = 0;
        this.rga = txt.str;
    }
    ins(range, stacking, type, data, Klass = stacking === constants_1.SliceStacking.Marker ? MarkerSlice_1.MarkerSlice : PersistedSlice_1.PersistedSlice) {
        const slicesModel = this.set.doc;
        const set = this.set;
        const api = slicesModel.api;
        const builder = api.builder;
        const stepId = builder.vec();
        const start = range.start.clone();
        const end = range.end.clone();
        stacking = stacking & 0b111;
        const header = (stacking << constants_1.SliceHeaderShift.Stacking) +
            ((start.anchor & 0b1) << constants_1.SliceHeaderShift.X1Anchor) +
            ((end.anchor & 0b1) << constants_1.SliceHeaderShift.X2Anchor);
        const headerId = builder.con(header);
        const x1Id = builder.con(start.id);
        const x2Id = builder.con((0, clock_1.compare)(start.id, end.id) === 0 ? 0 : end.id);
        const typeId = schema.type(type).build(builder);
        const tupleKeysUpdate = [
            [constants_1.SliceTupleIndex.Header, headerId],
            [constants_1.SliceTupleIndex.X1, x1Id],
            [constants_1.SliceTupleIndex.X2, x2Id],
            [constants_1.SliceTupleIndex.Type, typeId],
        ];
        if (data !== undefined)
            tupleKeysUpdate.push([constants_1.SliceTupleIndex.Data, builder.json(data)]);
        builder.insVec(stepId, tupleKeysUpdate);
        const chunkId = builder.insArr(set.id, set.id, [stepId]);
        // TODO: Consider using `s` schema here.
        api.apply();
        const tuple = slicesModel.index.get(stepId);
        const chunk = set.findById(chunkId);
        // TODO: Need to check if split slice text was deleted
        const slice = new Klass(slicesModel, this.txt, chunk, tuple, stacking, start, end);
        this.list.set(chunk.id, slice);
        return slice;
    }
    insMarker(range, type, data) {
        return this.ins(range, constants_1.SliceStacking.Marker, type, data);
    }
    insMarkerAfter(after, type, data, separator = constants_2.Chars.BlockSplitSentinel) {
        // TODO: test condition when cursors is at absolute or relative starts
        const txt = this.txt;
        const api = txt.model.api;
        const builder = api.builder;
        /**
         * We skip one clock cycle to prevent Block-wise RGA from merging adjacent
         * characters. We want the marker chunk to always be its own distinct chunk.
         */
        builder.nop(1);
        // TODO: Handle case when marker is inserted at the abs start, prevent abs start/end inserts.
        const textId = builder.insStr(txt.str.id, after, separator);
        api.apply();
        const point = txt.point(textId, constants_3.Anchor.Before);
        const range = txt.range(point, point.clone());
        return this.insMarker(range, type, data);
    }
    insStack(range, type, data) {
        return this.ins(range, constants_1.SliceStacking.Many, type, data);
    }
    insOne(range, type, data) {
        return this.ins(range, constants_1.SliceStacking.One, type, data);
    }
    insErase(range, type, data) {
        return this.ins(range, constants_1.SliceStacking.Erase, type, data);
    }
    unpack(chunk) {
        const txt = this.txt;
        const model = this.set.doc;
        const tupleId = chunk.data ? chunk.data[0] : undefined;
        if (!tupleId)
            throw new Error('SLICE_NOT_FOUND');
        const tuple = model.index.get(tupleId);
        if (!(tuple instanceof nodes_1.VecNode))
            throw new Error('NOT_TUPLE');
        let slice = PersistedSlice_1.PersistedSlice.deserialize(model, txt, chunk, tuple);
        if (slice.isSplit())
            slice = new MarkerSlice_1.MarkerSlice(model, txt, chunk, tuple, slice.stacking, slice.start, slice.end);
        return slice;
    }
    get(id) {
        return this.list.get(id);
    }
    del(id) {
        this.list.del(id);
        const set = this.set;
        const api = set.doc.api;
        if (!set.findById(id))
            return;
        // TODO: Is it worth checking if the slice is already deleted?
        api.builder.del(set.id, [(0, clock_1.tss)(id.sid, id.time, 1)]);
        api.apply();
    }
    delSlices(slices) {
        const set = this.set;
        const doc = set.doc;
        const api = doc.api;
        const spans = [];
        for (const slice of slices) {
            if (slice instanceof PersistedSlice_1.PersistedSlice) {
                const id = slice.id;
                if (!set.findById(id))
                    continue;
                spans.push(new clock_1.Timespan(id.sid, id.time, 1));
            }
        }
        if (!spans.length)
            return false;
        api.builder.del(this.set.id, spans);
        api.apply();
        return true;
    }
    size() {
        return this.list._size;
    }
    /**
     * @todo Rename to `each0`.
     */
    iterator0() {
        const iterator = this.list.iterator0();
        return () => iterator()?.v;
    }
    each() {
        return new iterator_1.UndEndIterator(this.iterator0());
    }
    forEach(callback) {
        this.list.forEach((node) => callback(node.v));
    }
    refresh() {
        const topologyHash = (0, hash_1.updateRga)(hash_2.CONST.START_STATE, this.set);
        if (topologyHash !== this._topologyHash) {
            this._topologyHash = topologyHash;
            let chunk;
            for (const iterator = this.set.iterator(); (chunk = iterator());) {
                const item = this.list.get(chunk.id);
                if (chunk.del) {
                    if (item)
                        this.list.del(chunk.id);
                }
                else {
                    if (!item)
                        this.list.set(chunk.id, this.unpack(chunk));
                }
            }
        }
        let hash = topologyHash;
        this.list.forEach(({ v: item }) => {
            item.refresh();
            hash = (0, hash_2.updateNum)(hash, item.hash);
        });
        return (this.hash = hash);
    }
    // ---------------------------------------------------------------- Printable
    toStringName() {
        return 'Slices';
    }
    toString(tab = '') {
        return (this.toStringName() +
            (0, printTree_1.printTree)(tab, [...this.list.entries()].map(({ v }) => (tab) => v.toString(tab))));
    }
}
exports.Slices = Slices;
//# sourceMappingURL=Slices.js.map