import {
  $isElementNode,
  $isLineBreakNode,
  $isTextNode,
  RootNode,
  TextNode,
} from 'lexical';

export default function $findNodeAndOffsetAtCharacterPosition(
  root: RootNode,
  targetCharacters: number
): null | { node: TextNode; offset: number } {
  let node = root.getFirstChild();
  let lastSeenNode: TextNode | null = null;
  let currentCharacters = 0;

  mainLoop: while (node !== null) {
    if ($isElementNode(node)) {
      const child = node.getFirstChild();

      if (child !== null) {
        node = child;
        continue;
      }
    } else if ($isTextNode(node)) {
      lastSeenNode = node;
      const characters = node.getTextContentSize();

      if (currentCharacters + characters > targetCharacters) {
        return {
          node,
          offset: Math.max(targetCharacters - currentCharacters, 0),
        };
      }
      currentCharacters += characters;
    } else if ($isLineBreakNode(node)) {
      if (lastSeenNode && currentCharacters + 1 > targetCharacters) {
        return {
          node: lastSeenNode,
          offset: lastSeenNode.getTextContentSize(),
        };
      }
      currentCharacters += 1;
    }
    const sibling = node.getNextSibling();

    if (sibling !== null) {
      node = sibling;
      continue;
    }
    let parent = node.getParent();
    while (parent !== null) {
      const parentSibling = parent.getNextSibling();

      if (parentSibling !== null) {
        node = parentSibling;
        continue mainLoop;
      }
      parent = parent.getParent();
    }
    break;
  }

  if (lastSeenNode) {
    return { node: lastSeenNode, offset: lastSeenNode.getTextContentSize() };
  }
  return null;
}
