import { getActiveElement } from '../../utils/component';
let processing = false;
/**
 * Simulate a selection for browsers that don't support shadow selection.
 * Cypress test case added in https://github.com/heartlandpayments/Vega/pull/1561/files#diff-83bdbaaf47fc156ab878099aa1f21b0e8ab92abe5852521299f1fb6efab001c2R300
 */
export class ShadowSelection {
  constructor() {
    this.rangeCount = 0;
    this.ranges = [];
  }
  /**
   * Returns a Range object at a specified index.
   *
   * @param {number} index - A specified index.
   * @returns {Range} - A Range object.
   */
  getRangeAt(index) {
    return this.ranges[index];
  }
  /**
   * Adds a Range to ranges array.
   *
   * @param {Range} range - The range of selection.
   */
  addRange(range) {
    this.ranges.push(range);
    this.rangeCount = this.ranges.length;
    if (!processing) {
      const windowSelection = window.getSelection();
      windowSelection.removeAllRanges();
      windowSelection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
    }
  }
  /**
   * Clears all ranges.
   */
  removeAllRanges() {
    this.ranges = [];
    this.rangeCount = 0;
  }
}
/**
 * Due to the ShadowRoot.getSelection is not a standard API, we add this polyfill to achieve this function.
 *
 * - On Chromium, calling document.getSelection will not pierce into the Shadow DOM and gives you some unhelpful high-level element.
 *   But it does expose the non-standard getSelection method on the ShadowRoot.
 *
 * - On Firefox, it does not implement ShadowRoot.getSelection,
 *   but document.getSelection will pierce through shadow dom and give you the exact element.
 *
 * - On Safari, Selection.getComposedRanges is supported as of v17. On versions before that,
 *   ShadowRoot.getSelection is not supported and apparently document.getSelection does not pierce the Shadow DOM, meaning you are just out of luck.
 *
 * https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot#shadowroot.getselection
 * https://stackoverflow.com/a/70523247
 */
export class ShadowSelectionPolyfill {
  constructor() {
    this.supportShadowSelection = typeof window.ShadowRoot.prototype['getSelection'] === 'function';
    this.supportBeforeInput = typeof window.InputEvent.prototype.getTargetRanges === 'function';
    this.isFirefox = window.navigator.userAgent.toLowerCase().indexOf('firefox') > 0;
    this.listen();
  }
  /**
   * For Firefox, we directly use document.getSelection()
   * For other browser which not support shadowroot.getSelection, we use this.selection which is built by us.
   * For browser which supported shadowroot.getSelection, we use shadowRoot.getselection().
   *
   * @param {ShadowRoot} shadow - A shadow.
   * @returns {Nullable<Selection>} - The shadow selection.
   */
  getSelection(shadow) {
    if (this.supportShadowSelection) {
      return shadow['getSelection']();
    }
    else {
      return this.isFirefox
        ? this.getFirefoxSelection()
        : this.polyfilledShadowSelection;
    }
  }
  /**
   * Get selection in firefox.
   *
   * Example:
   *
   * <span>span1</span><span>span2</span><span>span3</span>
   *
   * In firefox, while selected `span2`, the range be likely to:
   * range:
   *   startContainer: #text 'span1'
   *   startOffset: 5
   * 	 endContainer: #text 'span3'
   *   endOffset: 0
   * start is end of brefore node and end is start of after node.
   *
   * So we should modify the startContainer and endContainer here.
   *
   * modified range:
   *   startContainer: #text 'span2'
   *   startOffset: 0
   * 	 endContainer: #text 'span2'
   *   endOffset: 5
   *
   * @returns {Selection} - document selection
   */
  getFirefoxSelection() {
    // eslint-disable-next-line no-restricted-globals
    const selection = document.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const startNode = range.startContainer;
      const endNode = range.endContainer;
      if (startNode !== endNode && startNode.textContent.length === range.startOffset) {
        const nextSibling = startNode.nodeType === Node.TEXT_NODE
          ? startNode.parentNode.nextSibling
          : startNode.nextSibling;
        range.setStart(nextSibling.firstChild, 0);
      }
      if (endNode !== startNode && range.endOffset === 0) {
        const previousSibling = endNode.nodeType === Node.TEXT_NODE
          ? endNode.parentNode.previousSibling
          : endNode.previousSibling;
        range.setEnd(previousSibling.lastChild, previousSibling.lastChild.textContent.length);
      }
    }
    return selection;
  }
  /**
   * Sets up event listeners for `selectionchange` and `beforeinput` events to simulate shadow selection.
   */
  listen() {
    if (this.isFirefox || this.supportShadowSelection || !this.supportBeforeInput)
      return;
    this.polyfilledShadowSelection = new ShadowSelection();
    window.addEventListener('selectionchange', () => {
      if (!processing) {
        processing = true;
        const active = getActiveElement();
        if (active && active.hasAttribute('contenteditable')) {
          // This command will not actually work, it only trigger the `beforeinput` event,
          // the `beforeinput` event will prevented this command work.
          // eslint-disable-next-line no-restricted-globals
          document.execCommand('indent');
        }
        else {
          this.polyfilledShadowSelection.removeAllRanges();
        }
        processing = false;
      }
    }, true);
    window.addEventListener('beforeinput', (event) => {
      if (processing) {
        const ranges = event.getTargetRanges();
        const range = ranges[0];
        const newRange = new Range();
        newRange.setStart(range.startContainer, range.startOffset);
        newRange.setEnd(range.endContainer, range.endOffset);
        this.polyfilledShadowSelection.removeAllRanges();
        this.polyfilledShadowSelection.addRange(newRange);
        event.preventDefault();
        event.stopImmediatePropagation();
      }
    }, true);
  }
}
export default new ShadowSelectionPolyfill();
