"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PersistedSlice = void 0;
const hasOwnProperty_1 = require("@jsonjoy.com/util/lib/hasOwnProperty");
const Point_1 = require("../rga/Point");
const Range_1 = require("../rga/Range");
const hash_1 = require("../../../json-crdt/hash");
const printTree_1 = require("tree-dump/lib/printTree");
const constants_1 = require("./constants");
const hash_2 = require("../../../json-hash/hash");
const clock_1 = require("../../../json-crdt-patch/clock");
const json_pretty_1 = require("../../../json-pretty");
const util_1 = require("./util");
const json_crdt_patch_1 = require("../../../json-crdt-patch");
const JsonCrdtDiff_1 = require("../../../json-crdt-diff/JsonCrdtDiff");
const model_1 = require("../../../json-crdt/model");
const NestedType_1 = require("./NestedType");
/**
 * A persisted slice is a slice that is stored in a {@link Model}. It is used for
 * rich-text formatting and annotations.
 *
 * @todo Maybe rename to "saved", "stored", "mutable".
 */
class PersistedSlice extends Range_1.Range {
    static deserialize(model, txt, chunk, tuple) {
        const header = +tuple.get(0).view();
        const id1 = tuple.get(1).view();
        const id2 = (tuple.get(2).view() || id1);
        if (typeof header !== 'number')
            throw new Error('INVALID_HEADER');
        if (!(id1 instanceof clock_1.Timestamp))
            throw new Error('INVALID_ID');
        if (!(id2 instanceof clock_1.Timestamp))
            throw new Error('INVALID_ID');
        const anchor1 = (header & constants_1.SliceHeaderMask.X1Anchor) >>> constants_1.SliceHeaderShift.X1Anchor;
        const anchor2 = (header & constants_1.SliceHeaderMask.X2Anchor) >>> constants_1.SliceHeaderShift.X2Anchor;
        const stacking = (header & constants_1.SliceHeaderMask.Stacking) >>> constants_1.SliceHeaderShift.Stacking;
        const rga = txt.str;
        const p1 = new Point_1.Point(rga, id1, anchor1);
        const p2 = new Point_1.Point(rga, id2, anchor2);
        const slice = new PersistedSlice(model, txt, chunk, tuple, stacking, p1, p2);
        (0, util_1.validateType)(slice.type());
        return slice;
    }
    constructor(
    /** The `Model` where the slice is stored. */
    model, 
    /** The Peritext context. */
    txt, 
    /** The `arr` chunk of `arr` where the slice is stored. */
    chunk, 
    /** The `vec` node which stores the serialized contents of this slice. */
    tuple, stacking, start, end) {
        super(txt.str, start, end);
        this.model = model;
        this.txt = txt;
        this.chunk = chunk;
        this.tuple = tuple;
        this.start = start;
        this.end = end;
        /** ------------------------------------------------------ {@link Stateful} */
        this.hash = 0;
        this.rga = txt.str;
        this.id = chunk.id;
        this.stacking = stacking;
    }
    isSplit() {
        return this.stacking === constants_1.SliceStacking.Marker;
    }
    tupleApi() {
        return this.model.api.wrap(this.tuple);
    }
    // protected setType(schema: NodeBuilder): void {
    //   this.tupleApi().set([
    //     [SliceTupleIndex.Type, schema.type(type as SliceTypeSteps)],
    //   ]);
    // }
    // ---------------------------------------------------------------- mutations
    set(start, end = start) {
        super.set(start, end);
        this.update({ range: this });
    }
    /**
     * Expand range left and right to contain all invisible space: (1) tombstones,
     * (2) anchors of non-deleted adjacent chunks.
     */
    expand() {
        super.expand();
        this.update({ range: this });
    }
    update(params) {
        let updateHeader = false;
        const changes = [];
        const stacking = params.stacking;
        if (stacking !== undefined) {
            this.stacking = stacking;
            updateHeader = true;
        }
        const range = params.range;
        if (range) {
            updateHeader = true;
            changes.push([constants_1.SliceTupleIndex.X1, json_crdt_patch_1.s.con(range.start.id)], [constants_1.SliceTupleIndex.X2, json_crdt_patch_1.s.con(range.end.id)]);
            this.start = range.start;
            this.end = range.start === range.end ? range.end.clone() : range.end;
        }
        if (params.type !== undefined) {
            changes.push([constants_1.SliceTupleIndex.Type, params.type instanceof json_crdt_patch_1.NodeBuilder ? params.type : json_crdt_patch_1.s.jsonCon(params.type)]);
        }
        if ((0, hasOwnProperty_1.hasOwnProperty)(params, 'data'))
            changes.push([constants_1.SliceTupleIndex.Data, params.data]);
        if (updateHeader) {
            const header = (this.stacking << constants_1.SliceHeaderShift.Stacking) +
                (this.start.anchor << constants_1.SliceHeaderShift.X1Anchor) +
                (this.end.anchor << constants_1.SliceHeaderShift.X2Anchor);
            changes.push([constants_1.SliceTupleIndex.Header, json_crdt_patch_1.s.con(header)]);
        }
        this.tupleApi().set(changes);
    }
    getStore() {
        const txt = this.txt;
        const sid = this.id.sid;
        let store = txt.savedSlices;
        if (sid === store.set.doc.clock.sid)
            return store;
        store = txt.localSlices;
        if (sid === store.set.doc.clock.sid)
            return store;
        store = txt.extraSlices;
        if (sid === store.set.doc.clock.sid)
            return store;
        return;
    }
    del() {
        const store = this.getStore();
        if (!store)
            return;
        store.del(this.id);
    }
    isDel() {
        return this.chunk.del;
    }
    // -------------------------------------------------- slice type manipulation
    typeNode() {
        return this.tuple.get(constants_1.SliceTupleIndex.Type);
    }
    typeApi() {
        const node = this.typeNode();
        if (!node)
            return;
        return this.model.api.wrap(node);
    }
    nestedType() {
        return new NestedType_1.NestedType(this);
    }
    type() {
        return this.typeNode()?.view();
    }
    typeSteps() {
        const type = this.type() ?? 0 /* SliceTypeCon.p */;
        return Array.isArray(type) ? type : [type];
    }
    // -------------------------------------------------- slice data manipulation
    data() {
        return this.tuple.get(constants_1.SliceTupleIndex.Data)?.view();
    }
    dataNode() {
        const node = this.tuple.get(constants_1.SliceTupleIndex.Data);
        return node && this.model.api.wrap(node);
    }
    dataAsObj() {
        const node = this.dataNode();
        if (!(node instanceof model_1.ObjApi)) {
            this.tupleApi().set([[constants_1.SliceTupleIndex.Data, json_crdt_patch_1.s.obj({})]]);
        }
        return this.dataNode();
    }
    /**
     * Overwrites the data of this slice with the given data.
     *
     * @param data Data to set for this slice. The data can be any JSON value, but
     *     it is recommended to use an object.
     */
    setData(data) {
        this.tupleApi().set([[constants_1.SliceTupleIndex.Data, json_crdt_patch_1.s.jsonCon(data)]]);
    }
    /**
     * Merges object data into the slice's data using JSON CRDT diffing.
     *
     * @param data Data to merge into the slice. If the data is an object, it will be
     *     merged with the existing data of the slice using JSON CRDT diffing.
     */
    mergeData(data) {
        const { model } = this;
        const diff = new JsonCrdtDiff_1.JsonCrdtDiff(model);
        if (this.dataNode() instanceof model_1.ObjApi && !!data && typeof data === 'object' && !Array.isArray(data)) {
            const dataNode = this.dataAsObj();
            const patch = diff.diff(dataNode.node, data);
            model.applyPatch(patch);
        }
        else
            this.setData(data);
    }
    refresh() {
        let state = hash_2.CONST.START_STATE;
        state = (0, hash_1.updateNode)(state, this.tuple);
        const changed = state !== this.hash;
        this.hash = state;
        if (changed) {
            const tuple = this.tuple;
            const slice = PersistedSlice.deserialize(this.model, this.txt, this.chunk, tuple);
            this.stacking = slice.stacking;
            this.start = slice.start;
            this.end = slice.end;
        }
        return this.hash;
    }
    /** ----------------------------------------------------- {@link Printable} */
    toStringName() {
        const type = this.type();
        if (typeof type === 'number' && Math.abs(type) <= 64 && constants_1.SliceTypeName[type]) {
            return `slice [${constants_1.SliceStackingName[this.stacking]}] <${constants_1.SliceTypeName[type]}>`;
        }
        return `slice [${constants_1.SliceStackingName[this.stacking]}] ${JSON.stringify(type)}`;
    }
    toStringHeaderName() {
        const data = this.data();
        const dataFormatted = data ? (0, json_pretty_1.prettyOneLine)(data) : '∅';
        const dataLengthBreakpoint = 32;
        const header = `${this.toStringName()} ${super.toString('', true)}, ${constants_1.SliceStackingName[this.stacking]}, ${JSON.stringify(this.type())}${dataFormatted.length < dataLengthBreakpoint ? `, ${dataFormatted}` : ''}`;
        return header;
    }
    toStringHeader(tab = '') {
        const data = this.data();
        const dataFormatted = data ? (0, json_pretty_1.prettyOneLine)(data) : '';
        const dataLengthBreakpoint = 32;
        return (this.toStringHeaderName() +
            (0, printTree_1.printTree)(tab, [dataFormatted.length < dataLengthBreakpoint ? null : (tab) => dataFormatted]));
    }
}
exports.PersistedSlice = PersistedSlice;
//# sourceMappingURL=PersistedSlice.js.map