import { BlockToRTEBlockStrategyAbstract } from './block-to-rte-block-strategy.abstract';
import { RTEHtmlBlock } from '../../../../dto/blocks/html-block';
import { generateUUID } from '../../../../../../utils/misc';
import htmlElementToAnnotationGenerator from './annotation-handler/html-element-to-annotation-generator';
import { Predicate } from './predicate';
import { ALWAYS_INLINE_TAGS, DEDICATED_STRATEGY_TAGS, PHRASING_CONTENT_TAGS, STRUCTURED_CONTAINER_TAGS, isInBlockTextContext, } from './strategy-tags';
/**
 * Converting HTML block elements into RTE blocks.
 */
export class HtmlBlockStrategy extends BlockToRTEBlockStrategyAbstract {
  /**
   * @inheritDoc
   */
  canHandle(elementsArray, currentIndex, options) {
    const element = elementsArray[currentIndex];
    if (element.nodeType === Node.TEXT_NODE || this.isInlineTagWithoutBlockChildren(element, options)) {
      return 0;
    }
    // Skip dedicated-strategy tags unless all children are block-level embedded
    // elements (e.g. <p><video></p>), in which case fall back to html-block.
    if (DEDICATED_STRATEGY_TAGS.has(element.nodeName) && !this.shouldFallBackToHtmlBlock(element)) {
      return 0;
    }
    return !options.autoMatchFormat &&
      this.isCustomHtmlContainer(element) &&
      !Predicate.isTitleOrSubtitle(element)
      ? 1
      : 0;
  }
  /**
   * @inheritDoc
   */
  handle(elementsArray, options) {
    const element = elementsArray[0];
    return RTEHtmlBlock.from(this.generateHtmlBlock(element, options), options);
  }
  /**
   * @inheritDoc
   */
  appendChildBlocks(currentBlock, childBlocks) {
    currentBlock.children = childBlocks.map((block) => {
      block.parent = currentBlock;
      return block;
    });
  }
  /**
   * @inheritDoc
   */
  shouldProceedToElementChildren() {
    return true;
  }
  /**
   * Returns true for elements that should be treated as inline content even if they have no element children (e.g. <span>text</span>).
   * This includes any element in a block text context (P, H1-H6, LI) and any element with text-node children but no element-node children.
   *
   * In raw-HTML mode (autoMatchFormat: false), the text-only heuristic only
   * applies to elements whose parent is itself a text-only wrapper (e.g.
   * {@code <div><span>Title</span></div>}). Structural children of non-text
   * containers (TABLE → CAPTION, TR → TH/TD, etc.) skip this heuristic so
   * they reach {@link isCustomHtmlContainer} and become html-blocks.
   *
   * @param {HTMLElement} element - Element to check.
   * @param {VegaRTETransformOptions} options - Transform options.
   * @returns {boolean} True if the element should be treated as inline content.
   */
  isInlineTagWithoutBlockChildren(element, options) {
    if (isInBlockTextContext(element)) {
      // Empty elements that are not recognized inline elements should not
      // be forced inline — they may represent block-level embedded content
      // (e.g. <video src="...">, <audio>, <iframe src="...">).
      if (element.childNodes.length === 0 &&
        element.textContent === '' &&
        !ALWAYS_INLINE_TAGS.has(element.nodeName) &&
        !PHRASING_CONTENT_TAGS.has(element.nodeName)) {
        return false;
      }
      return true;
    }
    const childNodes = Array.from(element.childNodes);
    const isTextOnly = !childNodes.some((child) => child.nodeType === Node.ELEMENT_NODE) &&
      childNodes.some((child) => child.nodeType === Node.TEXT_NODE);
    if (!isTextOnly) {
      return false;
    }
    // In raw-HTML mode, a text-only element is inline only if:
    // 1. Its parent is not a structured container (TABLE, TR, …)
    // 2. The element itself is phrasing content (SPAN, EM, …)
    // Non-phrasing text-only elements (SUMMARY, FIGCAPTION, OPTION, SVG TEXT,
    // etc.) must fall through to become html-blocks.
    if (options.autoMatchFormat === false) {
      if (STRUCTURED_CONTAINER_TAGS.has(element.parentElement.nodeName))
        return false; // eslint-disable-line @typescript-eslint/no-non-null-assertion
      return PHRASING_CONTENT_TAGS.has(element.nodeName) || ALWAYS_INLINE_TAGS.has(element.nodeName);
    }
    return true;
  }
  /**
   * Returns true when a dedicated-strategy element should fall back to html-block
   * because all its children are non-text, non-inline, non-dedicated elements
   * — indicating purely block-level embedded content that the dedicated text-block
   * strategy cannot represent.
   *
   * @param {HTMLElement} element - The dedicated-strategy element to check.
   * @returns {boolean} True when the element should be treated as html-block.
   */
  shouldFallBackToHtmlBlock(element) {
    const childNodes = Array.from(element.childNodes);
    if (childNodes.length === 0)
      return false;
    // List items are structural children whose dedicated strategies manage
    // both block and inline children independently — never override them.
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    const parentTag = element.parentElement.nodeName;
    if (parentTag === 'UL' || parentTag === 'OL')
      return false;
    return childNodes.every((child) => child.nodeType === Node.ELEMENT_NODE &&
      child.textContent === '' &&
      !ALWAYS_INLINE_TAGS.has(child.nodeName) &&
      !PHRASING_CONTENT_TAGS.has(child.nodeName));
  }
  /**
   * Returns true for elements that should be treated as html containers.
   * A DIV is only a valid container when its first child is not an IMG
   * (IMG children are handled by the image strategy instead).
   *
   * @param {HTMLElement} element - Element to check.
   * @returns {boolean} True if the element qualifies as a html container.
   */
  isCustomHtmlContainer(element) {
    if (element.nodeName !== 'DIV') {
      return true;
    }
    return element.childNodes.length > 0 && element.childNodes[0].nodeName !== 'IMG';
  }
  /**
   * Generate HTML block DTO from an HTML element.
   *
   * @param {HTMLElement} element - HTML element to convert.
   * @param {VegaRTETransformOptions} options - Options for transformation.
   * @returns {VegaRTEHtmlBlock} - HTML block DTO.
   */
  generateHtmlBlock(element, options) {
    return {
      id: generateUUID(),
      type: 'html-block',
      htmlTag: element.nodeName.toLowerCase(),
      annotations: htmlElementToAnnotationGenerator.generate(RTEHtmlBlock, element, options),
      children: [],
    };
  }
}
