import ArenaFactory from '../arenas/ArenaFactory';
import NodeFactory from '../models/NodeFactory';
import NodeRegistry from '../helpers/NodeRegistry';
export default class ArenaModel {
    constructor(asm) {
        this.asm = asm;
        this.rootArenaName = '__ROOT__';
        this.registry = new NodeRegistry();
        this.arenas = [];
        this.arenasByName = {};
        this.formatings = [];
        this.formatingsByName = {};
        this.areanMarks = {};
        this.formatingMarks = {};
    }
    get model() {
        if (!this.rootNode) {
            throw new Error('Root node not defined');
        }
        return this.rootNode;
    }
    getRegistry() {
        return this.registry;
    }
    setRegistry(registry) {
        this.registry = registry;
    }
    setRoot(root) {
        this.rootNode = root;
    }
    // #region Arenas and Formatings
    setDefaultTextArena(arena) {
        this.rootArena = ArenaFactory.createRoot({
            root: true,
            name: this.rootArenaName,
            tag: '',
            attributes: [],
            allowedArenas: [arena],
            arenaForText: arena,
        });
        this.arenasByName[this.rootArena.name] = this.rootArena;
        this.rootNode = NodeFactory.createRootNode(this.rootArena);
    }
    registerArena(arenaOptions, markers, parentArenas) {
        const arena = ArenaFactory.createChild(arenaOptions);
        this.arenas.push(arena);
        this.arenasByName[arena.name] = arena;
        if (!arena.inline && parentArenas) {
            parentArenas.forEach((parentName) => {
                const parentArena = this.arenasByName[parentName];
                if (!parentArena) {
                    throw new Error(`Arena "${parentName}" not found`);
                }
                if (!parentArena.hasChildren) {
                    throw new Error(`Arena "${parentName}" has not children`);
                }
                parentArena.addAllowedChild(arena);
            });
        }
        if (markers) {
            markers.forEach(({ tag, attributes }) => {
                if (!this.areanMarks[tag]) {
                    this.areanMarks[tag] = [];
                }
                this.areanMarks[tag].push({
                    attributes,
                    arena,
                });
                this.areanMarks[tag].sort((a, b) => b.attributes.length - a.attributes.length);
            });
        }
        return arena;
    }
    registerFormating(formating, markers) {
        this.formatings.push(formating);
        this.formatingsByName[formating.name] = formating;
        markers.forEach(({ tag, attributes }) => {
            if (!this.formatingMarks[tag]) {
                this.formatingMarks[tag] = [];
            }
            this.formatingMarks[tag].push({
                attributes,
                formating,
            });
        });
        return formating;
    }
    getArena(name) {
        return this.arenasByName[name];
    }
    getArenas() {
        return this.arenas;
    }
    getFormatings() {
        return this.formatingsByName;
    }
    getArenaMarks(tagName) {
        return this.areanMarks[tagName];
    }
    getFormatingMarks(tagName) {
        return this.formatingMarks[tagName];
    }
    /** */
    applyMiddlewares(cursor, text) {
        let success = false;
        let result = false;
        let cur = cursor;
        if (cursor.node.arena.hasText) {
            const { middlewares } = cursor.node.arena;
            for (let i = 0; i < middlewares.length; i += 1) {
                [success, cur] = middlewares[i](this.asm.textarena, cur, text);
                result = success || result;
            }
        }
        return [result, cur];
    }
    // #endregion
    // #region Exporting
    getDataHtml() {
        return this.model.getOutputHtml(this.getFormatings());
    }
    getTemplate() {
        return this.model.getTemplate(this.getFormatings());
    }
    getPublicHtml() {
        return this.model.getPublicHtml(this.getFormatings());
    }
    getPlainTextOfSelection(selection) {
        if (selection.isCollapsed()) {
            return '';
        }
        const result = [];
        this.runNodesOfSelection(selection, (node, start, end) => {
            result.push(node.getPlainText(start, end));
        });
        return result.join('\n');
    }
    getOutputHtmlOfSelection(selection) {
        if (selection.isCollapsed()) {
            return '';
        }
        let result = '';
        const frms = this.getFormatings();
        const oneNode = selection.isSameNode();
        this.runNodesOfSelection(selection, (node, start, end) => {
            let nodeContent = node.getOutputHtml(frms, start, end);
            if (node.hasParent && node.parent.group && !oneNode) {
                if (start !== undefined || node.isFirstChild()) {
                    nodeContent = node.parent.getOpenTag() + nodeContent;
                }
                if (end !== undefined || node.isLastChild()) {
                    nodeContent += node.parent.getCloseTag();
                }
            }
            result += nodeContent;
        });
        if (oneNode) {
            const { startNode, startOffset, endOffset } = selection;
            if (startOffset === 0 && endOffset === startNode.getTextLength() - 1) {
                const { parent } = startNode;
                result = parent.getOpenTag() + result + parent.getCloseTag();
            }
        }
        return result;
    }
    getJson() {
        return '';
    }
    // #endregion
    /** */
    createChildNode(arena, isNew = false) {
        const node = NodeFactory.createChildNode(arena, this.registry, isNew);
        return node;
    }
    /** */
    getTextCursor(node, offset) {
        if (node.hasText) {
            return {
                node,
                offset,
            };
        }
        if (node.hasChildren && node.arena.arenaForText) {
            const child = node.getChild(offset - 1);
            if (child) {
                const cursor = this.getOrCreateNodeForText(child, undefined, true);
                if (cursor) {
                    return cursor;
                }
            }
            const cursor2 = this.getOrCreateNodeForText(node, offset);
            if (cursor2) {
                return cursor2;
            }
        }
        throw new Error('Text cursor not found');
    }
    /**
     * TODO split in two methods: with creating and without
     * Find or create text node in children or ancestors.
     * @param node AnyArenaNode
     * @param offset number
     * @returns ArenaCursorText | undefined
     */
    getOrCreateNodeForText(node, offset, onlyChild = false, forceCreate = false) {
        if (node.hasText) {
            return {
                node,
                offset: offset === undefined ? node.getTextLength() : offset,
            };
        }
        if (node.hasChildren && node.arena.arenaForText) {
            if (!forceCreate && (node.protected || !offset)) { // offset === 0 or undefined
                if (offset === undefined) {
                    for (let i = node.children.length - 1; i >= 0; i -= 1) {
                        const cursor = this.getOrCreateNodeForText(node.children[i], undefined, true);
                        if (cursor) {
                            return cursor;
                        }
                    }
                }
                else {
                    for (let i = 0; i < node.children.length; i += 1) {
                        const cursor = this.getOrCreateNodeForText(node.children[i], 0, true);
                        if (cursor) {
                            return cursor;
                        }
                    }
                }
            }
            if (!node.protected) {
                let newNode = this.createChildNode(node.arena.arenaForText);
                newNode = node.insertNode(newNode, offset);
                if (newNode) {
                    return this.getOrCreateNodeForText(newNode);
                }
            }
        }
        if (!onlyChild && node.hasParent) {
            return this.getOrCreateNodeForText(node.parent, offset === undefined ? node.getIndex() : node.getIndex() + 1);
        }
        return undefined;
    }
    /**
     * Try to create new node described by arena in given parent and offset.
     * @param arena ChildArena
     * @param parent ParentArenaNode
     * @param offset number
     * @returns ChildArenaNode | undefined
     */
    createAndInsertNode(arena, parent, offset, before = false, onlyChild = false, isNew = false) {
        if (parent.hasText) {
            if (onlyChild) {
                return undefined;
            }
            const { node, offset: parentOffset } = this.splitTextNode({ node: parent, offset });
            return this.createAndInsertNode(arena, node, parentOffset, parentOffset === 0, false, isNew);
        }
        if (parent.single) {
            return undefined;
        }
        if (parent.isAllowedNode(arena)) {
            const newNode = this.createChildNode(arena, isNew);
            return parent.insertNode(newNode, offset);
        }
        if (parent.protected) {
            if (before) {
                for (let i = offset; i >= 0; i -= 1) {
                    const child = parent.getChild(i);
                    if (child && child.hasChildren) {
                        const node = this.createAndInsertNode(arena, child, parent.children.length, true, true, isNew);
                        if (node) {
                            return node;
                        }
                    }
                }
            }
            else {
                for (let i = offset; i < parent.children.length; i += 1) {
                    const child = parent.getChild(i);
                    if (child && child.hasChildren) {
                        const node = this.createAndInsertNode(arena, child, 0, false, true, isNew);
                        if (node) {
                            return node;
                        }
                    }
                }
            }
        }
        if (!onlyChild && parent.hasParent) {
            this.splitMediatorNode(parent, offset);
            const secondParent = this.splitMediatorNode(parent, offset);
            let parentOffset = offset === 0 ? parent.getIndex() : parent.getIndex() + 1;
            if (secondParent) {
                parentOffset = secondParent.getIndex();
            }
            return this.createAndInsertNode(arena, parent.parent, parentOffset, before, false, isNew);
        }
        return undefined;
    }
    /**
     * Find in model tree the text node with global id equal given id parameter.
     * @param id: string
     * @returns ArenaNodeText | undefined
     */
    getTextNodeById(id) {
        const node = this.getNodeById(id);
        if (node === null || node === void 0 ? void 0 : node.hasText) {
            return node;
        }
        return undefined;
    }
    /**
     * Find in model tree the node with global id equal given id parameter.
     * @param id: string
     * @returns AnyArenaNode | undefined
     */
    getNodeById(id) {
        // return this.registry.get(id);
        const path = id.split('.').map((i) => parseInt(i, 10));
        let cursor = this.model;
        if (path.shift() === 0) {
            path.forEach((i) => {
                if (cursor && cursor.hasChildren) {
                    cursor = cursor.children[i];
                }
                else {
                    cursor = undefined;
                }
            });
        }
        return cursor;
    }
    insertHtml(selection, html) {
        let newSelection = selection;
        if (!selection.isCollapsed()) {
            newSelection = this.removeSelection(selection, 'backward');
        }
        const result = this.asm.parser.insertHtmlToModel(html, newSelection.startNode, newSelection.startOffset);
        if (result) {
            const cursor = this.getTextCursor(result[0], result[1]);
            newSelection.setCursor(cursor);
            // newSelection.setBoth(result[0] as ArenaNodeText, result[1]);
        }
        return newSelection;
    }
    insertTextToModel(selection, text, typing = false) {
        let newSelection = selection;
        if (!selection.isCollapsed()) {
            newSelection = this.removeSelection(selection, 'backward');
        }
        if (typing) {
            const cursor = newSelection.startNode.insertText(text, newSelection.startOffset, true);
            newSelection.setCursor(cursor);
        }
        else {
            const lines = text.split('\n');
            const firstLine = lines.shift();
            if (firstLine !== undefined) {
                let cursor = newSelection
                    .startNode.insertText(firstLine, newSelection.startOffset);
                lines.forEach((line) => {
                    if (cursor) {
                        const nextArena = cursor.node.arena.nextArena || cursor.node.arena;
                        const newNode = this.createAndInsertNode(nextArena, cursor.node.parent, cursor.node.getIndex() + 1);
                        if (newNode) {
                            cursor = newNode.insertText(line, 0);
                        }
                        else {
                            cursor = undefined;
                        }
                    }
                });
                if (cursor) {
                    newSelection.setCursor(cursor);
                }
            }
        }
        return newSelection;
    }
    removeNodeById(id) {
        const node = this.getNodeById(id);
        if (node && node.hasParent) {
            this.asm.eventManager.fire('removeNode', node);
            return node.remove();
        }
        return undefined;
    }
    /**
     * Remove selected text and all nodes between selections ends
     * @param selection
     * @param direction
     * @returns
     */
    removeSelection(selection, direction) {
        const newSelection = selection;
        if (selection.isCollapsed()) {
            const { node, offset } = newSelection.getCursor();
            if (direction === 'forward') {
                if (node.getTextLength() === offset) {
                    // const nextSibling = this.getNextSibling(node);
                    const nextSibling = node.parent.getChild(node.getIndex() + 1);
                    if (!nextSibling) {
                        return newSelection;
                    }
                    if (nextSibling.hasChildren && nextSibling.protected) {
                        return newSelection;
                    }
                    if (nextSibling.single) {
                        this.asm.eventManager.fire('removeNode', nextSibling);
                        nextSibling.remove();
                        return newSelection;
                    }
                    const cursor = this.getOrCreateNodeForText(nextSibling, 0);
                    // const cursor = nextSibling.getTextCursor(0);
                    if (cursor) {
                        if (node.getTextLength() === 0) {
                            this.asm.eventManager.fire('removeNode', node);
                            node.remove();
                            newSelection.setCursor(cursor);
                        }
                        else {
                            node.insertText(cursor.node.cutText(0), offset);
                            this.asm.eventManager.fire('removeNode', node);
                            cursor.node.remove();
                        }
                    }
                }
                else {
                    node.removeText(offset, offset + 1);
                }
            }
            if (direction === 'backward') {
                if (offset === 0) {
                    // At the begining of the text node
                    const newNode = this.getOutFromMediator(node, true);
                    if (newNode) {
                        newSelection.setBoth(newNode, 0);
                    }
                    else {
                        // nowhere to get out
                        const prevSibling = node.parent.getChild(node.getIndex() - 1);
                        if (!prevSibling) {
                            // TODO go to prev parent
                            return newSelection;
                        }
                        if (prevSibling.hasChildren && prevSibling.protected) {
                            const cursor = this.getOrCreateNodeForText(prevSibling);
                            if (cursor) {
                                newSelection.setCursor(cursor);
                            }
                            return newSelection;
                        }
                        if (prevSibling.single) {
                            this.asm.eventManager.fire('removeNode', prevSibling);
                            prevSibling.remove();
                            return newSelection;
                        }
                        if (prevSibling.hasText && prevSibling.getTextLength() === 0) {
                            this.asm.eventManager.fire('removeNode', prevSibling);
                            prevSibling.remove();
                            return newSelection;
                        }
                        // const cursor = prevSibling.getTextCursor(-1);
                        const cursor = this.getOrCreateNodeForText(prevSibling);
                        if (cursor) {
                            if (node.getTextLength() !== 0) {
                                cursor.node.insertText(node.getText(), cursor.offset);
                            }
                            this.asm.eventManager.fire('removeNode', node);
                            node.remove();
                            newSelection.setCursor(cursor);
                        }
                    }
                }
                else {
                    node.removeText(offset - 1, offset);
                    newSelection.setBoth(node, offset - 1);
                }
            }
            return newSelection;
        }
        const toRemove = [];
        this.runNodesOfSelection(newSelection, (node, start, end) => {
            if (start === undefined && end === undefined) {
                if (node.hasParent) {
                    toRemove.push(node);
                }
            }
            else if (node.hasText) {
                node.removeText(start || 0, end);
            }
        });
        toRemove.forEach((node) => {
            this.asm.eventManager.fire('removeNode', node);
            node.remove();
        });
        const { startNode, startOffset, endNode, } = newSelection;
        if (startNode !== endNode) {
            if (startNode.getTextLength() === 0) {
                this.asm.eventManager.fire('removeNode', startNode);
                startNode.remove();
                if (endNode.getTextLength() === 0) {
                    this.asm.eventManager.fire('removeNode', endNode);
                    const aCursor = endNode.remove();
                    const cursor = this.getOrCreateNodeForText(aCursor.node, aCursor.offset);
                    if (!cursor) {
                        throw Error('Cannot create text node');
                    }
                    newSelection.setCursor(cursor);
                }
                else {
                    newSelection.setBoth(endNode, 0);
                }
            }
            else {
                startNode.insertText(endNode.getText(), startOffset);
                this.asm.eventManager.fire('removeNode', endNode);
                endNode.remove();
                newSelection.setBoth(startNode, startOffset);
            }
        }
        else {
            newSelection.setBoth(startNode, startOffset);
        }
        return newSelection;
    }
    /**
     * Remove selected nodes and split selected node in two nodes.
     * If current node is empty, try split uprotected parent in two
     * or get out of protected node, if cursor in the end of parent.
     * @param selection
     * @returns
     */
    breakSelection(selection) {
        let newSelection = selection;
        if (!selection.isCollapsed()) {
            newSelection = this.removeSelection(selection, 'backward');
        }
        const cursor = newSelection.getCursor();
        const { node, offset } = cursor;
        const { parent, arena } = node;
        const nextArena = arena.nextArena || arena;
        if (offset === 0) {
            // At the begining of the text node
            if (node.isEmpty()) {
                // Text is empty. Trying to get out from this node (ex. in a list)
                const outNode = this.getOutFromMediator(node);
                if (outNode) {
                    newSelection.setBoth(outNode, 0);
                }
                else {
                    // nowhere to get out
                    const newNode = this.createAndInsertNode(nextArena, parent, node.getIndex() + 1);
                    if (newNode) {
                        // const cursor = newNode.getTextCursor(0);
                        const secondCursor = this.getOrCreateNodeForText(newNode, 0);
                        if (secondCursor) {
                            newSelection.setCursor(secondCursor);
                        }
                    }
                }
            }
            else {
                const newNode = this.createAndInsertNode(nextArena, parent, node.getIndex(), true);
                if (newNode) {
                    this.getOrCreateNodeForText(newNode, 0);
                }
            }
        }
        else {
            // const secondCursor = this.splitTextNode(cursor);
            // newSelection.setCursor();
            const newNode = this.createAndInsertNode(nextArena, parent, node.getIndex() + 1);
            if (newNode) {
                const newCursor = this.getOrCreateNodeForText(newNode, 0);
                // const cursor = newNode.getTextCursor(0);
                if (newCursor) {
                    const text = node.cutText(offset);
                    const cursor2 = newCursor.node.insertText(text, newCursor.offset);
                    newSelection.setCursor(Object.assign(Object.assign({}, cursor2), { offset: 0 }));
                }
            }
        }
        return newSelection;
    }
    splitTextNode({ node, offset }) {
        const { parent, arena } = node;
        const before = offset <= 0;
        const newOffset = node.getIndex() + (before ? 0 : 1);
        const nextArena = before ? arena : arena.nextArena || arena;
        const newNode = this.createAndInsertNode(nextArena, parent, newOffset);
        if (newNode) {
            const newCursor = this.getOrCreateNodeForText(newNode, 0);
            if (newCursor) {
                const text = node.cutText(offset);
                newCursor.node.insertText(text, newCursor.offset);
            }
        }
        return {
            node: parent,
            offset: newOffset,
        };
    }
    /** */
    moveChild(selection, direction) {
        if (selection.isSameNode()) {
            const node = selection.startNode;
            const index = node.getIndex();
            if (direction === 'up' && index === 0) {
                return selection;
            }
            if (direction === 'down' && node.isLastChild()) {
                return selection;
            }
            const children = node.parent.cutChildren(index, 1);
            if (direction === 'up') {
                node.parent.insertChildren(children, index - 1);
            }
            else {
                node.parent.insertChildren(children, index + 1);
            }
        }
        return selection;
    }
    /** */
    insertBeforeSelected(selection, arena) {
        if (!selection.isSameNode()) {
            return selection;
        }
        const { node } = selection.getCursor();
        if (node.parent.isAllowedNode(arena)) {
            this.createAndInsertNode(arena, node.parent, node.getIndex(), false, false, true);
        }
        return selection;
    }
    /**
     *
     * @param selection
     * @param arena
     * @returns
     */
    applyArenaToSelection(selection, arena) {
        if (arena.hasText) {
            return this.applyTextArenaToSelection(selection, arena);
        }
        if (arena.hasChildren) {
            if (arena.protected) {
                return this.applyProtectedArenaToSelection(selection, arena);
            }
            if (arena.group) {
                return this.toggleGroupArenaForSelection(selection, arena);
            }
            return this.toggleSimpleMediatorArenaForSelection(selection, arena);
        }
        return selection;
    }
    /**
     * Apply given formation to all selected nodes, including child nodes.
     * @param selection ArenaSelection
     * @param formating ArenaFormating
     * @returns ArenaSelection
     */
    applyFormationToSelection(selection, formating) {
        if (selection.isCollapsed()) {
            const { node, offset } = selection.getCursor();
            node.togglePromiseFormating(formating, offset);
        }
        else {
            this.runNodesOfSelection(selection, (node, start, end) => {
                if (node.hasText) {
                    node.toggleFormating(formating.name, start || 0, end || node.getTextLength());
                }
                if (node.hasChildren) {
                    this.runOfChildren(node, (n) => {
                        if (n.hasText) {
                            n.toggleFormating(formating.name, 0, n.getTextLength());
                        }
                    });
                }
            });
        }
        return selection;
    }
    clearFormationInSelection(selection) {
        this.runNodesOfSelection(selection, (node, start, end) => {
            if (node.hasText) {
                node.clearFormatings(start || 0, end || node.getTextLength());
                if (node.arena.nextArena
                    && node.arena.nextArena.hasText
                    && node.arena.nextArena !== node.arena
                    && node.parent.isAllowedNode(node.arena.nextArena)) {
                    const newNode = this.createChildNode(node.arena.nextArena);
                    if (newNode) {
                        newNode.insertText(node.getText(), 0);
                        node.parent.insertNode(newNode, node.getIndex());
                        if (selection.startNode === node) {
                            selection.setStartNode(newNode, selection.startOffset);
                        }
                        if (selection.endNode === node) {
                            selection.setEndNode(newNode, selection.endOffset);
                        }
                        node.remove();
                    }
                }
            }
            if (node.hasChildren) {
                this.runOfChildren(node, (n) => {
                    if (n.hasText) {
                        n.clearFormatings(start || 0, end || n.getTextLength());
                    }
                });
            }
        });
        return selection;
    }
    // #region [Inline Node]
    addInlineNode(selection, arena) {
        if (selection.isSameNode() && !selection.isCollapsed()) {
            const { startNode, startOffset, endOffset } = selection;
            return startNode.addInlineNode(arena, startOffset, endOffset);
        }
        return undefined;
    }
    getInlineNode(selection, arena) {
        if (selection.isSameNode()) {
            const { startNode, startOffset, endOffset } = selection;
            return startNode.getInlineNode(arena, startOffset, endOffset);
        }
        return undefined;
    }
    removeInlineNode(selection, node) {
        if (selection.isSameNode()) {
            const { startNode } = selection;
            return startNode.removeInlineNode(node);
        }
        return undefined;
    }
    updateInlineNode(selection, node) {
        if (selection.isSameNode() && !selection.isCollapsed()) {
            const { startNode, startOffset, endOffset } = selection;
            startNode.updateInlineNode(node, startOffset, endOffset);
        }
    }
    // #endregion
    /**
     * Call callback function for all selected nodes without any child nodes.
     * @param selection ArenaSelection
     * @param callback (node: ChildArenaNode, start?: number, end?: number) => void
     * @returns Common ancestor with start and end indexes of selected nodes.
     */
    runNodesOfSelection(selection, callback) {
        const { startNode, startOffset, endNode, endOffset, } = selection;
        if (selection.isSameNode()) {
            if (callback) {
                callback(startNode, startOffset, endOffset);
            }
            const index = startNode.getIndex();
            return [startNode.parent, index, index];
        }
        const commonAncestorCursor = this.getCommonAncestor(startNode, endNode);
        if (!commonAncestorCursor) {
            return undefined;
        }
        if (!callback) {
            return commonAncestorCursor;
        }
        const [commonAncestor] = commonAncestorCursor;
        let startCursor = startNode.getParent();
        const startNodes = [];
        while (startCursor && startCursor.node !== commonAncestor) {
            const len = startCursor.node.children.length;
            for (let i = startCursor.offset + 1; i < len; i += 1) {
                const child = startCursor.node.getChild(i);
                if (child) {
                    startNodes.push(child);
                }
            }
            if (startCursor.node.hasParent) {
                startCursor = startCursor.node.getParent();
            }
            else {
                startCursor = undefined;
            }
        }
        let endCursor = endNode.getParent();
        const endNodes = [];
        while (endCursor && endCursor.node !== commonAncestor) {
            for (let i = 0; i < endCursor.offset; i += 1) {
                const child = endCursor.node.getChild(i);
                if (child) {
                    endNodes.push(child);
                }
            }
            if (endCursor.node.hasParent) {
                endCursor = endCursor.node.getParent();
            }
            else {
                endCursor = undefined;
            }
        }
        callback(startNode, startOffset);
        startNodes.forEach((n) => callback(n));
        if (startCursor && endCursor) {
            for (let i = startCursor.offset + 1; i < endCursor.offset; i += 1) {
                const child = commonAncestor.getChild(i);
                if (child) {
                    callback(child);
                }
            }
        }
        endNodes.reverse().forEach((n) => callback(n));
        callback(endNode, undefined, endOffset);
        return commonAncestorCursor;
    }
    /** Split non protected node into two.
     * If offset in start or end of the givven node splitting is not necessary. */
    splitMediatorNode(node, offset) {
        if (offset === 0 || offset >= node.children.length) {
            return undefined;
        }
        if (node.arena.protected) {
            return undefined;
        }
        if (node.parent.arena.protected) {
            return undefined;
        }
        const newNode = this.createAndInsertNode(node.arena, node.parent, node.getIndex() + 1);
        if (!newNode || !(newNode.hasChildren)) {
            return undefined;
        }
        newNode.insertChildren(node.cutChildren(offset));
        return newNode;
    }
    /**  */
    getOutFromMediator(node, onlyGroup = false) {
        const { parent } = node;
        if (!parent.hasParent) {
            return undefined;
        }
        if (onlyGroup && parent.protected) {
            return undefined;
        }
        const index = node.getIndex();
        if (onlyGroup && !parent.group && index > 0) {
            return undefined;
        }
        const grandpaCursor = parent.getUnprotectedParent();
        if (grandpaCursor && grandpaCursor.node.arena.arenaForText) {
            // Try to get out from this node (ex. in a list)
            if (parent.hasParent && parent.parent === grandpaCursor.node) {
                // try to separate
                this.splitMediatorNode(parent, index);
            }
            else if (!node.isLastChild()
                || parent.children.length < 2
                || !node.isEmpty()) {
                return undefined;
            }
            let { offset } = grandpaCursor;
            if (index > 0 || parent.children.length === 1) {
                offset += 1;
            }
            const cursor = this.getOrCreateNodeForText(grandpaCursor.node, offset, false, true);
            // const cursor = grandpaCursor.node.insertText(text, offset);
            if (cursor) {
                const text = node.cutText(0);
                cursor.node.insertText(text, cursor.offset);
                node.remove();
                return cursor.node;
            }
        }
        return undefined;
    }
    // protected getNextSibling(
    //   node: AnyArenaNode,
    // ): ArenaNodeText | ArenaNodeSingle | undefined {
    //   if (!(node.hasParent)) {
    //     return undefined;
    //   }
    //   const next = node.parent.getChild(node.getIndex() + 1);
    //   if (next && next.single) {
    //     return next;
    //   }
    //   if (next?.hasChildren && next.protected) {
    //     return undefined;
    //   }
    //   if (next) {
    //     const cursor = this.getOrCreateNodeForText(next, 0);
    //     if (cursor) {
    //       // return next.getTextCursor(0).node;
    //       return cursor.node;
    //     }
    //   }
    //   return this.getNextSibling(node.parent);
    // }
    /** */
    runOfChildren(node, callback) {
        if (node.hasChildren) {
            node.children.forEach((child) => {
                this.runOfChildren(child, callback);
            });
        }
        else {
            callback(node);
        }
    }
    // #region getCommonAncestor
    getCommonAncestor(nodeA, nodeB) {
        if (nodeA === nodeB) {
            return undefined;
        }
        const ancestorsForA = this.getAncestors(nodeA);
        const ancestorsForB = this.getAncestors(nodeB);
        const commonMaxDeep = Math.min(ancestorsForA.length, ancestorsForB.length);
        if (commonMaxDeep === 0) {
            return undefined;
        }
        let result;
        for (let i = 0; i < commonMaxDeep; i += 1) {
            if (ancestorsForA[i].node === ancestorsForB[i].node) {
                result = [ancestorsForA[i].node, ancestorsForA[i].offset, ancestorsForB[i].offset];
            }
            else {
                break;
            }
        }
        return result;
    }
    getAncestors(node) {
        if (node.hasParent) {
            return [
                ...this.getAncestors(node.parent),
                { node: node.parent, offset: node.getIndex() },
            ];
        }
        return [];
    }
    // #endregion
    // #region [Apply text Arena]
    applyTextArenaToSelection(selection, arena) {
        const { startNode, startOffset, endNode, endOffset, } = selection;
        const newSelection = selection.clone();
        const toTransform = [];
        this.runNodesOfSelection(selection, (node) => {
            if (node.arena !== arena) {
                toTransform.push(node);
            }
        });
        toTransform.forEach((n) => {
            const newNode = this.transformNode(n, arena);
            if (newNode) {
                if (n === startNode) {
                    newSelection.setStartNode(newNode, startOffset);
                }
                if (n === endNode) {
                    newSelection.setEndNode(newNode, endOffset);
                }
            }
        });
        return newSelection;
    }
    transformNode(node, arena) {
        if (node.hasText) {
            const cursor = this.getParentWhoCanCreateNode(node, arena);
            if (cursor) {
                const newNode = this.createAndInsertNode(arena, cursor.node, cursor.offset);
                if (newNode) {
                    newNode.insertText(node.getText(), 0);
                    node.remove();
                    return newNode;
                }
            }
        }
        else if (node.hasChildren) {
            [...node.children].forEach((child) => this.transformNode(child, arena));
        }
        return undefined;
    }
    getParentWhoCanCreateNode(node, arena, after = false) {
        if (!node.hasParent) {
            return undefined;
        }
        const { parent } = node;
        if (parent.arena.protected) {
            return undefined;
        }
        const offset = node.getIndex() + (after ? 1 : 0);
        if (parent.isAllowedNode(arena)) {
            return {
                node: parent,
                offset,
            };
        }
        if (parent.hasParent) {
            //  && parent.group
            const secondParent = this.splitMediatorNode(parent, node.getIndex());
            if (secondParent) {
                return this.getParentWhoCanCreateNode(secondParent, arena);
            }
            if (offset === 0) {
                // try to create before parent
                return this.getParentWhoCanCreateNode(parent, arena);
            }
            return this.getParentWhoCanCreateNode(parent, arena, true);
        }
        return undefined;
    }
    // [Apply text Arena]
    // #endregion
    toggleGroupArenaForSelection(selection, arena) {
        const { startNode, startOffset, endNode, endOffset, } = selection;
        const newSelection = selection.clone();
        const toWrap = [];
        const toUnwrap = [];
        this.runNodesOfSelection(selection, (node) => {
            if (node.hasText) {
                if (node.parent.isAllowedNode(arena)) {
                    toWrap.push(node);
                }
                else if (node.parent.group && node.parent.hasParent) {
                    if (node.parent.arena === arena) {
                        toUnwrap.push(node.parent);
                    }
                    else if (node.parent.parent.isAllowedNode(arena)) {
                        toWrap.push(node.parent);
                    }
                }
            }
            else if (node.hasChildren) {
                if (node.group) {
                    if (node.arena === arena) {
                        toUnwrap.push(node);
                    }
                    else {
                        toWrap.push(node);
                    }
                }
            }
        });
        if (toWrap.length > 0) {
            toWrap.forEach((node) => {
                if (node.hasText) {
                    if (node.parent.isAllowedNode(arena)) {
                        const newNode = this.createAndInsertNode(arena, node.parent, node.getIndex());
                        const cursor = this.getOrCreateNodeForText(newNode);
                        if (cursor) {
                            cursor.node.insertText(node.getText(), cursor.offset);
                            if (node === startNode) {
                                newSelection.setStartNode(cursor.node, startOffset);
                            }
                            if (node === endNode) {
                                newSelection.setEndNode(cursor.node, endOffset);
                            }
                            node.remove();
                        }
                    }
                    else if (node.parent.group && node.parent.hasParent) {
                        if (node.parent.arena !== arena
                            && node.parent.parent.isAllowedNode(arena)) {
                            const newNode = this.createAndInsertNode(arena, node.parent.parent, node.parent.getIndex());
                            if (newNode) {
                                newNode.insertChildren(node.parent.cutChildren(0));
                                node.parent.remove();
                                // node.parent.parent.mergeChildren();
                            }
                        }
                    }
                }
                else if (node.hasChildren) {
                    if (node.group) {
                        if (node.arena !== arena) {
                            const newNode = this.createAndInsertNode(arena, node.parent, node.getIndex());
                            if (newNode) {
                                newNode.insertChildren(node.cutChildren(0));
                                node.remove();
                                // node.parent.mergeChildren();
                            }
                        }
                    }
                }
            });
        }
        else if (toUnwrap.length > 0) {
            toUnwrap.forEach((node) => {
                if (node.hasText) {
                    if (node.parent.group && node.parent.hasParent) {
                        if (node.parent.arena === arena) {
                            const { parent } = node;
                            const grandpa = parent;
                            let offset = parent.getIndex();
                            const children = parent.cutChildren(0);
                            children.forEach((child) => {
                                if (grandpa.isAllowedNode(child.arena)) {
                                    grandpa.insertChildren([child], offset);
                                    offset += 1;
                                }
                                else if (child.hasText) {
                                    const newNode = this.createAndInsertNode(grandpa.arena.arenaForText, grandpa, offset);
                                    if (newNode) {
                                        offset += 1;
                                        const cursor = this.getOrCreateNodeForText(newNode);
                                        if (cursor) {
                                            cursor.node.insertText(child.getText(), cursor.offset);
                                            if (child === startNode) {
                                                newSelection.setStartNode(cursor.node, startOffset);
                                            }
                                            if (child === endNode) {
                                                newSelection.setEndNode(cursor.node, endOffset);
                                            }
                                        }
                                    }
                                }
                            });
                            parent.remove();
                            // parent.mergeChildren();
                        }
                    }
                }
                else if (node.hasChildren) {
                    if (node.group) {
                        if (node.arena === arena) {
                            const { parent } = node;
                            let offset = node.getIndex();
                            const children = node.cutChildren(0);
                            children.forEach((child) => {
                                if (parent.isAllowedNode(child.arena)) {
                                    parent.insertChildren([child], offset);
                                    offset += 1;
                                }
                                else if (child.hasText) {
                                    const newNode = this.createAndInsertNode(parent.arena.arenaForText, node.parent, offset);
                                    if (newNode) {
                                        offset += 1;
                                        const cursor = this.getOrCreateNodeForText(newNode);
                                        if (cursor) {
                                            cursor.node.insertText(child.getText(), cursor.offset);
                                            if (child === startNode) {
                                                newSelection.setStartNode(cursor.node, startOffset);
                                            }
                                            if (child === endNode) {
                                                newSelection.setEndNode(cursor.node, endOffset);
                                            }
                                        }
                                    }
                                }
                            });
                            node.remove();
                            // node.mergeChildren();
                        }
                    }
                }
            });
        }
        return newSelection;
    }
    /**
     * Wrap selected nodes by protected Node described by given Arena.
     * All selected children will be took of and inserted in new Node, if it allow Arena.
     * Rest children will be back in them ancestor after new Node.
     * @param selection ArenaSelection
     * @param arena ArenaAncestor
     * @returns ArenaSelection
     */
    applyProtectedArenaToSelection(selection, arena) {
        const commonAncestorCursor = this.runNodesOfSelection(selection);
        if (!commonAncestorCursor) {
            return selection;
        }
        const [commonAncestor, start, end] = commonAncestorCursor;
        if (!commonAncestor.isAllowedNode(arena)) {
            return selection;
        }
        const newNode = this.createAndInsertNode(arena, commonAncestor, start);
        if (newNode && newNode.hasChildren) {
            const childrentToWrap = commonAncestor.cutChildren(start + 1, end - start + 1);
            const rest = newNode.insertChildren(childrentToWrap);
            if (rest.length) {
                commonAncestor.insertChildren(rest, newNode.getIndex() + 1);
            }
            // const cursor = newNode.getTextCursor(0);
            const cursor = this.getOrCreateNodeForText(newNode, 0);
            if (cursor) {
                selection.setCursor(cursor);
            }
        }
        return selection;
    }
    // #region [Toggle simple non group Arena]
    toggleSimpleMediatorArenaForSelection(selection, arena) {
        const commonAncestorCursor = this.runNodesOfSelection(selection);
        if (!commonAncestorCursor) {
            return selection;
        }
        const newSelection = selection.clone();
        const [commonAncestor, start, end] = commonAncestorCursor;
        const ancestor = this.tryToFindAncestor(commonAncestor, arena);
        if (ancestor) {
            this.unwrapNode(ancestor);
            return newSelection;
        }
        let dad;
        let childrentToWrap;
        let offset = start;
        if (commonAncestor.isAllowedNode(arena)) {
            dad = commonAncestor;
            childrentToWrap = dad.cutChildren(start, end - start + 1);
            offset = start;
        }
        else {
            const toWrap = this.tryToFindAncestor2(commonAncestor, arena);
            if (!toWrap) {
                return newSelection;
            }
            dad = toWrap.parent;
            offset = toWrap.getIndex();
            childrentToWrap = dad.cutChildren(offset, 1);
        }
        let wrap = false;
        for (let i = 0; i < childrentToWrap.length; i += 1) {
            const child = childrentToWrap[i];
            if (arena.allowedArenas.includes(child.arena)
            // || (arena.arenaForText && 'hasText' in child)
            ) {
                wrap = true;
                break;
            }
        }
        if (!wrap) {
            childrentToWrap.forEach((child) => {
                if (child.arena === arena && child.hasChildren) {
                    const children = child.cutChildren(0);
                    dad.insertChildren(children, offset);
                    offset += children.length;
                }
                else {
                    dad.insertChildren([child], offset);
                    offset += 1;
                }
            });
        }
        else {
            let toWrap = [];
            for (let i = 0; i < childrentToWrap.length; i += 1) {
                const child = childrentToWrap[i];
                if (arena.allowedArenas.includes(child.arena)
                // || (arena.arenaForText && 'hasText' in child)
                ) {
                    toWrap.push(child);
                }
                else {
                    if (toWrap.length > 0) {
                        const newNode = this.createAndInsertNode(arena, dad, offset);
                        // const newNode = dad.createAndInsertNode(arena, offset);
                        if (newNode) {
                            newNode.insertChildren(toWrap, 0);
                            offset += 1;
                            toWrap = [];
                        }
                        // if (toWrap.length === i) {
                        //   newSelection.setStartNode(newNode.getChild(0), startOffset);
                        // }
                    }
                    dad.insertChildren([child], offset);
                    offset += 1;
                }
            }
            if (toWrap.length > 0) {
                const newNode = this.createAndInsertNode(arena, dad, offset);
                if (newNode) {
                    newNode.insertChildren(toWrap, 0);
                }
                // if (toWrap.length === childrentToWrap.length) {
                //   newSelection.setStartNode(newNode.getChild(0), startOffset);
                // }
                // newSelection.setEndNode(newNode.getChild(toWrap.length - 1), endOffset);
            }
            dad.mergeChildren(0);
        }
        return newSelection;
    }
    tryToFindAncestor(node, arena) {
        if (node.hasParent) {
            if (node.arena === arena && node.hasChildren) {
                return node;
            }
            return this.tryToFindAncestor(node.parent, arena);
        }
        return undefined;
    }
    tryToFindAncestor2(node, arena) {
        if (node.hasParent) {
            if (node.parent.arena.allowedArenas.includes(arena)) {
                return node;
            }
            return this.tryToFindAncestor2(node.parent, arena);
        }
        return undefined;
    }
    unwrapNode(node) {
        const { parent } = node;
        const offset = node.getIndex();
        const children = node.cutChildren(0);
        const childrenToInsert = [];
        children.forEach((child) => {
            if (parent.isAllowedNode(child.arena)) {
                childrenToInsert.push(child);
            }
            else if (child.hasText) {
                const newNode = this.createChildNode(parent.arena.arenaForText);
                if (newNode) {
                    newNode.insertText(child.getText(), 0);
                    childrenToInsert.push(newNode);
                }
            }
        });
        // TODO catch rest nodes
        parent.insertChildren(childrenToInsert, offset);
        node.remove();
    }
}
//# sourceMappingURL=ArenaModel.js.map