import { isFirefox } from "./browser";

export const getSelection = () => {
  return window.getSelection();
};

const getOffset = (element: Node, rangeContainer: Node, pos: number, offset = 0): number => {
  if (element === rangeContainer) {
    return offset + pos;
  }

  const childs = element.childNodes;
  const { length } = childs;
  if (length === 0) {
    return offset + pos;
  }

  let _offset = offset;
  for (let i = 0; i < length; i++) {
    const child = childs[i];
    const textLength = child?.textContent?.length || 0;

    if (child.contains(rangeContainer)) {
      return getOffset(child, rangeContainer, pos, _offset);
    }
    _offset += textLength;
  }

  console.error("range not found");
  return _offset + pos;
};

export const getFirstRange = (target: Node) => {
  const selection = getSelection();
  if (!selection) {
    return;
  }
  if (selection.rangeCount === 0) {
    return null;
  }
  const range = selection.getRangeAt(0);

  if (range) {
    if (!target.contains(range.startContainer) || !target.contains(range.endContainer)) {
      return null;
    }

    if (range.startContainer === target) {
      let priorityEnd = 0;
      if (isFirefox()) {
        // @ts-ignore - Firefox has innerText property on Node but TypeScript doesn't recognize it
        priorityEnd = target?.innerText?.length;
      }
      return {
        range,
        startOffset: range.startOffset,
        endOffset: priorityEnd || range.endOffset,
      };
    }

    const startOffset = getOffset(target, range.startContainer, range.startOffset);
    const endOffset = getOffset(target, range.endContainer, range.endOffset);

    return {
      range,
      startOffset,
      endOffset,
    };
  }
  return null;
};

export const isActiveElement = (element?: HTMLElement | null) => {
  return element && document.activeElement === element;
};

const foundElementByPos = (
  element: Node,
  pos: number,
  offset: number,
  isLast: boolean,
  callback: (foundElem: Node, pos: number) => void
) => {
  const childs = element.childNodes;
  const { length } = childs;
  let _offset = offset;
  for (let i = 0; i < length; i++) {
    const _isLast = i === length - 1;
    const child = childs[i];
    if (!child || child.textContent == null) {
      continue;
    }

    if (
      isLast && _isLast
        ? pos <= _offset + child.textContent.length
        : pos < _offset + child.textContent.length
    ) {
      if (child.nodeName === "SPAN") {
        if (child.textContent.length === 0 && child.childNodes.length === 0) {
          callback(child, pos - _offset);
        } else {
          foundElementByPos(child, pos, _offset, isLast && _isLast, callback);
        }
      } else {
        callback(child, pos - _offset);
      }
      break;
    } else {
      _offset += child.textContent.length;
      continue;
    }
  }
};

export const clearRange = () => {
  const selection = window.getSelection();
  selection?.removeAllRanges();
};

export const setRange = (element: Node, startOffset: number, endOffset: number) => {
  // need update?
  // const firstRange = getFirstRange(element);
  // if (firstRange?.startOffset === startOffset && firstRange?.endOffset === endOffset) {
  //   return;
  // }

  // reset new range
  const range = new Range();
  const { length } = element.childNodes;

  try {
    if (length === 0) {
      range.setStart(element, 0);
      range.setEnd(element, 0);
    } else if (length === 1 && element.firstChild?.nodeType === Node.TEXT_NODE) {
      range.setStart(element, startOffset);
      range.setEnd(element, endOffset);
    } else {
      foundElementByPos(element, startOffset, 0, true, (found, pos) => {
        range.setStart(found, pos);
      });
      foundElementByPos(element, endOffset, 0, true, (found, pos) => {
        range.setEnd(found, pos);
      });
    }

    const selection = window.getSelection();
    selection?.removeAllRanges();
    selection?.addRange(range);
  } catch (error) {
    console.error(error);
  }
};
