var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { VegaSlimmer, MapToComponentField, MapToComponentMethod } from 'vega-slimmer/core';
import { DomNodeSubjectObserverFactory } from '../../../../helpers/change-manager/subject/dom-node-subject/dom-node-subject-observer-factory';
import { VegaInternalRichTextEditDeleteSelectedNodes } from '../../../../helpers/event-manager/event-id/vega-internal-event-id';
import { RTETextNode } from '../../dto/nodes/text-node';
import { RemoveChildrenAction } from '../../dto/actions/remove-child-action';
import { ReplaceSelectedTextAction } from '../../dto/actions/replace-selected-text-action';
import { RTETextBlock } from '../../dto/blocks/text-block';
import { InsertChildrenBeforeAction } from '../../dto/actions/insert-children-before-block';
import { generateUUID } from '../../../../utils/misc';
import { AppendChildrenAction } from '../../dto/actions/append-child-nodes-action';
import { UpdateTextAction } from '../../dto/actions/update-text-action';
import { RTECodeBlockNode } from '../../dto/nodes/code-block-node';
import { VegaRTEContent } from '../../dto/content-state';
/**
 * The `DeleteSelectedNodesController` class provides methods to delete selected nodes.
 */
export class DeleteSelectedNodesController extends VegaSlimmer {
  constructor() {
    super(...arguments);
    /**
     * Deletes the selected nodes.
     *
     * @param {NotifyObserverPayload<boolean>} payload The notify payload
     * @param {boolean} payload.detail The delete process need merge start node and end node or not, default value is true.
     */
    this.deleteActionProcess = (payload) => {
      const range = this.selectionController.getCurrentRange();
      const { startContainer, endContainer, startOffset, endOffset } = range;
      if (startContainer !== endContainer || startOffset !== endOffset) {
        const cursorNode = this.removeSelectionRange();
        const mergeStartAndEndFlag = payload && payload.detail ? payload.detail : false;
        if (mergeStartAndEndFlag) {
          this.mergeRangeStartAndEnd(startContainer, endContainer);
        }
        this.selectionController.enqueueSelectionRangeFutureState(cursorNode, range.startOffset);
      }
    };
  }
  /**
   * Adds the observer to the host element.
   */
  addDeleteObserver() {
    DomNodeSubjectObserverFactory.addUniqueObserverToNode(this.host, VegaInternalRichTextEditDeleteSelectedNodes, this.deleteActionProcess);
  }
  /**
   * Removes the observer from the host element.
   */
  removeDeleteObserver() {
    DomNodeSubjectObserverFactory.removeUniqueObserverFromNode(this.host, VegaInternalRichTextEditDeleteSelectedNodes);
  }
  /**
   * Removes selected nodes within a range in a rich text editor content.
   * The method is only used for different nodes are selected
   *
   * @returns {RTENode} The range start node, usually the start node is the first item of selectedNodes, if the selectedNodes is all removed, the start node will be a new text node.
   */
  removeSelectionRange() {
    const range = this.selectionController.getCurrentRange();
    const selectedNodes = this.selectionController.getSelectedNodes();
    const { endOffset, startOffset } = range;
    const startNode = selectedNodes[0];
    const endNode = selectedNodes[selectedNodes.length - 1];
    if (selectedNodes.length > 1) {
      // Remove all selected nodes except start node and end node(the end image node will be removed), the start node need to get the index of the block
      const shouldRemovedItems = selectedNodes.filter((item) => {
        if (item.isContentEditable()) {
          return item !== startNode && item !== endNode;
        }
        else {
          return (item !== startNode &&
            (item !== endNode || this.isEndNodeFullyInTheRange(endNode, endOffset, item)));
        }
      });
      shouldRemovedItems.forEach((item) => {
        item.parent.apply(new RemoveChildrenAction(item));
      });
    }
    this.removeRangeStartAndEndSelectedText();
    /**
     * if the range start node is image and the item is selected, so we need remove the image item, the image block will be removed if the item is the only item.
     * but before remove the image block, we need insert a paragraph to make sure the start block and start node could be get correct before insert text or break paragraph action.
     * the code block node should do the same logic.
     */
    if (!startNode.isContentEditable() &&
      this.isStartNodeFullyInTheRange(startNode, endNode, startOffset, endOffset)) {
      // the image block will be deleted when the image is the only item
      if (startNode.parent.children.length === 1) {
        const paragraph = this.createEmptyParagraph();
        let shouldDeleteBlock = startNode.parent;
        // if the node grand parent is html block and the html block only has one child, we need insert the paragraph before the html block to jump out the html block
        if (shouldDeleteBlock.parent &&
          !(shouldDeleteBlock.parent instanceof VegaRTEContent) &&
          shouldDeleteBlock.parent.children.length === 1) {
          shouldDeleteBlock = shouldDeleteBlock.parent;
        }
        shouldDeleteBlock.parent.apply(new InsertChildrenBeforeAction(shouldDeleteBlock, paragraph));
        startNode.parent.apply(new RemoveChildrenAction(startNode));
        return paragraph.children[0];
      }
      else if (startNode === startNode.parent.children[0]) {
        // the start node should be the next image item if the image is first item
        startNode.parent.apply(new RemoveChildrenAction(startNode));
        return startNode.parent.children[0];
      }
    }
    return startNode;
  }
  /**
   * Removes the selected text at the start and end of a range in a Rich Text Editor.
   */
  removeRangeStartAndEndSelectedText() {
    const selectedNodes = this.selectionController.getSelectedNodes();
    const currentRange = this.selectionController.getCurrentRange();
    if (selectedNodes[0] instanceof RTETextNode && selectedNodes[0].isContentEditable()) {
      selectedNodes[0].apply(new ReplaceSelectedTextAction('', currentRange));
    }
    const endNode = selectedNodes[selectedNodes.length - 1];
    if (selectedNodes.length > 1 && endNode instanceof RTETextNode && endNode.isContentEditable()) {
      selectedNodes[selectedNodes.length - 1].apply(new ReplaceSelectedTextAction('', currentRange));
    }
  }
  /**
   * Creates a new paragraph with an empty text node and returns it.
   *
   * @returns {RTETextBlock} An empty paragraph with a unique identifier and a text node appended to it.
   */
  createEmptyParagraph() {
    const paragraph = new RTETextBlock(generateUUID(), 'paragraph');
    const textNode = new RTETextNode(`${paragraph.id}1`, '', paragraph);
    paragraph.apply(new AppendChildrenAction([textNode]));
    return paragraph;
  }
  /**
   * Merges text nodes in a rich text editor.
   *
   * @param {Node} startContainer - Start text node element
   * @param {Node} endContainer - End text node element
   */
  mergeRangeStartAndEnd(startContainer, endContainer) {
    if (startContainer !== endContainer) {
      if (startContainer.nodeType === Node.TEXT_NODE && endContainer.nodeType === Node.TEXT_NODE) {
        const selectedNodes = this.selectionController.getSelectedNodes();
        const startRTENode = selectedNodes[0];
        const endRTENode = selectedNodes[selectedNodes.length - 1];
        if (startRTENode && startRTENode.parent && endRTENode && endRTENode.parent) {
          const endBlock = endRTENode.parent;
          if (this.shouldMergeTextNode(startRTENode, endRTENode)) {
            startRTENode.apply(new UpdateTextAction(startRTENode.text + endRTENode.text));
            if (startRTENode.parent === endRTENode.parent) {
              startRTENode.parent.apply(new RemoveChildrenAction(endRTENode));
            }
            else {
              endRTENode.parent.apply(new RemoveChildrenAction(endRTENode.parent.children[0]));
            }
          }
          if (startRTENode instanceof RTETextNode &&
            endRTENode instanceof RTETextNode &&
            startRTENode.parent !== endRTENode.parent) {
            startRTENode.parent.apply(new AppendChildrenAction(endBlock.children));
            endBlock.children = [];
          }
          if (endBlock.children.length < 1) {
            endBlock.parent.apply(new RemoveChildrenAction(endBlock));
          }
        }
      }
    }
  }
  /**
   * Compares the annotations of two RTETextNodes to determine if they can be merged.
   *
   * @param {RTETextNode} nodeA - node A
   * @param {RTETextNode} nodeB - node B
   * @returns {boolean} true or false
   */
  shouldMergeTextNode(nodeA, nodeB) {
    const { annotations: annotationsA } = nodeA.toJSON();
    const { annotations: annotationsB } = nodeB.toJSON();
    return (nodeA instanceof RTETextNode &&
      nodeB instanceof RTETextNode &&
      JSON.stringify(annotationsA) === JSON.stringify(annotationsB));
  }
  /**
   * Check the decorator node is fully in the range end.
   *
   * @param {RTENode} rangeEndNode - The end node of the range
   * @param {number} endOffset - The end offset of the range
   * @param {RTENode<RTEBlock>} checkedNode - The checked decorator node
   * @returns {boolean} - Whether the end node is fully within the range
   */
  isEndNodeFullyInTheRange(rangeEndNode, endOffset, checkedNode) {
    if (checkedNode instanceof RTECodeBlockNode) {
      return true;
    }
    else {
      return checkedNode === rangeEndNode && endOffset !== 0;
    }
  }
  /**
   * Check if the start decorator node is fully in the range.
   *
   * @param {RTENode<RTEBlock>} rangeStartNode - The start node of the range
   * @param {RTENode} rangeEndNode - The end node of the range
   * @param {number} startOffset - The start offset of the range
   * @param {number} endOffset - The end offset of the range
   * @returns {boolean} - Whether the start node is fully within the range
   */
  isStartNodeFullyInTheRange(rangeStartNode, rangeEndNode, startOffset, endOffset) {
    if (rangeStartNode instanceof RTECodeBlockNode) {
      return true;
    }
    else {
      return startOffset === 0 && (rangeEndNode !== rangeStartNode || endOffset !== 0);
    }
  }
}
__decorate([
  MapToComponentField()
], DeleteSelectedNodesController.prototype, "host", void 0);
__decorate([
  MapToComponentField()
], DeleteSelectedNodesController.prototype, "selectionController", void 0);
__decorate([
  MapToComponentField()
], DeleteSelectedNodesController.prototype, "value", void 0);
__decorate([
  MapToComponentMethod('connectedCallback')
], DeleteSelectedNodesController.prototype, "addDeleteObserver", null);
__decorate([
  MapToComponentMethod('disconnectedCallback')
], DeleteSelectedNodesController.prototype, "removeDeleteObserver", null);
