import { Arr } from '@ephox/katamari';
import { Attribute, Insert, SugarElement, SugarText } from '@ephox/sugar';

import type DOMUtils from 'tinymce/core/api/dom/DOMUtils';
import type EditorSelection from 'tinymce/core/api/dom/Selection';

import * as TextCollect from './TextCollect';
import * as TextPosition from './TextPosition';
import type { Pattern, TextMatch, TextSection } from './Types';

const find = (pattern: Pattern, sections: TextSection[]): TextMatch[][] =>
  Arr.bind(sections, (section) => {
    const elements = section.elements;
    const content = Arr.map(elements, SugarText.get).join('');
    const positions = TextPosition.find(content, pattern, section.sOffset, content.length - section.fOffset);
    return TextPosition.extract(elements, positions);
  });

const mark = (matches: TextMatch[][], replacementNode: HTMLElement): void => {
  // Walk backwards and mark the positions
  // Note: We need to walk backwards so the position indexes don't change
  Arr.eachr(matches, (match, idx) => {
    Arr.eachr(match, (pos) => {
      const wrapper = SugarElement.fromDom(replacementNode.cloneNode(false) as HTMLElement);
      Attribute.set(wrapper, 'data-mce-index', idx);
      const textNode = pos.element.dom;
      if (textNode.length === pos.finish && pos.start === 0) {
        Insert.wrap(pos.element, wrapper);
      } else {
        if (textNode.length !== pos.finish) {
          textNode.splitText(pos.finish);
        }
        const matchNode = textNode.splitText(pos.start);
        Insert.wrap(SugarElement.fromDom(matchNode), wrapper);
      }
    });
  });
};

const findAndMark = (dom: DOMUtils, pattern: Pattern, node: Node, replacementNode: HTMLElement): number => {
  const textSections = TextCollect.fromNode(dom, node);
  const matches = find(pattern, textSections);
  mark(matches, replacementNode);
  return matches.length;
};

const findAndMarkInSelection = (dom: DOMUtils, pattern: Pattern, selection: EditorSelection, replacementNode: HTMLElement): number => {
  const bookmark = selection.getBookmark();

  // Handle table cell selection as the table plugin enables
  // you to fake select table cells and perform actions on them
  const nodes = dom.select('td[data-mce-selected],th[data-mce-selected]');
  const textSections = nodes.length > 0 ? TextCollect.fromNodes(dom, nodes) : TextCollect.fromRng(dom, selection.getRng());

  // Find and mark matches
  const matches = find(pattern, textSections);
  mark(matches, replacementNode);

  // Restore the selection
  selection.moveToBookmark(bookmark);
  return matches.length;
};

export {
  findAndMark,
  findAndMarkInSelection
};
