import { NodeTypeEnum, RTENode } from './node.abstract';
import { NodeAnnotation, NodeAnnotationTypeEnum } from '../annotations/node-annotation.abstract';
import { AnnotationAction } from '../actions/annotation-action.abstract';
import { TextSplittableAction } from '../actions/splittable-action.abstract';
import { SelectionRangeAnnotation } from '../annotations/selection-range-annotation';
import { generateUUID } from '../../../../utils/misc';
import { InternalAnnotationTypeEnum } from '../annotations/annotation.abstract';
import { StrikethroughAnnotation } from '../annotations/strikethrough-annotation';
import { UnderlineAnnotation } from '../annotations/underline-annotation';
import { ItalicAnnotation } from '../annotations/italic-annotation';
import { BoldAnnotation } from '../annotations/bold-annotation';
import { isNonNullable } from '../../../../types/type-guard';
import { CodeAnnotation } from '../annotations/code-annotation';
import { TextColorAnnotation } from '../annotations/text-color-annotation';
import { LinkAnnotation } from '../annotations/link-annotation';
import { ModifyContentActionType } from '../actions/modify-content-action.abstract';
import { LinkGroupAnnotationAction } from '../actions/link-group-annotation-action';
import domNodeSubjectFactory from '../../../../helpers/change-manager/subject/dom-node-subject/dom-node-subject-factory';
import { VegaInternalRichTextEditorSelectionRangeChange } from '../../../../helpers/event-manager/event-id/vega-internal-event-id';
import ChangeManager from '../../../../helpers/change-manager/change-manager';
import { ZERO_WIDTH_SPACE } from '../../constants/constant';
import { SyncUpSelectionAction } from '../actions/sync-up-selection-action';
import { ActionHandleStrategyRegistry } from '../action-handle-strategies/action-handle-strategy-registry';
import { NodeSplitTextStrategy } from '../action-handle-strategies/apply-annotation-strategies/node-split-text-strategy';
import { NodeUpdateAnnotationMapStrategy } from '../action-handle-strategies/apply-annotation-strategies/node-update-annotation-map-strategy';
import { CommonAnnotation } from '../annotations/common-annotation.abstract';
import { NodeUpdateTextStrategy } from '../action-handle-strategies/modify-content-strategies/node-update-text-strategy';
import { NodeReplaceSelectedTextStrategy } from '../action-handle-strategies/modify-content-strategies/node-replace-selected-text-strategy';
import { InlineHtmlAnnotation } from '../annotations/inline-html-annotation';
import { LinkGroupNodeSplitStrategy } from '../action-handle-strategies/modify-content-strategies/link-group-node-split-strategy';
import { DeleteLinkGroupStrategy } from '../action-handle-strategies/modify-content-strategies/delete-link-group-strategy';
import { PlainTextAnnotation } from '../annotations/plain-text-annotation';
import { HTML_VOID_TAGS } from '../../constants/constant';
import { LinkAnnotationAction } from '../actions/link-annotation-action';
import { BoldAnnotationAction } from '../actions/bold-annotation-action';
import { ClearFormattingAnnotationAction } from '../actions/clear-formatting-annotation-action';
import { ItalicAnnotationAction } from '../actions/italic-annotation-action';
import { UnderlineAnnotationAction } from '../actions/underline-annotation-action';
import { TextColorAnnotationAction } from '../actions/text-color-annotation-action';
import { StrikethroughAnnotationAction } from '../actions/strikethrough-annotation-action';
import { CodeAnnotationAction } from '../actions/code-annotation-action';
/**
 * Text node
 */
export class RTETextNode extends RTENode {
  constructor(id, text, parentBlock, annotationMap) {
    super(id, annotationMap);
    this.type = NodeTypeEnum.TEXT;
    this.parent = parentBlock;
    this.text = text;
    this.registerModifyStrategy();
  }
  /**
   * Determines if the given HTML tag is supported by the RTETextNode.
   *
   * @param {string} tag - The HTML tag to check.
   * @returns {boolean} `true` if the tag is supported, otherwise `false`.
   */
  static supportsHtmlTag(tag) {
    return ['span', 'code'].includes(tag);
  }
  /**
   * Creates an RTETextNode from a VegaRTETextNode with annotations and a parent RTETextBlock.
   *
   * @param {VegaRTETextNode} richText - A rich text node
   * @param {RTETextBlock} parentBlock - Parent block
   * @param {VegaRTETransformOptions} options - Optional transformation options.
   * @returns {RTETextNode} an RTETextNode
   */
  static from(richText, parentBlock, options = { autoMatchFormat: true }) {
    const textNode = new RTETextNode(richText.id, richText.text, parentBlock);
    const { annotations } = richText;
    if (annotations) {
      textNode.annotationMap = this.generateTextAnnotationMap(annotations, options, parentBlock, textNode);
    }
    return textNode;
  }
  /**
   * Generates a map of text annotations for the given set of annotations.
   *
   * @param {VegaRTETextAnnotations} annotations - The annotations to be converted into a map.
   * @param {VegaRTETransformOptions} options - Optional transformation options.
   * @param {RTETextBlock} parentBlock - The parent text block.
   * @param {RTETextNode} textNode - The text node.
   * @returns {NodeAnnotations} The map of text annotations.
   */
  static generateTextAnnotationMap(annotations, options, parentBlock, textNode) {
    return new Map(Object.keys(annotations)
      .map((type) => {
      if (type === 'link' && annotations.link && parentBlock && textNode) {
        parentBlock.apply(new LinkGroupAnnotationAction(annotations.link.groupKey, [textNode], 'link'));
      }
      return this.createAnnotationEntity(type, annotations[type], options);
    })
      .filter(isNonNullable));
  }
  /**
   * Creates different types of text annotations based on the provided type and value.
   *
   * @param {keyof VegaRTETextAnnotations} type - The key of the type.
   * @param {unknown} value - The value that will be used to create the specific type of annotation entity based on the `type` provided.
   * @param {VegaRTETransformOptions} [options] - Optional transformation options that may be used for creating the annotation entity.
   * @returns {Nullable<[RTENodeAnnotationMapKey, RTENodeAnnotationMapValue]>} Returning a nullable tuple containing a key and a
   * value from the `RTENodeAnnotationMapKey` and `RTENodeAnnotationMapValue` types.
   */
  static createAnnotationEntity(type, value, options) {
    const autoMatchFormat = !!(options && options.autoMatchFormat);
    switch (type) {
      case 'bold':
        return BoldAnnotation.from(value);
      case 'italic':
        return ItalicAnnotation.from(value);
      case 'underline':
        return UnderlineAnnotation.from(value);
      case 'strikethrough':
        return StrikethroughAnnotation.from(value);
      case 'code':
        return CodeAnnotation.from(value, autoMatchFormat);
      case 'textColor':
        return TextColorAnnotation.from(value);
      case 'link':
        return LinkAnnotation.from(value, autoMatchFormat);
      case 'inlineHtml': {
        return InlineHtmlAnnotation.from(value);
      }
      case 'plainText': {
        return PlainTextAnnotation.from(value);
      }
    }
    return RTENode.createAnnotationEntity(type, value);
  }
  /**
   * @inheritDoc
   */
  toJSON() {
    const annotations = Array.from(this.annotationMap.values())
      .filter((annotation) => annotation instanceof NodeAnnotation ||
      annotation instanceof CommonAnnotation ||
      annotation instanceof InlineHtmlAnnotation)
      .reduce((record, annotation) => {
      return Object.assign(Object.assign({}, record), annotation.toJSON());
    }, {});
    return Object.assign(Object.assign({ id: this.id }, (Object.keys(annotations).length > 0 ? { annotations } : {})), { type: 'text', text: this.text });
  }
  /**
   * @inheritDoc
   */
  toHtml(options) {
    const inlineHtmlAnnotation = this.getAnnotationByType(NodeAnnotationTypeEnum.INLINE_HTML);
    return inlineHtmlAnnotation
      ? this.renderWithWrapperStr(inlineHtmlAnnotation, options)
      : this.renderTextStr(options);
  }
  /**
   * Creates a new RTETextNode with the specified text and copies the parent block and annotation map from the original node.
   *
   * @param {string} text - The text content of the RTETextNode that will be created.
   * @returns {RTETextNode} An new RTETextNode
   */
  cloneWithText(text) {
    const cloned = new RTETextNode(generateUUID(), text, this.parent, super.cloneAnnotations());
    return cloned;
  }
  /**
   * @inheritDoc
   */
  clone(parent) {
    const cloned = new RTETextNode(this.id, this.text, parent, super.cloneAnnotations());
    return cloned;
  }
  /**
   * Checks if a node is empty
   *
   * @returns {boolean}  It returns `true` if the node is empty
   */
  isEmpty() {
    // Inline HTML annotations (e.g., <br>) are considered non-empty even with empty text content.
    if (this.getAnnotationByType(NodeAnnotationTypeEnum.INLINE_HTML))
      return false;
    return !this.text || this.text === ZERO_WIDTH_SPACE || this.text === '\n';
  }
  /**
   * @inheritDoc
   */
  isTextNode() {
    return true;
  }
  /**
   * @inheritDoc
   */
  getRangeEndOffset() {
    if (!this.isContentEditable() && this.parent) {
      const parent = this.parent;
      const nodeIndex = parent.children.indexOf(this);
      return nodeIndex + 1; // place the cursor after the non-editable text node;
    }
    return this.text === '\n' ? 0 : this.text.length;
  }
  /**
   * @inheritDoc
   */
  getRangeContainer() {
    const element = this.getElementRef();
    if (element) {
      if (this.isContentEditable()) {
        return element.firstChild;
      }
      else {
        return element.parentElement;
      }
    }
  }
  /**
   * @inheritDoc
   */
  isContentEditable() {
    return true;
  }
  /**
   * Returns the inline HTML wrapper descriptor at the given depth in the
   * annotation chain. Depth 0 is the outermost wrapper, depth 1 is its child, etc.
   *
   * @param {number} depth - Zero-based depth in the inline HTML chain.
   * @returns {InlineHtmlTagDescriptor | null} The wrapper descriptor at that depth, or `null`.
   */
  getInlineHtmlTagAtDepth(depth) {
    const annotation = this.getInlineHtmlAnnotationAtDepth(depth);
    if (!annotation)
      return null;
    if (HTML_VOID_TAGS.has(annotation.htmlTag))
      return null;
    return {
      htmlTag: annotation.htmlTag,
      attrStr: this.annotationAttrStr(annotation),
      groupId: annotation.groupId,
      annotation,
    };
  }
  /**
   * Renders the node starting from the given depth in the inline HTML chain.
   * Depth 0 renders the full chain (equivalent to {@link toHtml} for inline HTML nodes).
   *
   * @param {number} depth - Zero-based depth to start rendering from.
   * @param {VegaRTETransformOptions} [options] - Optional conversion options that may affect how the text node is rendered as HTML.
   * @returns {string} HTML string from that depth onward.
   */
  renderHtmlFromDepth(depth, options) {
    // At depth 0 this is, by contract, equivalent to `toHtml`. Delegate
    // so derived classes that override `toHtml` (token, and future RTE
    // extensions) emit their own markup instead of falling back to the
    // base text rendering. See VD-9042.
    if (depth === 0)
      return this.toHtml(options);
    const annotation = this.getInlineHtmlAnnotationAtDepth(depth);
    if (!annotation)
      return this.renderTextStr(options);
    return this.renderWithWrapperStr(annotation, options);
  }
  /**
   * Traverses the inline HTML annotation chain to the given depth and returns
   * the annotation at that level, or `null` if the chain is shorter.
   *
   * @param {number} depth - Zero-based depth (0 = outermost annotation).
   * @returns {Nullable<InlineHtmlAnnotation>} The annotation at that depth, or `null`.
   */
  getInlineHtmlAnnotationAtDepth(depth) {
    let current = this.getAnnotationByType(NodeAnnotationTypeEnum.INLINE_HTML);
    for (let i = 0; i < depth && current; i++) {
      current = current.child;
    }
    return current;
  }
  /**
   * Registers the action strategies for the RTETextNode.
   */
  registerModifyStrategy() {
    const dtoClassName = this.constructor.name;
    if (!RTETextNode.registerActionStrategyStatus[dtoClassName]) {
      if (this.isContentEditable()) {
        ActionHandleStrategyRegistry.register(ModifyContentActionType.UPDATE_TEXT, dtoClassName, new NodeUpdateTextStrategy());
        ActionHandleStrategyRegistry.register(ModifyContentActionType.REPLACE_SELECTED_TEXT, dtoClassName, new NodeReplaceSelectedTextStrategy());
        ActionHandleStrategyRegistry.register(ModifyContentActionType.LINK_GROUP_NODE_SPLIT, dtoClassName, new LinkGroupNodeSplitStrategy());
        ActionHandleStrategyRegistry.register(LinkAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      }
      ActionHandleStrategyRegistry.register(TextSplittableAction.name, dtoClassName, new NodeSplitTextStrategy());
      ActionHandleStrategyRegistry.register(ModifyContentActionType.DELETE_LINK_GROUP, dtoClassName, new DeleteLinkGroupStrategy());
      ActionHandleStrategyRegistry.register(BoldAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(ClearFormattingAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(ItalicAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(UnderlineAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(TextColorAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(StrikethroughAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(CodeAnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      ActionHandleStrategyRegistry.register(AnnotationAction.name, dtoClassName, new NodeUpdateAnnotationMapStrategy());
      RTETextNode.registerActionStrategyStatus[dtoClassName] = true;
    }
  }
  /**
   * @inheritDoc
   */
  doAnnotationActionApply(action) {
    const selectionRangeAnnotation = this.getAnnotationByType(InternalAnnotationTypeEnum.SELECTION_RANGE);
    if (action instanceof TextSplittableAction && action.doSplit && selectionRangeAnnotation) {
      const splitStrategy = ActionHandleStrategyRegistry.get(TextSplittableAction.name, this.constructor.name);
      if (splitStrategy) {
        const newNode = splitStrategy.execute(action, this);
        if (newNode) {
          this.applyActionToNode(newNode, action);
        }
      }
    }
    else {
      this.applyActionToNode(this, action);
    }
    if (typeof this.parent.mergeNodes === 'function') {
      this.parent.mergeNodes(action);
    }
  }
  /**
   * @inheritDoc
   */
  doModifyActionApply(action) {
    ActionHandleStrategyRegistry.executeTheStrategy(action, this);
  }
  /**
   * Apply action to node
   *
   * @param {RTETextNode} node - Text node
   * @param {AnnotationAction} action - AnnotationAction
   */
  applyActionToNode(node, action) {
    const annotation = action.toAnnotation();
    if (action instanceof SyncUpSelectionAction) {
      this.syncUpSelectedNodes(node);
      return;
    }
    // Sync up RTETextNode selected state to selection controller for any action apply, except selection range change.
    if (!(annotation instanceof SelectionRangeAnnotation)) {
      this.syncUpSelectedNodes(node);
    }
    /**
     * 1. Try to get the specific strategy for the action and node type first (e.g., LinkAnnotationAction on RTETextNode).
     * 2. If no specific strategy found, try to get the generic NodeUpdateAnnotationMapStrategy for AnnotationAction on RTETextNode.
     */
    const strategy = ActionHandleStrategyRegistry.get(action.constructor.name, node.constructor.name);
    if (strategy) {
      strategy.execute(action, node);
      return;
    }
    const updateAnnotationMapStrategy = ActionHandleStrategyRegistry.get(AnnotationAction.name, RTETextNode.name);
    if (updateAnnotationMapStrategy) {
      updateAnnotationMapStrategy.execute(action, node);
      return;
    }
  }
  /**
   * Sync up the selected node by change manager
   *
   * @param {RTETextNode} selectedNode - The selected node
   */
  syncUpSelectedNodes(selectedNode) {
    const currentNodeDom = this.getElementRef();
    if (currentNodeDom) {
      const nodeSubject = domNodeSubjectFactory.getSubjectFromParentNodeByEventId(currentNodeDom, VegaInternalRichTextEditorSelectionRangeChange);
      if (nodeSubject) {
        ChangeManager.notify(nodeSubject, { node: selectedNode });
      }
    }
  }
  /**
   * @param {InlineHtmlAnnotation} inlineHtmlNode - The inline HTML annotation to wrap the text node.
   * @param {VegaRTETransformOptions} [options] - Optional conversion options that may affect how the text node is rendered as HTML.
   * @returns {string} The HTML string representation of the text node wrapped with the inline HTML.
   */
  renderWithWrapperStr(inlineHtmlNode, options) {
    const { htmlTag, child } = inlineHtmlNode;
    const attrStr = this.annotationAttrStr(inlineHtmlNode);
    if (!child) {
      if (HTML_VOID_TAGS.has(htmlTag)) {
        return `<${htmlTag}${attrStr}>`;
      }
      if (!this.text) {
        return `<${htmlTag}${attrStr}></${htmlTag}>`;
      }
      return `<${htmlTag}${attrStr}>${this.renderTextStr(options)}</${htmlTag}>`;
    }
    return `<${htmlTag}${attrStr}>${this.renderWithWrapperStr(child, options)}</${htmlTag}>`;
  }
  /**
   * Generate the attribute string for the inline HTML annotation
   *
   * @param {InlineHtmlAnnotation} annotation - The inline HTML annotation
   * @returns {string} The attribute string for the inline HTML annotation
   */
  annotationAttrStr(annotation) {
    const { customAttribute, customClass, customStyle } = annotation;
    const attrs = [
      ...((customAttribute === null || customAttribute === void 0 ? void 0 : customAttribute.toString()) || []),
      customClass === null || customClass === void 0 ? void 0 : customClass.toString(),
      customStyle === null || customStyle === void 0 ? void 0 : customStyle.toString(),
    ].filter(isNonNullable);
    return attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
  }
  /**
   * Generates the HTML representation of the text node.
   *
   * @param {VegaRTETransformOptions} [options] - Optional conversion options that may affect how the text node is rendered as HTML.
   * @returns {string} The HTML string representation of the text node.
   */
  renderTextStr(options) {
    var _a;
    const NodeTag = ((_a = this.getAnnotationByType(NodeAnnotationTypeEnum.CODE)) === null || _a === void 0 ? void 0 : _a.code)
      ? 'code'
      : 'span';
    const attrStr = super.generateAttributeString(options);
    // If the text contains a newline character, we need to replace it with a <br> tag.
    if (this.text === '\n') {
      return `<br>`;
    }
    return this.shouldRenderAsPlainText() ? this.text : `<${NodeTag}${attrStr}>${this.text}</${NodeTag}>`;
  }
  /**
   * Determines if the text node should be rendered as plain text.
   *
   * @returns {boolean} `true` if the text node should be rendered as plain text, otherwise `false`.
   */
  shouldRenderAsPlainText() {
    const plainTextAnnotation = super.getAnnotationByType(NodeAnnotationTypeEnum.PLAIN_TEXT);
    if (plainTextAnnotation) {
      return (plainTextAnnotation.plainText &&
        Array.from(this.annotationMap.values()).filter((annotation) => (annotation instanceof NodeAnnotation &&
          !(annotation instanceof LinkAnnotation) &&
          !(annotation instanceof PlainTextAnnotation)) ||
          annotation instanceof CommonAnnotation).length === 0);
    }
    else {
      return false;
    }
  }
}
RTETextNode.registerActionStrategyStatus = {};
RTETextNode.htmlTag = 'span';
