"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Inline = exports.InlineAttrEndPoint = exports.InlineAttrStartPoint = exports.InlineAttrContained = exports.InlineAttrEnd = exports.InlineAttrStart = exports.InlineAttrPassing = exports.AbstractInlineAttr = void 0;
const printTree_1 = require("tree-dump/lib/printTree");
const toLine_1 = require("pojo-dump/lib/toLine");
const constants_1 = require("../slice/constants");
const Range_1 = require("../rga/Range");
const ChunkSlice_1 = require("../util/ChunkSlice");
const Cursor_1 = require("../editor/Cursor");
const hash_1 = require("../../../json-crdt/hash");
const util_1 = require("../slice/util");
class AbstractInlineAttr {
    constructor(slice) {
        this.slice = slice;
    }
    /** @returns Whether the attribute starts at the start of the inline. */
    isStart() {
        return false;
    }
    /** @returns Whether the attribute ends at the end of the inline. */
    isEnd() {
        return false;
    }
    /** @returns Whether the attribute is collapsed to a point. */
    isCollapsed() {
        return false;
    }
}
exports.AbstractInlineAttr = AbstractInlineAttr;
/** The attribute started before this inline and ends after this inline. */
class InlineAttrPassing extends AbstractInlineAttr {
}
exports.InlineAttrPassing = InlineAttrPassing;
/** The attribute starts at the beginning of this inline. */
class InlineAttrStart extends AbstractInlineAttr {
    isStart() {
        return true;
    }
}
exports.InlineAttrStart = InlineAttrStart;
/** The attribute ends at the end of this inline. */
class InlineAttrEnd extends AbstractInlineAttr {
    isEnd() {
        return true;
    }
}
exports.InlineAttrEnd = InlineAttrEnd;
/** The attribute starts and ends in this inline, exactly contains it. */
class InlineAttrContained extends AbstractInlineAttr {
    isStart() {
        return true;
    }
    isEnd() {
        return true;
    }
}
exports.InlineAttrContained = InlineAttrContained;
/** The attribute is collapsed at start of this inline. */
class InlineAttrStartPoint extends AbstractInlineAttr {
    isStart() {
        return true;
    }
    isCollapsed() {
        return true;
    }
}
exports.InlineAttrStartPoint = InlineAttrStartPoint;
/** The attribute is collapsed at end of this inline. */
class InlineAttrEndPoint extends AbstractInlineAttr {
    isEnd() {
        return true;
    }
    isCollapsed() {
        return true;
    }
}
exports.InlineAttrEndPoint = InlineAttrEndPoint;
/**
 * The `Inline` class represents a range of inline text within a block, which
 * has the same annotations and formatting for all of its text contents, i.e.
 * its text contents can be rendered as a single (`<span>`) element. However,
 * the text contents might still be composed of multiple {@link ChunkSlice}s,
 * which are the smallest units of text and need to be concatenated to get the
 * full text content of the inline.
 */
class Inline extends Range_1.Range {
    constructor(txt, p1, p2, start, end) {
        super(txt.str, start, end);
        this.txt = txt;
        this.p1 = p1;
        this.p2 = p2;
    }
    /**
     * @returns A stable unique identifier of this *inline* within a list of other
     *     inlines of the parent block. Can be used for UI libraries to track the
     *     identity of the inline across renders.
     */
    key() {
        const start = this.start;
        return (0, hash_1.hashId)(start.id) + (start.anchor ? 0 : 1);
    }
    /**
     * @returns The position of the inline within the text.
     */
    pos() {
        const chunkSlice = this.texts(1)[0];
        if (!chunkSlice)
            return -1;
        const chunk = chunkSlice.chunk;
        const pos = this.rga.pos(chunk);
        return pos + chunkSlice.off;
    }
    createAttr(slice) {
        const p1 = this.p1;
        const p2 = this.p2;
        return !slice.start.cmp(slice.end)
            ? !slice.start.cmp(p1)
                ? new InlineAttrStartPoint(slice)
                : new InlineAttrEndPoint(slice)
            : !p1.cmp(slice.start)
                ? !p2.cmp(slice.end)
                    ? new InlineAttrContained(slice)
                    : new InlineAttrStart(slice)
                : !p2.cmp(slice.end)
                    ? new InlineAttrEnd(slice)
                    : new InlineAttrPassing(slice);
    }
    /**
     * @returns Returns the attributes of the inline, which are the slice
     *     annotations and formatting applied to the inline.
     *
     * @todo Rename to `.stat()`.
     * @todo Create a more efficient way to compute inline stats, separate: (1)
     *     boolean flags, (2) cursor, (3) other attributes.
     */
    attr() {
        if (this._attr)
            return this._attr;
        const attr = (this._attr = {});
        const p1 = this.p1;
        const p2 = this.p2;
        const slices1 = p1.layers;
        const slices2 = p1.markers;
        const slices3 = p2.isAbsEnd() ? p2.markers : [];
        const length1 = slices1.length;
        const length2 = slices2.length;
        const length3 = slices3.length;
        const length12 = length1 + length2;
        const length123 = length12 + length3;
        for (let i = 0; i < length123; i++) {
            const slice = i >= length12 ? slices3[i - length12] : i >= length1 ? slices2[i - length1] : slices1[i];
            if (slice instanceof Range_1.Range) {
                const type = slice.type();
                switch (slice.stacking) {
                    case constants_1.SliceStacking.Cursor: {
                        const stack = attr[constants_1.SliceTypeName.Cursor] ?? (attr[constants_1.SliceTypeName.Cursor] = []);
                        stack.push(this.createAttr(slice));
                        break;
                    }
                    case constants_1.SliceStacking.Many: {
                        const stack = attr[type] ?? (attr[type] = []);
                        stack.push(this.createAttr(slice));
                        break;
                    }
                    case constants_1.SliceStacking.One: {
                        attr[type] = [this.createAttr(slice)];
                        break;
                    }
                    case constants_1.SliceStacking.Erase: {
                        delete attr[type];
                        break;
                    }
                }
            }
        }
        return attr;
    }
    hasCursor() {
        return !!this.attr()[constants_1.SliceTypeName.Cursor];
    }
    /** @todo Make this return a list of cursors. */
    cursorStart() {
        const attributes = this.attr();
        const stack = attributes[constants_1.SliceTypeName.Cursor];
        if (!stack)
            return;
        const attribute = stack[0];
        if (attribute instanceof InlineAttrStart ||
            attribute instanceof InlineAttrContained ||
            attribute instanceof InlineAttrStartPoint) {
            const slice = attribute.slice;
            return slice instanceof Cursor_1.Cursor ? slice : void 0;
        }
        return;
    }
    cursorEnd() {
        const attributes = this.attr();
        const stack = attributes[constants_1.SliceTypeName.Cursor];
        if (!stack)
            return;
        const attribute = stack[0];
        if (attribute instanceof InlineAttrEnd ||
            attribute instanceof InlineAttrContained ||
            attribute instanceof InlineAttrEndPoint) {
            const slice = attribute.slice;
            return slice instanceof Cursor_1.Cursor ? slice : void 0;
        }
        return;
    }
    /**
     * Returns a 2-tuple if this inline is part of a selection. The 2-tuple sides
     * specify how selection ends on each side. Empty string means the selection
     * continues past that edge, `focus` and `anchor` specify that the edge
     * is either a focus caret or an anchor, respectively.
     *
     * @returns Selection state of this inline.
     */
    selection() {
        const attributes = this.attr();
        const stack = attributes[constants_1.SliceTypeName.Cursor];
        if (!stack)
            return;
        const attribute = stack[0];
        const cursor = attribute.slice;
        if (!(cursor instanceof Cursor_1.Cursor))
            return;
        if (attribute instanceof InlineAttrPassing)
            return ['', ''];
        if (attribute instanceof InlineAttrStart)
            return [cursor.isStartFocused() ? 'focus' : 'anchor', ''];
        if (attribute instanceof InlineAttrEnd)
            return ['', cursor.isEndFocused() ? 'focus' : 'anchor'];
        if (attribute instanceof InlineAttrContained)
            return cursor.isStartFocused() ? ['focus', 'anchor'] : ['anchor', 'focus'];
        return;
    }
    texts(limit = 1e6) {
        const texts = [];
        const txt = this.txt;
        const overlay = txt.overlay;
        let cnt = 0;
        overlay.chunkSlices0(this.start.chunk(), this.start, this.end, (chunk, off, len) => {
            if (overlay.isMarker(chunk.id))
                return;
            cnt++;
            texts.push(new ChunkSlice_1.ChunkSlice(chunk, off, len));
            if (cnt === limit)
                return true;
        });
        return texts;
    }
    // ------------------------------------------------------------------- export
    toJson() {
        let node = this.text();
        const attrs = this.attr();
        for (const key in attrs) {
            const keyNum = Number(key);
            if (keyNum === constants_1.SliceTypeName.Cursor || keyNum === constants_1.SliceTypeName.RemoteCursor)
                continue;
            const attr = attrs[key];
            if (!attr.length)
                node = [key, { inline: true }, node];
            else {
                const length = attr.length;
                for (let i = 0; i < length; i++) {
                    const slice = attr[i].slice;
                    const data = slice.data();
                    const attributes = data === void 0 ? { inline: true } : { inline: true, data };
                    node = [key === keyNum + '' ? keyNum : key, attributes, node];
                }
            }
        }
        return node;
    }
    // ---------------------------------------------------------------- Printable
    toStringName() {
        return 'Inline';
    }
    toString(tab = '') {
        const header = `${super.toString(tab)}`;
        const attr = this.attr();
        const attrKeys = Object.keys(attr);
        const texts = this.texts();
        return (header +
            (0, printTree_1.printTree)(tab, [
                !attrKeys.length
                    ? null
                    : (tab) => 'attributes' +
                        (0, printTree_1.printTree)(tab, attrKeys.map((key) => () => {
                            return key === '-1'
                                ? '▚ (cursor)'
                                : (0, util_1.formatStep)(key) +
                                    ' = ' +
                                    (0, toLine_1.toLine)(attr[key].map((attr) => {
                                        const slice = attr.slice;
                                        return slice instanceof Cursor_1.Cursor ? [slice.type(), slice.data()] : slice.data();
                                    }));
                        })),
                !texts.length
                    ? null
                    : (tab) => 'texts' +
                        (0, printTree_1.printTree)(tab, this.texts().map((text) => (tab) => text.toString(tab))),
            ]));
    }
}
exports.Inline = Inline;
//# sourceMappingURL=Inline.js.map