var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
import { decoupleReference, generateUniqueId, getAbsoluteOffset, normalizeRange, getDeepestTextNode } from '../../common/utils/common';
import { getSelectedRange, getTextOffset, setCursorPosition } from '../../common/utils/selection';
import { getBlockContentElement, getContentElementBasedOnId } from '../../common/utils/block';
import { NodeSelection } from '../../selection/selection';
import { isNodeInsideElement, findClosestParent } from '../../common/utils/dom';
import { events } from '../../common/constant';
import { sanitizeBlock, sanitizeContent } from '../../common/utils/transform';
import { FormattingHelper } from '../../common/utils/isformatted';
import * as constants from '../../common/constant';
import { BlockType, ContentType } from '../../models/enums';
import { BlockFactory } from '../../block-manager/services/index';
var FormattingAction = /** @class */ (function () {
    function FormattingAction(manager) {
        /** @hidden */
        this.lastRemovedFormat = null;
        /** @hidden */
        this.activeInlineFormats = new Set();
        this.formatCache = new WeakMap();
        this.ignoredContentTypes = new Set([ContentType.Mention, ContentType.Label]);
        this.ignoredBlockTypes = new Set([BlockType.Callout, BlockType.Image, BlockType.Divider, BlockType.Code]);
        this.parent = manager;
        this.nodeSelection = new NodeSelection(this.parent.blockContainer);
        this.addEventListeners();
    }
    FormattingAction.prototype.addEventListeners = function () {
        this.parent.observer.on(constants.FORMATTINGACTION, this.execCommand, this);
        this.parent.observer.on(events.destroy, this.destroy, this);
    };
    FormattingAction.prototype.removeEventListeners = function () {
        this.parent.observer.off(constants.FORMATTINGACTION, this.execCommand);
        this.parent.observer.off(events.destroy, this.destroy);
    };
    /**
     * Executes the formatting command based on the provided options.
     *
     * @param {ExecCommandOptions} options - The options for the formatting command.
     * @returns {void}
     * @hidden
     */
    FormattingAction.prototype.execCommand = function (options) {
        this.performOperation(options);
    };
    FormattingAction.prototype.performOperation = function (options) {
        var blocksToFormat = this.resolveBlocksToFormat();
        if (!blocksToFormat || blocksToFormat.length === 0) {
            return;
        }
        this.nodeSelection.saveSelection();
        var _a = this.applyFormattingToBlocks(blocksToFormat, options), blockIDs = _a.blockIDs, oldBlockModels = _a.oldBlockModels, updatedBlockModels = _a.updatedBlockModels;
        this.nodeSelection.restoreSelection();
        this.parent.undoRedoAction.trackFormattingForUndoRedo(blockIDs, oldBlockModels, updatedBlockModels, options.isTypingWithFormat, this.nodeSelection.savedSelectionState);
        this.parent.observer.notify('formatting-performed', options);
    };
    FormattingAction.prototype.applyFormattingToBlocks = function (blocks, options) {
        var blockIDs = [];
        var oldBlockModels = [];
        var updatedBlockModels = [];
        for (var _i = 0, blocks_1 = blocks; _i < blocks_1.length; _i++) {
            var block = blocks_1[_i];
            if (this.ignoredBlockTypes.has(block.blockType) || block.content.length <= 0) {
                continue;
            }
            var contentEl = getBlockContentElement(this.parent.getBlockElementById(block.id));
            if (!contentEl || contentEl.textContent.trim() === '') {
                continue;
            }
            blockIDs.push(block.id);
            oldBlockModels.push(decoupleReference(sanitizeBlock(block)));
            this.processFormattingActions(block, options);
            this.removeRootContentId(getBlockContentElement(this.parent.getBlockElementById(block.id)));
            updatedBlockModels.push(decoupleReference(sanitizeBlock(block)));
        }
        return { blockIDs: blockIDs, oldBlockModels: oldBlockModels, updatedBlockModels: updatedBlockModels };
    };
    FormattingAction.prototype.processFormattingActions = function (block, options) {
        var oldBlock = decoupleReference(sanitizeBlock(block));
        var newContents = this.processContentModel(block, options);
        /* Model */
        this.parent.blockService.updateContent(block.id, newContents);
        this.parent.stateManager.updateManagerBlocks();
        /* DOM */
        this.parent.observer.notify('modelChanged', { type: 'ReRenderBlockContent', state: {
                data: [{ block: block, oldBlock: oldBlock }]
            } });
    };
    FormattingAction.prototype.processContentModel = function (block, options) {
        var newContent = [];
        var blockElement = this.parent.getBlockElementById(block.id);
        var range = this.getBlockSpecificRange(getSelectedRange(), blockElement);
        var selectedContents = this.getSelectedContents(range, block.content, blockElement);
        var formatIntent = this.getFormatIntent(selectedContents, options.command);
        for (var _i = 0, _a = block.content; _i < _a.length; _i++) {
            var content = _a[_i];
            var contentText = content.content;
            var contentLength = contentText.length;
            var contentElement = getContentElementBasedOnId(content.id, blockElement);
            var isStartInsideContent = isNodeInsideElement(range.startContainer, contentElement);
            var isEndInsideContent = isNodeInsideElement(range.endContainer, contentElement);
            var isContentFullyInsideSelection = this.isContentFullyInsideSelection(contentElement, range);
            if ((!isStartInsideContent && !isEndInsideContent && !isContentFullyInsideSelection)
                || this.ignoredContentTypes.has(content.contentType)) {
                // Fully outside selection — just copy as-it-is
                newContent.push(__assign({}, content));
                continue;
            }
            var startContentElement = findClosestParent(range.startContainer, "#" + content.id);
            var endContentElement = findClosestParent(range.endContainer, "#" + content.id);
            var localOffsets = this.calculateLocalOffsets(contentLength, contentElement, isStartInsideContent, isEndInsideContent, range);
            var beforeText = contentText.substring(0, localOffsets.start);
            var selectedText = contentText.substring(localOffsets.start, localOffsets.end);
            var afterText = contentText.substring(localOffsets.end);
            /* If both start and end are outside the content and content is fully inside selection,
            then current content element is the reference node */
            var isMiddleNode = !isStartInsideContent && !isEndInsideContent && isContentFullyInsideSelection;
            var isOnlyContentSelected = (isStartInsideContent && isEndInsideContent && isContentFullyInsideSelection &&
                (startContentElement === endContentElement) && (startContentElement === contentElement));
            if (beforeText) {
                newContent.push(this.processSurroundSegment(beforeText, content, false));
            }
            if (selectedText) {
                newContent.push(this.processSelectedSegment(options, selectedText, content, !isOnlyContentSelected, formatIntent, isMiddleNode));
            }
            if (afterText) {
                newContent.push(this.processSurroundSegment(afterText, content, true));
            }
        }
        return newContent;
    };
    FormattingAction.prototype.processSurroundSegment = function (text, content, isAfterText) {
        var newId = isAfterText ? generateUniqueId(constants.CONTENT_ID_PREFIX) : null;
        var processedContent = BlockFactory.createContentFromPartial(__assign({}, decoupleReference(sanitizeContent(content)), { id: newId, content: text }));
        return processedContent;
    };
    FormattingAction.prototype.processSelectedSegment = function (options, selectedText, content, shouldForce, formatIntent, isMiddleNode) {
        var selectedContent = decoupleReference(sanitizeContent(content));
        if (!isMiddleNode) {
            var newId = generateUniqueId(constants.CONTENT_ID_PREFIX);
            selectedContent = BlockFactory.createContentFromPartial(__assign({}, selectedContent, { id: newId, content: selectedText }));
        }
        return this.toggleFormat(selectedContent, options, shouldForce, formatIntent);
    };
    FormattingAction.prototype.toggleFormat = function (content, options, force, formatIntent) {
        /* Handle if it is sub command */
        if (!options.command && (content.contentType === ContentType.Link || options.subCommand === ContentType.Link)) {
            return this.parent.blockService.applyLinkFormatting(content, options.value);
        }
        /* Command handling */
        return this.parent.blockService.toggleContentStyles(content, options.command, force, formatIntent, options.value);
    };
    /**
     * Handles formatting action on user typing.
     *
     * @returns {boolean} - Returns true if formatting was applied, false otherwise.
     * @hidden
     */
    FormattingAction.prototype.handleTypingWithActiveFormats = function () {
        var _this = this;
        var selection = window.getSelection();
        if (!selection.isCollapsed) {
            return false;
        }
        var range = selection.getRangeAt(0);
        var currentNode = range.startContainer;
        var blockElement = findClosestParent(range.startContainer, '.' + constants.BLOCK_CLS);
        var contentElement = getBlockContentElement(blockElement);
        var absoluteOffset = getAbsoluteOffset(contentElement, range.startContainer, range.startOffset);
        if (this.lastRemovedFormat && this.isNodeFormattedWith(currentNode, this.lastRemovedFormat)) {
            return this.removeFormatToLastCharacter(blockElement, contentElement, range, absoluteOffset);
        }
        if (this.areAllActiveFormatsApplied(currentNode)) {
            return false;
        }
        this.parent.stateManager.updateContentOnUserTyping(blockElement);
        var createdRange = this.nodeSelection.createRangeWithOffsets(range.startContainer, range.startContainer, range.startOffset - 1, range.startOffset);
        this.activeInlineFormats.forEach(function (format) {
            if (_this.isNodeFormattedWith(currentNode, format)) {
                // Skip this format since it's already applied
                return;
            }
            _this.execCommand({ command: format, isTypingWithFormat: true });
        });
        createdRange.collapse(false);
        this.parent.editorMethods.selectRange(createdRange);
        setCursorPosition(contentElement, absoluteOffset);
        return true;
    };
    FormattingAction.prototype.removeFormatToLastCharacter = function (blockElement, contentElement, range, absoluteOffset) {
        this.parent.stateManager.updateContentOnUserTyping(blockElement);
        var createdRange = this.nodeSelection.createRangeWithOffsets(range.startContainer, range.startContainer, range.startOffset - 1, range.startOffset);
        this.execCommand({ command: this.lastRemovedFormat, isTypingWithFormat: true });
        this.lastRemovedFormat = null;
        createdRange.collapse(false);
        this.parent.editorMethods.selectRange(createdRange);
        setCursorPosition(contentElement, absoluteOffset);
        return true;
    };
    /**
     * Toggles the active inline formats when formatting.
     * Triggers when user presses keys such as Ctrl+B, Ctrl+I, Ctrl+U and Ctrl+Shift+X.
     *
     * @param {string} command - The formatting command to toggle.
     * @returns {void}
     * @hidden
     */
    FormattingAction.prototype.toggleActiveFormats = function (command) {
        if (this.activeInlineFormats.has(command)) {
            this.activeInlineFormats.delete(command);
            this.lastRemovedFormat = command;
        }
        else {
            this.activeInlineFormats.add(command);
            this.lastRemovedFormat = null;
        }
    };
    FormattingAction.prototype.getBlockSpecificRange = function (globalRange, blockElement) {
        var contentElement = getBlockContentElement(blockElement);
        var blockRange = document.createRange();
        var startTextNode = contentElement.firstChild.nodeType === Node.ELEMENT_NODE
            ? (getDeepestTextNode(contentElement.firstChild) || contentElement.firstChild) : contentElement.firstChild;
        var endTextNode = contentElement.lastChild.nodeType === Node.ELEMENT_NODE ?
            (getDeepestTextNode(contentElement.lastChild) || contentElement.lastChild) : contentElement.lastChild;
        var tableEle = blockElement.closest('.' + constants.TABLE_BLOCK_CLS);
        var hasActiveSel = this.parent.tableSelectionManager.hasActiveTableSelection(tableEle);
        blockRange.selectNodeContents(contentElement);
        blockRange.setStart(startTextNode, 0);
        blockRange.setEnd(endTextNode, endTextNode.textContent.length);
        if (!globalRange || hasActiveSel) {
            return blockRange;
        }
        if (isNodeInsideElement(globalRange.startContainer, blockElement)) {
            blockRange.setStart(globalRange.startContainer, globalRange.startOffset);
        }
        if (isNodeInsideElement(globalRange.endContainer, blockElement)) {
            blockRange.setEnd(globalRange.endContainer, globalRange.endOffset);
        }
        return blockRange;
    };
    FormattingAction.prototype.isContentFullyInsideSelection = function (contentElement, range) {
        var selectionRange = range.cloneRange();
        var contentRange = document.createRange();
        contentRange.selectNodeContents(contentElement);
        return selectionRange.compareBoundaryPoints(Range.START_TO_END, contentRange) > 0 &&
            selectionRange.compareBoundaryPoints(Range.END_TO_START, contentRange) < 0;
    };
    FormattingAction.prototype.calculateLocalOffsets = function (contentLength, contentElement, isStartInsideContent, isEndInsideContent, range) {
        var localStart = 0;
        var localEnd = contentLength;
        if (isStartInsideContent) {
            localStart = range.startOffset + getTextOffset(range.startContainer, contentElement);
        }
        if (isEndInsideContent) {
            localEnd = range.endOffset + getTextOffset(range.endContainer, contentElement);
        }
        localStart = Math.max(0, Math.min(contentLength, localStart));
        localEnd = Math.max(localStart, Math.min(contentLength, localEnd));
        return { start: localStart, end: localEnd };
    };
    FormattingAction.prototype.getFormatIntent = function (selectedContents, format) {
        // If even one content doesn't have the format, we intend to apply it
        for (var _i = 0, selectedContents_1 = selectedContents; _i < selectedContents_1.length; _i++) {
            var content = selectedContents_1[_i];
            var styles = content.properties.styles;
            if (this.ignoredContentTypes.has(content.contentType) || (styles && format in styles && styles[format])) {
                // Continue checking others
                continue;
            }
            return true; // At least one lacks the format → apply
        }
        return false; // All have the format → remove
    };
    FormattingAction.prototype.getSelectedContents = function (range, contents, blockElement) {
        var selected = [];
        var normalizedRange = normalizeRange(this.getBlockSpecificRange(range, blockElement));
        for (var _i = 0, contents_1 = contents; _i < contents_1.length; _i++) {
            var content = contents_1[_i];
            var contentElement = getContentElementBasedOnId(content.id, blockElement);
            var isStartInside = isNodeInsideElement(normalizedRange.startContainer, contentElement);
            var isEndInside = isNodeInsideElement(normalizedRange.endContainer, contentElement);
            var isFullyInside = this.isContentFullyInsideSelection(contentElement, normalizedRange);
            if (isStartInside || isEndInside || isFullyInside) {
                selected.push(content);
            }
        }
        return selected;
    };
    FormattingAction.prototype.areAllActiveFormatsApplied = function (node) {
        var _this = this;
        var allApplied = true;
        this.activeInlineFormats.forEach(function (format) {
            if (!_this.isNodeFormattedWith(node, format)) {
                allApplied = false;
            }
        });
        return allApplied;
    };
    FormattingAction.prototype.isNodeFormattedWith = function (node, format) {
        var currentElement = node.nodeType === Node.TEXT_NODE ?
            node.parentElement : node;
        if (currentElement && this.formatCache.has(currentElement)) {
            return this.formatCache.get(currentElement).has(format);
        }
        var formats = new Set();
        while (currentElement) {
            if (this.doesElementHaveFormat(currentElement, format)) {
                formats.add(format);
            }
            // Stop if we've reached an element with an id (root content element)
            if (currentElement.id) {
                break;
            }
            currentElement = currentElement.parentElement;
        }
        if (currentElement) {
            this.formatCache.set(currentElement, formats);
        }
        return formats.has(format);
    };
    FormattingAction.prototype.doesElementHaveFormat = function (element, format) {
        switch (format) {
            case 'bold':
                return FormattingHelper.isBold(element);
            case 'italic':
                return FormattingHelper.isItalic(element);
            case 'underline':
                return FormattingHelper.isUnderline(element);
            case 'strikethrough':
                return FormattingHelper.isStrikethrough(element);
            default:
                return false;
        }
    };
    FormattingAction.prototype.removeRootContentId = function (contentElement) {
        if (contentElement.id && contentElement.firstChild.nodeType === Node.ELEMENT_NODE) {
            contentElement.removeAttribute('id');
        }
    };
    FormattingAction.prototype.resolveBlocksToFormat = function () {
        var selectedBlocks = this.parent.editorMethods.getSelectedBlocks();
        var range = getSelectedRange();
        // Case A: Whole blocks are selected (including possible Table blocks)
        if (selectedBlocks && selectedBlocks.length > 0) {
            return this.expandSelectedBlocks(selectedBlocks);
        }
        // Case B: No block selection → likely table cell selection
        if ((!range) && (!selectedBlocks || selectedBlocks.length === 0)) {
            return this.getSelectedCellBlocksFromTable();
        }
        // Fallback: should never happen
        return [];
    };
    FormattingAction.prototype.expandSelectedBlocks = function (selectedBlocks) {
        var result = [];
        for (var _i = 0, selectedBlocks_1 = selectedBlocks; _i < selectedBlocks_1.length; _i++) {
            var block = selectedBlocks_1[_i];
            if (block.blockType === BlockType.Table) {
                var tableEl = this.parent.getBlockElementById(block.id);
                result.push.apply(result, this.parent.tableSelectionManager.getAllCellBlocks(tableEl));
            }
            else {
                result.push(block);
            }
        }
        return result;
    };
    FormattingAction.prototype.getSelectedCellBlocksFromTable = function () {
        var tableCtx = this.parent.blockRenderer.tableRenderer.resolveTableContext();
        if (tableCtx && tableCtx.tableBlockEl) {
            return this.parent.tableSelectionManager.getSelectedCellBlocks(tableCtx.tableBlockEl);
        }
        return [];
    };
    FormattingAction.prototype.destroy = function () {
        this.removeEventListeners();
        this.lastRemovedFormat = null;
        this.activeInlineFormats = null;
        this.nodeSelection = null;
        this.formatCache = null;
        this.activeInlineFormats = null;
        this.ignoredContentTypes = null;
        this.ignoredBlockTypes = null;
    };
    return FormattingAction;
}());
export { FormattingAction };
