import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { createDOMRange, createRectsFromDOMRange } from '@lexical/selection';
import { $getSelection, $isRangeSelection } from 'lexical';
import { useCallback, useEffect } from 'react';
import { useInterval } from 'usehooks-ts';

export const SINGLE_LINE_SELECTION_HEIGHT = 19.5;

export const SelectionWatcherPlugin = ({
  onSelectionUpdate,
  onScroll,
}: {
  onSelectionUpdate: (bounds: DOMRect) => void;
  onScroll: (scrollTop: number) => void;
}) => {
  const [editor] = useLexicalComposerContext();
  const syncSelection = useCallback(() => {
    editor.read(() => {
      const selection = $getSelection();
      const editorRect = editor.getRootElement()?.getBoundingClientRect();
      if (editorRect && $isRangeSelection(selection)) {
        let range = createDOMRange(
          editor,
          selection.anchor.getNode(),
          selection.anchor.offset,
          selection.focus.getNode(),
          selection.focus.offset
        );

        let isZeroLengthSelection = false;
        if (
          range?.endContainer === range?.startContainer &&
          range?.endOffset === range?.startOffset
        ) {
          isZeroLengthSelection = true;
          range =
            createDOMRange(
              editor,
              selection.anchor.getNode(),
              selection.anchor.offset,
              selection.focus.getNode(),
              selection.focus.offset + 1
            ) || range;
        }
        if (!range) return;
        const rects = createRectsFromDOMRange(editor, range);
        if (!rects.length) {
          return;
        }

        const rectsWithWidth = rects.filter((rect) => rect.width > 0);

        const validRects = rectsWithWidth.length === 0 ? rects : rectsWithWidth;

        const left = Math.min(...validRects.map((rect) => rect.left));
        const top = Math.min(...validRects.map((rect) => rect.top));
        const right = Math.max(...validRects.map((rect) => rect.right));
        const bottom = Math.max(...validRects.map((rect) => rect.bottom));

        const outerRect = new DOMRect(
          left - editorRect.left,
          top - editorRect.top - 2,
          isZeroLengthSelection ? 0 : right - left,
          bottom - top + 5
        );
        onSelectionUpdate(outerRect);
      }
    });
  }, [editor, onSelectionUpdate]);

  useInterval(syncSelection, 250);

  useEffect(() => {
    const deregisterUpdateListener = editor.registerUpdateListener(() => {
      syncSelection();

      let deregisterScrollListener = () => {};

      const rootElement = editor.getRootElement();
      const rootParent = rootElement?.parentElement;
      if (rootParent) {
        const handleScroll = () => {
          onScroll(rootParent.scrollTop);
        };
        rootParent.addEventListener('scroll', handleScroll);
        deregisterScrollListener = () => {
          rootParent.removeEventListener('scroll', handleScroll);
        };
      }

      return () => {
        deregisterUpdateListener();
        deregisterScrollListener();
      };
    });
  }, [onScroll, syncSelection, editor]);
  return null;
};
