import { BlockType } from '../../models/enums';
import { sanitizeBlock, sanitizeContent } from '../../common/utils/transform';
import { getSelectedRange } from '../../common/utils/selection';
import { getBlockModelById, getBlockContentElement, isChildrenProp } from '../../common/utils/block';
import { decoupleReference, generateUniqueId } from '../../common/utils/common';
import { isNodeAroundSpecialElements } from '../../common/utils/common';
import { findClosestParent, wrapNodeWithTag } from '../../common/utils/dom';
import * as constants from '../../common/constant';
import { BlockFactory } from './index';
/**
 * Manages state and data in the BlockEditor
 */
var StateManager = /** @class */ (function () {
    /**
     * Creates a new StateManager instance
     *
     * @param {BlockManager} parent The parent BlockManager instance
     */
    function StateManager(parent) {
        this.parent = parent;
    }
    /**
     * Updates content in the block model
     *
     * @param {Node} contentElement The content element
     * @param {BlockModel} blockModel The block model to update
     * @returns {void}
     * @hidden
     */
    StateManager.prototype.updateContentModelBasedOnDOM = function (contentElement, blockModel) {
        var childLen = contentElement ? contentElement.childNodes.length : 0;
        if ((childLen === 0) || (childLen === 1 && contentElement.childNodes[0].nodeType === Node.TEXT_NODE)) {
            var textNode = contentElement ? contentElement.firstChild : null;
            var nodeValue = (textNode && textNode.nodeValue) ? textNode.nodeValue : '';
            if (blockModel.content.length === 0) {
                blockModel.content = [BlockFactory.createTextContent({ content: nodeValue })];
            }
            else {
                blockModel.content[0].content = nodeValue;
            }
        }
        else {
            if (!contentElement) {
                return;
            }
            // Case 2: Multiple Child Elements (span, strong, em, etc.)
            var content_1 = [];
            contentElement.childNodes.forEach(function (childNode) {
                if (childNode.nodeType === Node.ELEMENT_NODE) {
                    var element_1 = childNode;
                    var contentModel = blockModel.content.find(function (item) { return item.id === element_1.id; });
                    if (contentModel) {
                        contentModel.content = element_1.textContent;
                        content_1.push(contentModel);
                    }
                    else {
                        content_1.push(BlockFactory.createTextContent({ id: element_1.id, content: element_1.textContent }));
                    }
                }
            });
            /* Model update */
            this.parent.blockService.updateContent(blockModel.id, content_1);
        }
    };
    /**
     * Updates the internal content models based on user typing
     *
     * @param {HTMLElement} blockElement - The block element being updated
     * @param {Event} updateEvent - The original input event that triggered the update (optional)
     * @returns {void}
     * @hidden
     */
    StateManager.prototype.updateContentOnUserTyping = function (blockElement, updateEvent) {
        var _this = this;
        if (!blockElement) {
            return;
        }
        var block = getBlockModelById(blockElement.id, this.parent.getEditorBlocks());
        if (!block || (block && (block.blockType === 'Code' || block.blockType === 'Table'))) {
            return;
        }
        var oldBlockModel = decoupleReference(sanitizeBlock(block));
        var contentElement = this.getContentElementForUpdate(getSelectedRange(), blockElement);
        if (!contentElement) {
            return;
        }
        if (!block.content || contentElement.childNodes.length === 0) {
            this.parent.blockService.updateContent(block.id, []);
        }
        var previousContent;
        var newContentId = '';
        contentElement.childNodes.forEach(function (node) {
            if (node.nodeType === Node.TEXT_NODE) {
                if (node.textContent) {
                    if (block.content.length === 0) {
                        _this.parent.blockService.updateContent(block.id, [BlockFactory.createTextContent({ id: node.parentElement.id || generateUniqueId(constants.CONTENT_ID_PREFIX), content: node.textContent })]);
                        contentElement.id = newContentId = block.content[0].id;
                    }
                    else {
                        if (isNodeAroundSpecialElements(node)) {
                            var index = Array.from(contentElement.childNodes).indexOf(node);
                            block.content.splice(index, 0, BlockFactory.createTextContent({ content: node.textContent }));
                            var span = wrapNodeWithTag(node.cloneNode(true), 'span');
                            span.id = newContentId = block.content[parseInt(index.toString(), 10)].id;
                            contentElement.insertBefore(span, node);
                            contentElement.removeChild(node);
                        }
                        else {
                            previousContent = decoupleReference(sanitizeContent(block.content[0]));
                            block.content[0].content = node.textContent;
                            newContentId = block.content[0].id;
                        }
                    }
                }
            }
            else if (node.nodeType === Node.ELEMENT_NODE) {
                var element_2 = node;
                if (element_2.classList.contains('e-label-chip') || element_2.classList.contains('e-user-chip')) {
                    return;
                }
                if (element_2.innerText || element_2.childNodes.length > 0) {
                    var existingContent = block.content.find(function (c) { return c.id === element_2.id; });
                    if (existingContent) {
                        previousContent = decoupleReference(sanitizeContent(existingContent));
                        existingContent.content = element_2.innerText;
                        newContentId = existingContent.id;
                    }
                    else {
                        var newId = newContentId = element_2.id || generateUniqueId(constants.CONTENT_ID_PREFIX);
                        block.content.push(BlockFactory.createTextContent({ id: newId, content: element_2.innerText }));
                    }
                }
            }
        });
        if (block.blockType !== BlockType.Code) {
            this.cleanUpStaleContents(block, contentElement);
        }
        var clonedBlock = decoupleReference(sanitizeBlock(block));
        this.parent.undoRedoAction.trackContentChangedForUndoRedo(oldBlockModel, clonedBlock);
        this.parent.eventService.addChange({
            action: 'Update',
            data: {
                block: clonedBlock,
                prevBlock: oldBlockModel
            }
        });
        this.parent.observer.notify('triggerBlockChange', this.parent.eventService.getChanges());
        this.updateManagerBlocks();
    };
    StateManager.prototype.getContentElementForUpdate = function (range, blockElement) {
        var contentElement = getBlockContentElement(blockElement);
        if (!contentElement) {
            return null;
        }
        if (blockElement.closest('.' + constants.TOGGLE_BLOCK_CLS)) {
            var toggleHeader = findClosestParent(range.startContainer, '.e-toggle-header');
            if (toggleHeader) {
                contentElement = toggleHeader.querySelector('.' + constants.CONTENT_CLS);
            }
        }
        return contentElement;
    };
    /**
     * Cleans up stale content models from a block
     *
     * @param {BlockModel} block The block model to clean
     * @param {HTMLElement} contentElement The content element to check against
     * @returns {void}
     * @hidden
     */
    StateManager.prototype.cleanUpStaleContents = function (block, contentElement) {
        var idAttributes = ['id', 'data-label-id', 'data-user-id'];
        var domContentIds = new Set();
        for (var _i = 0, _a = Array.from(contentElement.childNodes); _i < _a.length; _i++) {
            var node = _a[_i];
            if (node.nodeType === Node.ELEMENT_NODE) {
                for (var _b = 0, idAttributes_1 = idAttributes; _b < idAttributes_1.length; _b++) {
                    var attr = idAttributes_1[_b];
                    var value = node.getAttribute(attr);
                    if (value) {
                        domContentIds.add(value);
                    }
                }
            }
            else if (node.nodeType === Node.TEXT_NODE) {
                if (node.parentElement) {
                    domContentIds.add(node.parentElement.id);
                }
            }
        }
        var currentContent = block.content.filter(function (c) { return domContentIds.has(c.id); });
        if (currentContent.length !== block.content.length) {
            this.parent.blockService.updateContent(block.id, currentContent);
        }
    };
    /**
     * Populates blocks with unique IDs if they don't have them
     *
     * @param {BlockModel[]} blocks Array of block models
     * @param {string} parentBlockId Optional parent block ID
     * @returns {void}
     * @hidden
     */
    StateManager.prototype.populateUniqueIds = function (blocks, parentBlockId) {
        var _this = this;
        blocks.forEach(function (block) {
            if (!block.id) {
                block.id = generateUniqueId(constants.BLOCK_ID_PREFIX);
            }
            if (parentBlockId) {
                block.parentId = parentBlockId;
            }
            if (block.content) {
                block.content.forEach(function (content) {
                    if (!content.id) {
                        content.id = generateUniqueId(constants.CONTENT_ID_PREFIX);
                    }
                });
            }
            var props = block.properties;
            if ((isChildrenProp(block)) && props.children.length > 0) {
                _this.populateUniqueIds(props.children, block.id);
            }
        });
    };
    /**
     * Updates the property changes to the model
     *
     * @returns {void}
     * @hidden
     */
    StateManager.prototype.updateManagerBlocks = function () {
        this.parent.blocks = this.parent.getEditorBlocks();
        this.parent.observer.notify('updateEditorBlocks', { blocks: this.parent.blocks });
    };
    return StateManager;
}());
export { StateManager };
