"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.MergeTreeTextHelper = exports.TextSegment = void 0;
const mergeTree_1 = require("./mergeTree");
const localReference_1 = require("./localReference");
// Maximum length of text segment to be considered to be merged with other segment.
// Maximum segment length is at least 2x of it (not taking into account initial segment creation).
// The bigger it is, the more expensive it is to break segment into sub-segments (on edits)
// The smaller it is, the more segments we have in snapshots (and in memory) - it's more expensive to load snapshots.
// Small number also makes ReplayTool produce false positives ("same" snapshots have slightly different binary
// representations).  More measurements needs to be done, but it's very likely the right spot is somewhere between
// 1K-2K mark.  That said, we also break segments on newline and there are very few segments that are longer than 256
// because of it.  Must be an even number
const TextSegmentGranularity = 256;
class TextSegment extends mergeTree_1.BaseSegment {
    constructor(text) {
        super();
        this.text = text;
        this.type = TextSegment.type;
        this.cachedLength = text.length;
    }
    static is(segment) {
        return segment.type === TextSegment.type;
    }
    static make(text, props) {
        const seg = new TextSegment(text);
        if (props) {
            seg.addProperties(props);
        }
        return seg;
    }
    static fromJSONObject(spec) {
        if (typeof spec === "string") {
            return new TextSegment(spec);
        }
        else if (spec && typeof spec === "object" && "text" in spec) {
            const textSpec = spec;
            return TextSegment.make(textSpec.text, textSpec.props);
        }
        return undefined;
    }
    toJSONObject() {
        // To reduce snapshot/ops size, we serialize a TextSegment as a plain 'string' if it is
        // not annotated.
        return this.properties
            ? { text: this.text, props: this.properties }
            : this.text;
    }
    clone(start = 0, end) {
        const text = this.text.substring(start, end);
        const b = TextSegment.make(text, this.properties);
        this.cloneInto(b);
        return b;
    }
    canAppend(segment) {
        return !this.text.endsWith("\n")
            && TextSegment.is(segment)
            && (this.cachedLength <= TextSegmentGranularity ||
                segment.cachedLength <= TextSegmentGranularity);
    }
    toString() {
        return this.text;
    }
    append(segment) {
        if (TextSegment.is(segment)) {
            // Note: Must call 'appendLocalRefs' before modifying this segment's length as
            // 'this.cachedLength' is used to adjust the offsets of the local refs.
            localReference_1.LocalReferenceCollection.append(this, segment);
            this.text += segment.text;
            this.cachedLength = this.text.length;
        }
        else {
            throw new Error("can only append text segment");
        }
    }
    // TODO: retain removed text for undo
    // returns true if entire string removed
    removeRange(start, end) {
        let remnantString = "";
        const len = this.text.length;
        if (start > 0) {
            remnantString += this.text.substring(0, start);
        }
        if (end < len) {
            remnantString += this.text.substring(end);
        }
        this.text = remnantString;
        this.cachedLength = remnantString.length;
        return (remnantString.length === 0);
    }
    createSplitSegmentAt(pos) {
        if (pos > 0) {
            const remainingText = this.text.substring(pos);
            this.text = this.text.substring(0, pos);
            this.cachedLength = this.text.length;
            const leafSegment = new TextSegment(remainingText);
            return leafSegment;
        }
    }
}
exports.TextSegment = TextSegment;
TextSegment.type = "TextSegment";
function isTextAndMarkerAccumulator(accum) {
    return accum.parallelArrays === true;
}
class MergeTreeTextHelper {
    constructor(mergeTree) {
        this.mergeTree = mergeTree;
        this.gatherText = (segment, pos, refSeq, clientId, start, end, accumText) => {
            var _a, _b;
            let _start = start;
            if (TextSegment.is(segment)) {
                let beginTags = "";
                let endTags = "";
                if (isTextAndMarkerAccumulator(accumText)) {
                    // TODO: let clients pass in function to get tag
                    const tags = [];
                    const initTags = [];
                    if ((_a = segment.properties) === null || _a === void 0 ? void 0 : _a["font-weight"]) {
                        tags.push("b");
                    }
                    if ((_b = segment.properties) === null || _b === void 0 ? void 0 : _b["text-decoration"]) {
                        tags.push("u");
                    }
                    const remTags = [];
                    if (tags.length > 0) {
                        for (const tag of tags) {
                            if (!accumText.tagsInProgress.includes(tag)) {
                                beginTags += `<${tag}>`;
                                initTags.push(tag);
                            }
                        }
                        for (const accumTag of accumText.tagsInProgress) {
                            if (!tags.includes(accumTag)) {
                                endTags += `</${accumTag}>`;
                                remTags.push(accumTag);
                            }
                        }
                        for (const initTag of initTags.reverse()) {
                            accumText.tagsInProgress.push(initTag);
                        }
                    }
                    else {
                        for (const accumTag of accumText.tagsInProgress) {
                            endTags += `</${accumTag}>`;
                            remTags.push(accumTag);
                        }
                    }
                    for (const remTag of remTags) {
                        const remdex = accumText.tagsInProgress.indexOf(remTag);
                        if (remdex >= 0) {
                            accumText.tagsInProgress.splice(remdex, 1);
                        }
                    }
                }
                accumText.textSegment.text += endTags;
                accumText.textSegment.text += beginTags;
                if ((_start <= 0) && (end >= segment.text.length)) {
                    accumText.textSegment.text += segment.text;
                }
                else {
                    if (_start < 0) {
                        _start = 0;
                    }
                    if (end >= segment.text.length) {
                        accumText.textSegment.text += segment.text.substring(_start);
                    }
                    else {
                        accumText.textSegment.text += segment.text.substring(_start, end);
                    }
                }
            }
            else {
                if (accumText.placeholder && (accumText.placeholder.length > 0)) {
                    if (accumText.placeholder === "*") {
                        const marker = segment;
                        accumText.textSegment.text += `\n${marker.toString()}`;
                    }
                    else {
                        for (let i = 0; i < segment.cachedLength; i++) {
                            accumText.textSegment.text += accumText.placeholder;
                        }
                    }
                }
                else if (isTextAndMarkerAccumulator(accumText)) {
                    const marker = segment;
                    if (marker.hasTileLabel(accumText.parallelMarkerLabel)) {
                        accumText.parallelMarkers.push(marker);
                        accumText.parallelText.push(accumText.textSegment.text);
                        accumText.textSegment.text = "";
                    }
                }
            }
            return true;
        };
    }
    getTextAndMarkers(refSeq, clientId, label, start, end) {
        const range = this.getValidRange(start, end, refSeq, clientId);
        const accum = {
            parallelArrays: true,
            parallelMarkerLabel: label,
            parallelMarkers: [],
            parallelText: [],
            tagsInProgress: [],
            textSegment: new TextSegment(""),
        };
        this.mergeTree.mapRange({ leaf: this.gatherText }, refSeq, clientId, accum, range.start, range.end);
        return { parallelText: accum.parallelText, parallelMarkers: accum.parallelMarkers };
    }
    getText(refSeq, clientId, placeholder = "", start, end) {
        const range = this.getValidRange(start, end, refSeq, clientId);
        const accum = { textSegment: new TextSegment(""), placeholder };
        this.mergeTree.mapRange({ leaf: this.gatherText }, refSeq, clientId, accum, range.start, range.end);
        return accum.textSegment.text;
    }
    getValidRange(start, end, refSeq, clientId) {
        const range = {
            end: end !== null && end !== void 0 ? end : this.mergeTree.getLength(refSeq, clientId),
            start: start !== null && start !== void 0 ? start : 0,
        };
        return range;
    }
}
exports.MergeTreeTextHelper = MergeTreeTextHelper;
//# sourceMappingURL=textSegment.js.map