import Intervaler from './Intervaler';
import InlineIntervaler from './InlineIntervaler';
import NodeFactory from '../models/NodeFactory';
const insertInNodes = (nodes, tags, interval) => {
    for (let i = 0; i < nodes.length; i += 1) {
        const node = nodes[i];
        if (node.start >= interval.end) {
            break;
        }
        if (interval.start < node.end && interval.end > node.start) {
            if (node.name === '') {
                const newNodes = [];
                if (node.start < interval.start) {
                    newNodes.push({
                        name: '',
                        start: node.start,
                        end: interval.start,
                        children: [],
                    });
                }
                newNodes.push(Object.assign(Object.assign({}, tags), { start: Math.max(node.start, interval.start), end: Math.min(node.end, interval.end), children: [{
                            name: '',
                            start: Math.max(node.start, interval.start),
                            end: Math.min(node.end, interval.end),
                            children: [],
                        }] }));
                if (node.end > interval.end) {
                    newNodes.push({
                        name: '',
                        start: interval.end,
                        end: node.end,
                        children: [],
                    });
                }
                nodes.splice(i, 1, ...newNodes);
                i += newNodes.length - 1;
            }
            else {
                insertInNodes(node.children, tags, interval);
            }
        }
    }
};
export default class RichTextManager {
    constructor(text, formatings, inlines) {
        this.promises = {};
        this.text = text || '';
        this.formatings = formatings || {};
        this.inlines = inlines || new InlineIntervaler();
    }
    getText() {
        return this.text;
    }
    setText(text) {
        if (text.length === this.text.length) {
            this.text = text;
        }
    }
    getTextLength() {
        return this.text.length;
    }
    getFormatings() {
        return this.formatings;
    }
    getHtml(frms, start, end) {
        const { text } = this;
        if (text === '') {
            return '<br/>';
        }
        const globalStart = start !== null && start !== void 0 ? start : 0;
        const globalEnd = end !== null && end !== void 0 ? end : this.text.length;
        const tree = this.getHtmlTree(frms, globalStart, globalEnd);
        const [html] = this.getHtmlFromTree(tree);
        return html;
    }
    insertText(rtm, offset = 0, keepFormatings = false) {
        const text = typeof rtm === 'string' ? rtm : rtm.getText();
        const endOffset = offset + text.length;
        this.text = this.text.slice(0, offset) + text + this.text.slice(offset);
        const formationsForApply = keepFormatings ? this.getFormationgsForApply(offset) : [];
        this.shiftFormatings(offset, text.length);
        if (rtm instanceof RichTextManager) {
            this.merge(rtm, offset);
        }
        else {
            formationsForApply.forEach((name) => this.insertFormating(name, offset, endOffset));
        }
        return endOffset;
    }
    removeText(start, end) {
        this.cutText(start, end);
    }
    clearText() {
        this.text = '';
        this.formatings = {};
        this.promises = {};
        this.inlines = new InlineIntervaler();
    }
    cutText(start, end) {
        let text;
        if (end === undefined) {
            text = this.text.slice(start);
            this.text = this.text.slice(0, start);
        }
        else {
            text = this.text.slice(start, end);
            this.text = this.text.slice(0, start) + this.text.slice(end);
        }
        const inlines = this.inlines.cut(start, end);
        const formatings = this.cutFormatings(start, end);
        return new RichTextManager(text, formatings, inlines);
    }
    insertFormating(name, start, end) {
        if (!this.formatings[name]) {
            this.formatings[name] = new Intervaler();
        }
        this.formatings[name].addInterval(start, end);
    }
    togglePromiseFormating(formating, offset) {
        const { name } = formating;
        let type = 'add';
        if (this.promises[name] && this.promises[name].offset === offset) {
            if (this.promises[name].type === 'add') {
                type = 'remove';
            }
        }
        else if (this.hasFormating(name, offset, offset)) {
            type = 'remove';
        }
        this.promises[name] = {
            offset,
            type,
        };
    }
    clearPromises() {
        this.promises = {};
    }
    toggleFormating(name, start, end) {
        if (!this.formatings[name]) {
            this.formatings[name] = new Intervaler();
            this.formatings[name].addInterval(start, end);
        }
        else {
            if (this.formatings[name].hasInterval(start, end)) {
                this.formatings[name].removeInterval(start, end);
            }
            else {
                this.formatings[name].addInterval(start, end);
            }
        }
    }
    clearFormatings(start, end) {
        Object.entries(this.formatings).forEach(([, intervaler]) => {
            intervaler.removeInterval(start, end);
        });
    }
    hasFormating(name, start, end) {
        if (!this.formatings[name]) {
            return false;
        }
        return this.formatings[name].hasInterval(start || 0, end || this.text.length);
    }
    ltrim() {
        const match = this.text.match(/^( +)/g);
        if (!match) {
            return;
        }
        const len = match[0].length;
        this.cutText(0, len);
    }
    rtrim() {
        const match = this.text.match(/( +)$/g);
        if (!match) {
            return;
        }
        const len = match[0].length;
        const start = this.text.length - len;
        this.cutText(start, start + len);
    }
    clearSpaces() {
        let match;
        // eslint-disable-next-line no-cond-assign
        while (match = / {2,}/.exec(this.text)) {
            const start = match.index;
            const end = start + match[0].length - 1;
            this.cutText(start, end);
        }
    }
    addInlineNode(arena, start, end) {
        const node = NodeFactory.createInlineNode(arena);
        this.inlines.addInterval(start, end, node);
        return node;
    }
    getInlineNode(arena, start, end) {
        const node = this.inlines.getNode(start, end);
        if (node && node.arena === arena) {
            return node;
        }
        return undefined;
    }
    removeInlineNode(node) {
        this.inlines.removeNode(node);
    }
    updateInlineNode(node, start, end) {
        this.inlines.updateNode(node, start, end);
    }
    clone() {
        const entries = Object
            .entries(this.formatings)
            .map(([name, intervaler]) => [name, intervaler.clone()]);
        const formatings = [...entries].reduce((obj, [key, val]) => {
            // eslint-disable-next-line no-param-reassign
            obj[key] = val;
            return obj;
        }, {});
        return new RichTextManager(this.text, formatings, this.inlines.clone());
    }
    cutFormatings(start, end) {
        const formatings = {};
        this.inlines.cut(start, end);
        Object.entries(this.formatings).forEach(([name, intervaler]) => {
            formatings[name] = intervaler.cut(start, end);
        });
        return formatings;
    }
    prepareText(text, first, last) {
        let result = text
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;')
            .replace(/\s\s/g, ' &nbsp;')
            .replace(/\u00A0/, '&nbsp;');
        if (first) {
            result = result.replace(/^\s/, '&nbsp;');
        }
        if (last) {
            result = result.replace(/\s$/, '&nbsp;');
        }
        return result;
    }
    getInsertions(frms) {
        const insertions = [];
        Object.entries(this.formatings).forEach(([name, intervaler]) => {
            if (frms[name]) {
                const { tag, attributes } = frms[name];
                let attributesStr = '';
                attributes.forEach((attr) => {
                    attributesStr += ` ${attr}`;
                });
                intervaler.getIntervals().forEach((interval) => {
                    insertions.push({
                        offset: interval.start,
                        tag: `<${tag.toLowerCase()}${attributesStr}>`,
                    });
                    insertions.push({
                        offset: interval.end,
                        tag: `</${tag.toLowerCase()}>`,
                    });
                });
            }
        });
        return insertions.sort((a, b) => a.offset - b.offset);
    }
    shiftFormatings(offset, step) {
        this.inlines.shift(offset, step, false);
        Object.values(this.formatings)
            .forEach((intervaler) => intervaler.shift(offset, step));
    }
    merge(formatings, offset) {
        this.inlines.merge(formatings.inlines, offset);
        Object.entries(formatings.formatings).forEach(([name, intervaler]) => {
            if (!this.formatings[name]) {
                this.formatings[name] = new Intervaler();
            }
            this.formatings[name].merge(intervaler, offset);
        });
    }
    getHtmlTree(frms, globalStart, globalEnd) {
        const rootNodes = [{
                name: '',
                start: globalStart,
                end: globalEnd,
                children: [],
            }];
        this.inlines.getIntervals().forEach((obj) => {
            const { node, start, end } = obj;
            const [openTag, closeTag] = node.getTags();
            insertInNodes(rootNodes, {
                name: node.arena.name,
                openTag,
                closeTag,
            }, { start, end });
        });
        Object.entries(this.formatings).forEach(([name, intervaler]) => {
            if (frms[name]) {
                const { tag, attributes } = frms[name];
                let attributesStr = '';
                attributes.forEach((attr) => {
                    attributesStr += ` ${attr}`;
                });
                intervaler.getIntervals().forEach((interval) => {
                    insertInNodes(rootNodes, {
                        name,
                        openTag: `<${tag.toLowerCase()}${attributesStr}>`,
                        closeTag: `</${tag.toLowerCase()}>`,
                    }, interval);
                });
            }
        });
        return rootNodes;
    }
    getHtmlFromTree(nodes, prepareSpace = false) {
        let text = '';
        let lastSpace = prepareSpace;
        for (let i = 0; i < nodes.length; i += 1) {
            const node = nodes[i];
            if (node.name === '') {
                let prepared = this.prepareText(this.text.slice(node.start, node.end), node.start === 0, node.end === this.text.length);
                if (lastSpace) {
                    prepared = prepared.replace(/^\s/, '&nbsp;');
                }
                lastSpace = prepared.slice(-1) === ' ';
                text += prepared;
            }
            else {
                const [childText, childPrepareSpace] = this.getHtmlFromTree(node.children, lastSpace);
                lastSpace = childPrepareSpace;
                text += node.openTag + childText + node.closeTag;
            }
        }
        return [text, lastSpace];
    }
    getFormationgsForApply(offset) {
        const result = [];
        Object.entries(this.formatings).forEach(([name, intervaler]) => {
            if (intervaler.hasInterval(offset, offset)) {
                result.push(name);
            }
        });
        Object.entries(this.promises).forEach(([name, promise]) => {
            if (promise.offset === offset) {
                if (promise.type === 'add') {
                    if (!result.includes(name)) {
                        result.push(name);
                    }
                }
                else {
                    const index = result.indexOf(name);
                    if (index > -1) {
                        result.splice(index, 1);
                    }
                }
            }
        });
        return result;
    }
}
//# sourceMappingURL=RichTextManager.js.map