import { TextStyleAnnotation } from '../annotations/text-style-annotation';
import { ModifyContentActionType } from '../actions/modify-content-action.abstract';
import { AppendChildrenAction } from '../actions/append-child-nodes-action';
import { RTEBlock } from './block.abstract';
import { TextStyleAnnotationAction } from '../actions/text-style-annotation-action';
import { NodeAnnotationTypeEnum } from '../annotations/node-annotation.abstract';
import { LinkAnnotationAction } from '../actions/link-annotation-action';
import { LinkGroupAnnotationAction } from '../actions/link-group-annotation-action';
import { generateUUID } from '../../../../utils/misc';
import { ActionHandleStrategyRegistry } from '../action-handle-strategies/action-handle-strategy-registry';
import { BlockUpdateTextStyleStrategy } from '../action-handle-strategies/apply-annotation-strategies/block-update-text-style-strategy';
import { BlockUpdateLinkGroupStrategy } from '../action-handle-strategies/apply-annotation-strategies/block-update-link-group-strategy';
import { BlockSplitWithTextNodeStrategy } from '../action-handle-strategies/modify-content-strategies/block-split-with-text-node-strategy';
import { BlockReplaceNodesStrategy } from '../action-handle-strategies/modify-content-strategies/block-replace-nodes-strategy';
import { BlockMergeNodesStrategy } from '../action-handle-strategies/modify-content-strategies/block-merge-nodes-strategy';
import { BlockInsertLineBreakStrategy } from '../action-handle-strategies/modify-content-strategies/block-insert-line-break-strategy';
import { BlockInsertLineBreakWithBlocksStrategy } from '../action-handle-strategies/modify-content-strategies/block-insert-line-break-with-blocks-strategy';
import { BlockInsertImageIntoTextStrategy } from '../action-handle-strategies/modify-content-strategies/block-insert-image-into-text-strategy';
import { BlockTransformToListStrategy } from '../action-handle-strategies/modify-content-strategies/block-transform-to-list-strategy';
import { InternalAnnotationTypeEnum } from '../annotations/annotation.abstract';
import { isNonNullable } from '../../../../types/type-guard';
import { HorizontalAlignmentAnnotationAction } from '../actions/horizontal-alignment-annotation-action';
import { BlockUpdateHorizontalAlignmentStrategy } from '../action-handle-strategies/apply-annotation-strategies/block-update-horizontal-alignment-strategy';
import { BlockUpdateIndentStrategy } from '../action-handle-strategies/apply-annotation-strategies/block-update-indent-strategy';
import { IndentAnnotationAction } from '../actions/indent-annotation-action';
import RTEDTOClassManager from '../../../../helpers/rte-manager/dto-class-manager';
import { BlockUnlinkLinkGroupStrategy } from '../action-handle-strategies/modify-content-strategies/block-unlink-link-group-strategy';
/**
 * Text block
 */
export class RTETextBlock extends RTEBlock {
  constructor(id, type, options) {
    super(id);
    this.children = [];
    this.type = type;
    const { autoMatchFormat } = options || { autoMatchFormat: true };
    if (autoMatchFormat) {
      if (RTETextBlock.isAcceptableTextStyle(type)) {
        this.annotationMap = new Map([TextStyleAnnotation.from(type)]);
      }
      else {
        this.annotationMap = new Map([TextStyleAnnotation.from('paragraph')]);
      }
    }
  }
  /**
   * Converts a VegaRTETextBlock into an RTETextBlock.
   *
   * @param {VegaRTETextBlock} block - The block object
   * @param {VegaRTETransformOptions} options - Optional transformation options.
   * @returns {RTETextBlock} A RTETextBlock class
   */
  static from(block, options = { autoMatchFormat: true }) {
    var _a, _b;
    const textBlock = new RTETextBlock(block.id, block.type, options);
    const annotations = options.autoMatchFormat
      ? Object.assign(Object.assign({}, block.annotations), { textStyle: (_b = (_a = block.annotations) === null || _a === void 0 ? void 0 : _a.textStyle) !== null && _b !== void 0 ? _b : textBlock.type }) : block.annotations;
    textBlock.children = block.nodes
      .map((richText) => {
      const NodeClass = RTEDTOClassManager.getRTENodeClass(richText.type);
      if (NodeClass) {
        return NodeClass.from(richText, textBlock, options);
      }
    })
      .filter(isNonNullable);
    if (annotations) {
      super.convertAnnotationsToMap(textBlock.annotationMap, annotations);
    }
    return textBlock;
  }
  /**
   * The function `createAnnotationEntity` creates a block annotation entity based on the provided type
   * and value.
   *
   * @param {keyof VegaRTEBlockAnnotations} type - The `type` parameter is a key of the `VegaRTEBlockAnnotations` enum, which specifies
   * the type of annotation entity to create.
   * @param {unknown} value - The `value` parameter in the `createAnnotationEntity` function is the value
   * that will be used to create the annotation entity. It can be of any type depending on the specific
   * annotation being created.
   * @returns {Nullable<BlockAnnotationsEntity>} The `createAnnotationEntity` function returns a nullable tuple containing a
   * `BlockAnnotationTypeEnum` and a `BlockAnnotation` object.
   */
  static createAnnotationEntity(type, value) {
    if (type === 'textStyle') {
      return TextStyleAnnotation.from(value);
    }
    else {
      return super.createAnnotationEntity(type, value);
    }
  }
  /**
   * Checks if a given text block type is one of the acceptable text styles.
   *
   * @param {VegaRTETextBlockType} type - The text block type
   * @returns {boolean} Return a boolean value indicating whether the `type` parameter is valid.
   */
  static isAcceptableTextStyle(type) {
    return [
      'title',
      'subtitle',
      'paragraph',
      'heading-1',
      'heading-2',
      'heading-3',
      'heading-4',
      'heading-5',
      'heading-6',
    ].includes(type);
  }
  /**
   * Merges RTETextNode nodes based on certain conditions.
   *
   * @param {AnnotationAction} action - AnnotationAction
   */
  mergeNodes(action) {
    this.children = this.children.reduce((prev, cur) => {
      const lastNode = prev.at(-1);
      if (action instanceof LinkAnnotationAction && action.needMergeNode) {
        const lastNodeLinkAnnotation = lastNode === null || lastNode === void 0 ? void 0 : lastNode.getAnnotationByType(NodeAnnotationTypeEnum.LINK);
        const curNodeLinkAnnotation = cur.getAnnotationByType(NodeAnnotationTypeEnum.LINK);
        if (lastNodeLinkAnnotation &&
          curNodeLinkAnnotation &&
          lastNodeLinkAnnotation.toEqual(curNodeLinkAnnotation)) {
          return prev;
        }
      }
      // TODO: handle other cases like merge bold nodes
      return [...prev, cur];
    }, []);
  }
  /**
   * @inheritDoc
   */
  toJSON() {
    return Object.assign(Object.assign({}, super.toJSON()), { type: this.type, nodes: this.children.map((node) => node.toJSON()) });
  }
  /**
   * @inheritDoc
   */
  toHtml(options) {
    const BlockTag = this.getBlockTag();
    const attrStr = super.generateAttributeString(options);
    const nodesHtml = this.getChildrenHtml(options);
    return super.shouldRenderAsInternalWrapper()
      ? nodesHtml
      : [`<${BlockTag}${attrStr}>`, nodesHtml, `</${BlockTag}>`].join('');
  }
  /**
   * Clone the nodes to a new block
   *
   * @param {RTEBlock} nodes - The nodes will be copied
   * @returns {RTETextBlock} New block
   */
  cloneWithNodes(nodes) {
    const block = new RTETextBlock(generateUUID(), this.type);
    block.annotationMap = super.cloneAnnotations();
    block.apply(new AppendChildrenAction(nodes));
    return block;
  }
  /**
   * @inheritDoc
   */
  clone(parent) {
    const clonedBlock = new RTETextBlock(this.id, this.type);
    clonedBlock.children = this.children.map((node) => node.clone(clonedBlock));
    clonedBlock.annotationMap = super.cloneAnnotations();
    clonedBlock.parent = parent;
    return clonedBlock;
  }
  /**
   * @inheritDoc
   */
  isNotEmpty() {
    return this.children.length > 0 && this.children.some((node) => !node.isEmpty());
  }
  /**
   * @inheritDoc
   */
  getLastNode() {
    return this.children[this.children.length - 1];
  }
  /**
   * Get the HTML tag for the block based on its type.
   *
   * @returns {RTETextBlockTagType} The HTML tag for the block.
   */
  getBlockTag() {
    switch (this.type) {
      case 'title':
      case 'subtitle':
        return `div`;
      case 'heading-1':
        return `h1`;
      case 'heading-2':
        return `h2`;
      case 'heading-3':
        return `h3`;
      case 'heading-4':
        return `h4`;
      case 'heading-5':
        return `h5`;
      case 'heading-6':
        return `h6`;
      case 'paragraph':
      default:
        return 'p';
    }
  }
  /**
   * Get the HTML string for the children nodes, including handling link group annotations.
   *
   * @param {VegaRTETransformOptions} [options] - Optional conversion options that may affect how the children nodes are rendered as HTML.
   * @returns {string} The HTML string representing the children nodes.
   */
  getChildrenHtml(options) {
    const linkGroupAnnotation = this.getAnnotationByType(InternalAnnotationTypeEnum.LINK_GROUP);
    let nodesHtml = this.renderGroupedNodesAtDepth(this.children, 0, options);
    if (linkGroupAnnotation && this.children.length > 0) {
      nodesHtml = this.generateLinkHtml(linkGroupAnnotation, nodesHtml, options);
    }
    return nodesHtml;
  }
  /**
   * @inheritDoc
   */
  doAnnotationActionApply(action) {
    const strategy = ActionHandleStrategyRegistry.get(action.constructor.name, RTETextBlock.name);
    if (strategy) {
      strategy.execute(action, this);
    }
  }
  /**
   * Recursively groups `nodes` by their inline HTML wrapper at the given
   * `depth`, emitting shared wrappers for adjacent siblings with the same
   * `groupId` and descending into the next depth for each group.
   *
   * @param {RTETextNode[]} nodes - The nodes to render.
   * @param {number} depth - Current depth in the inline HTML chain (0 = outermost).
   * @param {VegaRTETransformOptions} [options] - Optional conversion options.
   * @returns {string} HTML string with grouped wrappers at this depth.
   */
  renderGroupedNodesAtDepth(nodes, depth, options) {
    var _a;
    let result = '';
    let i = 0;
    while (i < nodes.length) {
      const tag = nodes[i].getInlineHtmlTagAtDepth(depth);
      if (!(tag === null || tag === void 0 ? void 0 : tag.groupId)) {
        result += nodes[i].renderHtmlFromDepth(depth, options);
        i++;
        continue;
      }
      let groupEnd = i + 1;
      while (groupEnd < nodes.length &&
        ((_a = nodes[groupEnd].getInlineHtmlTagAtDepth(depth)) === null || _a === void 0 ? void 0 : _a.groupId) === tag.groupId) {
        groupEnd++;
      }
      if (groupEnd === i + 1) {
        // Single node in group — render normally.
        result += nodes[i].renderHtmlFromDepth(depth, options);
      }
      else {
        const innerHtml = this.renderGroupedNodesAtDepth(nodes.slice(i, groupEnd), depth + 1, options);
        result += `<${tag.htmlTag}${tag.attrStr}>${innerHtml}</${tag.htmlTag}>`;
      }
      i = groupEnd;
    }
    return result;
  }
  /**
   * Generate Link Html
   *
   * @param {LinkGroupAnnotation} linkGroupAnnotation - linkGroupAnnotation
   * @param {string} nodesHtml - The nodes html
   * @param {VegaRTETransformOptions} [options] - Optional conversion options that may affect how the children nodes are rendered as HTML.
   * @returns {string} link html
   */
  generateLinkHtml(linkGroupAnnotation, nodesHtml, options) {
    nodesHtml = '';
    let linkGroupNodes = [];
    for (let i = 0; i < this.children.length; i++) {
      const currentLinkAnnotation = this.children[i].getAnnotationByType(NodeAnnotationTypeEnum.LINK);
      const nextLinkAnnotation = this.children[i + 1]
        ? this.children[i + 1].getAnnotationByType(NodeAnnotationTypeEnum.LINK)
        : null;
      if (currentLinkAnnotation) {
        linkGroupNodes.push(this.children[i]);
        if (!nextLinkAnnotation ||
          currentLinkAnnotation.link.groupKey !== nextLinkAnnotation.link.groupKey) {
          const linkAttributes = linkGroupAnnotation.toString(currentLinkAnnotation, {
            standalone: true,
            filterInlineStyles: options === null || options === void 0 ? void 0 : options.filterInlineStyles,
          });
          nodesHtml += [
            `<a${linkAttributes ? ` ${linkAttributes}` : ''}>`,
            this.renderGroupedNodesAtDepth(linkGroupNodes, 0, options),
            `</a>`,
          ].join('');
          linkGroupNodes = [];
        }
      }
      else {
        nodesHtml += this.children[i].toHtml(options);
      }
    }
    return nodesHtml;
  }
}
(() => {
  ActionHandleStrategyRegistry.register(TextStyleAnnotationAction.name, RTETextBlock.name, new BlockUpdateTextStyleStrategy());
  ActionHandleStrategyRegistry.register(HorizontalAlignmentAnnotationAction.name, RTETextBlock.name, new BlockUpdateHorizontalAlignmentStrategy());
  ActionHandleStrategyRegistry.register(IndentAnnotationAction.name, RTETextBlock.name, new BlockUpdateIndentStrategy());
  ActionHandleStrategyRegistry.register(LinkGroupAnnotationAction.name, RTETextBlock.name, new BlockUpdateLinkGroupStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.UNLINK_LINK_GROUP, RTETextBlock.name, new BlockUnlinkLinkGroupStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.SPLIT_BLOCK_WITH_NODE, RTETextBlock.name, new BlockSplitWithTextNodeStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.REPLACE_CHILD_NODES, RTETextBlock.name, new BlockReplaceNodesStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.MERGE_TWO_BLOCKS_NODES, RTETextBlock.name, new BlockMergeNodesStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.LINE_BREAK_SINGLE_BLOCK, RTETextBlock.name, new BlockInsertLineBreakStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.LINE_BREAK_MULTIPLE_BLOCKS, RTETextBlock.name, new BlockInsertLineBreakWithBlocksStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.INSERT_IMAGE_TO_BLOCK, RTETextBlock.name, new BlockInsertImageIntoTextStrategy());
  ActionHandleStrategyRegistry.register(ModifyContentActionType.TRANSFORM_LIST_BLOCK, RTETextBlock.name, new BlockTransformToListStrategy());
})();
