import { InternalAnnotationTypeEnum } from '../../annotations/annotation.abstract';
import { ActionHandleStrategy } from '../action-handle-strategy.abstract';
import { TextSplittableAction } from '../../actions/splittable-action.abstract';
import stateEntityRenderingRegistry from '../../../slimmers/controllers/state-entity-rendering-registry';
import { LinkGroupNodeSplitAction } from '../../actions/link-group-node-split-action';
/**
 * Split the text node into two or there nodes according to the rules.
 */
export class NodeSplitTextStrategy extends ActionHandleStrategy {
  constructor() {
    super(...arguments);
    // The new create node that need apply annotation or the current target. This property used to export after the execute method invoke.
    this.applyTargetNode = null;
  }
  /**
   * Split the text node.
   *
   * @param {TextSplittableAction} action - The split action.
   * @param {RTETextNode} target - The text node that need to be splited.
   * @returns {Nullable<RTETextNode>} - The new insert text node or the current text node that need to apply the action.
   */
  handleAction(action, target) {
    const selectionRangeAnnotation = target.getAnnotationByType(InternalAnnotationTypeEnum.SELECTION_RANGE);
    this.setApplyTargetNode(null);
    if (action instanceof TextSplittableAction && action.doSplit && selectionRangeAnnotation) {
      const { startContainer, endContainer, startOffset, endOffset } = selectionRangeAnnotation.range;
      const noTextRangeSelected = startContainer === endContainer && startOffset === endOffset;
      target.parent.children = target.parent.children.flatMap((item) => {
        if (item.id === target.id) {
          if (noTextRangeSelected) {
            return this.insertNewNode(startOffset, target);
          }
          if (this.shouldSplitSelection(selectionRangeAnnotation.range) &&
            target.isContentEditable()) {
            const splittedTextNodes = this.splitTextNode(selectionRangeAnnotation.range, target);
            if (item.annotationMap.has('LINK')) {
              item.apply(new LinkGroupNodeSplitAction(splittedTextNodes));
            }
            return splittedTextNodes;
          }
          else {
            this.setApplyTargetNode(target);
            return target;
          }
        }
        return item;
      });
    }
    return this.applyTargetNode;
  }
  /**
   * Insert the new node at the start offset
   *
   * @param {number} startOffset - The start position offset
   * @param {RTETextNode} target - RTETextNode
   * @returns {RTETextNode[]} - RTETextNode array
   */
  insertNewNode(startOffset, target) {
    const newNode = target.cloneWithText('');
    this.setApplyTargetNode(newNode);
    const index = target.parent.children.findIndex((node) => node === target);
    if (startOffset === 0 || (!target.isContentEditable() && startOffset === index)) {
      return [newNode, target];
    }
    if (startOffset === target.text.length ||
      (!target.isContentEditable() && startOffset === index + 1)) {
      return [target, newNode];
    }
    const beforeNode = target.cloneWithText(target.text.substring(0, startOffset));
    const afterNode = target.cloneWithText(target.text.substring(startOffset));
    return [beforeNode, newNode, afterNode];
  }
  /**
   * Whether a given range should be split based on various conditions within the range.
   *
   * @param {Range} range - Selection range
   * @returns {boolean} Returns a boolean value indicating whether the selection range should be split.
   */
  shouldSplitSelection(range) {
    const { startContainer, endContainer, startOffset, endOffset } = range;
    const isCrossNode = startContainer !== endContainer;
    const isPartialStart = startOffset > 0;
    const isPartialEnd = endOffset < (endContainer.textContent ? endContainer.textContent.length : 0);
    const isPartialSelection = isPartialStart || isPartialEnd;
    return isCrossNode || isPartialSelection;
  }
  /**
   * Split text node with range
   *
   * @param {Range} range - Selection range
   * @param {RTETextNode} target - RTETextNode
   * @returns {RTETextNode[] | RTETextNode} RTETextNode array
   */
  splitTextNode(range, target) {
    const { startContainer, endContainer, startOffset } = range;
    const startNode = startContainer.parentElement;
    const endNode = endContainer.parentElement;
    /**
     * Split the text from the start offset to the end offset
     *
     * @param {string} text - The text string
     * @param {number} startOffset - The start offset position
     * @param {number} endOffset - The end offset position
     * @returns {RTETextNode[]} - The split text node array
     */
    const splitText = (text, startOffset, endOffset) => {
      const beforeText = text.substring(0, startOffset);
      const selectedText = text.substring(startOffset, endOffset);
      const afterText = text.substring(endOffset);
      return [
        target.cloneWithText(beforeText),
        target.cloneWithText(selectedText),
        target.cloneWithText(afterText),
      ];
    };
    const startItemDto = startNode && stateEntityRenderingRegistry.getEntityByDOM(startNode);
    const endItemDto = endNode && stateEntityRenderingRegistry.getEntityByDOM(endNode);
    if (startItemDto && startItemDto.id === target.id) {
      const endOffset = startContainer === endContainer ? range.endOffset : target.text.length;
      const [beforeNode, selectedNode, afterNode] = splitText(target.text, startOffset, endOffset);
      this.setApplyTargetNode(selectedNode);
      return [beforeNode, selectedNode, afterNode].filter((node) => !!node.text);
    }
    else if (endItemDto && endItemDto.id === target.id) {
      const [beforeNode, selectedNode, afterNode] = splitText(target.text, 0, range.endOffset);
      this.setApplyTargetNode(selectedNode);
      return [beforeNode, selectedNode, afterNode].filter((node) => !!node.text);
    }
    else {
      this.setApplyTargetNode(target);
      return target;
    }
  }
  /**
   * Store the text node which need to apply the annotation. Clear the value if the input is null.
   *
   * @param {RTETextNode | null} node - The new text node.
   */
  setApplyTargetNode(node) {
    this.applyTargetNode = node;
  }
}
