import { BaseHandler } from './base-handler';
import { RTETextBlock } from '../../../../dto/blocks/text-block';
import { ZERO_WIDTH_SPACE } from '../../../../constants/constant';
import { RTETextNode } from '../../../../dto/nodes/text-node';
import { UpdateTextAction } from '../../../../dto/actions/update-text-action';
import { ReplaceSelectedTextAction } from '../../../../dto/actions/replace-selected-text-action';
import { InsertChildrenBeforeAction } from '../../../../dto/actions/insert-children-before-block';
import { InsertTextToDecoratorNodeAction } from '../../../../dto/actions/insert-text-to-decorator-node-action';
/** Handles inserting text into a VegaRTEContent based on the current selection and input event. User input character will trigger this callback */
export class InsertTextHandler extends BaseHandler {
  /**
   * Update the state value when user type character
   *
   * @param {InputEvent} event - The input event object
   */
  handleEvent(event) {
    const text = event.data;
    const currentRange = this.selectionController.getCurrentRange();
    const { startContainer, startOffset } = currentRange;
    const selectedNodes = this.selectionController.getSelectedNodes();
    if (selectedNodes.length < 2) {
      if (!selectedNodes.length && this.isRootContainer(startContainer)) {
        const cursorNode = this.insertTextToRoot(text);
        this.selectionController.enqueueSelectionRangeFutureState(cursorNode, text.length);
      }
      else if (selectedNodes[0] instanceof RTETextNode) {
        if (selectedNodes[0].isContentEditable()) {
          let nextOffset = startOffset + text.length;
          let updateFlag = false;
          let cursorNode = selectedNodes[0];
          if (cursorNode.text === '\n') {
            cursorNode = this.insertTextToLineBreakNode(cursorNode, text);
            updateFlag = true;
          }
          else {
            if (startContainer.nodeValue === ZERO_WIDTH_SPACE &&
              startOffset === ZERO_WIDTH_SPACE.length &&
              cursorNode.isEmpty()) {
              startContainer.nodeValue = '';
              nextOffset = nextOffset - ZERO_WIDTH_SPACE.length;
            }
            if (cursorNode instanceof RTETextNode) {
              updateFlag = true;
              cursorNode.apply(new ReplaceSelectedTextAction(text, currentRange));
            }
          }
          updateFlag &&
            this.selectionController.enqueueSelectionRangeFutureState(cursorNode, Math.min(nextOffset, cursorNode['text'].length));
        }
        else {
          selectedNodes[0].parent.apply(new InsertTextToDecoratorNodeAction(selectedNodes[0], text, startOffset));
        }
      }
    }
    else {
      // The selected nodes is multiple nodes
      const cursorNode = this.removeSelectionRange(true);
      if (cursorNode instanceof RTETextNode) {
        cursorNode.apply(new ReplaceSelectedTextAction(text, currentRange));
        this.selectionController.enqueueSelectionRangeFutureState(cursorNode, Math.min(startOffset + text.length, cursorNode.text.length));
      }
    }
  }
  /**
   * Insert the text in root element when root container is empty, should create text block and text node
   *
   * @param {string} text The insert text
   * @returns {RTETextNode} The new create text node
   */
  insertTextToRoot(text) {
    if (this.currentState &&
      this.currentState.children[0] &&
      this.currentState.children[0] instanceof RTETextBlock) {
      const firstNode = this.currentState.children[0].children[0];
      firstNode.apply(new UpdateTextAction(text));
      return firstNode;
    }
  }
  /**
   * Insert text to a line break node need a special logic if the line break has a next text node.
   *
   * @param {RTETextNode} lineBreakNode The current line break node, the text value is \n
   * @param {string} text The text need to insert
   * @returns {Nullable<RTETextNode>} The new text node or the current line break node
   */
  insertTextToLineBreakNode(lineBreakNode, text) {
    const { startOffset, endOffset } = this.selectionController.getCurrentRange();
    const textBlock = lineBreakNode.parent;
    if (textBlock instanceof RTETextBlock) {
      const index = textBlock.children.indexOf(lineBreakNode);
      if (startOffset === 0 && startOffset === endOffset && textBlock.children[index + 1]) {
        const newTextNode = lineBreakNode.cloneWithText(text);
        textBlock.apply(new InsertChildrenBeforeAction(lineBreakNode, newTextNode));
        return newTextNode;
      }
      else {
        lineBreakNode.apply(new UpdateTextAction(text));
        return lineBreakNode;
      }
    }
  }
}
