import { useCallback, useLayoutEffect, useState } from 'react';

type ClientRect = Record<keyof Omit<DOMRect, "toJSON">, number>;

function roundValues(_rect: ClientRect) {
  const rect = {
    ..._rect
  }
  for (const key of Object.keys(rect)) {
    // @ts-ignore
    rect[key] = Math.round(rect[key])
  }
  return rect
}

function shallowDiff(prev: any, next: any) {
  if (prev != null && next != null) {
    for (const key of Object.keys(next)) {
      if (prev[key] != next[key]) {
        return true
      }
    }
  } else if (prev != next) {
    return true
  }
  return false
}

type Verse = {
  surahNo?: number,
  verseNo: number,
  verseText: string
};

type TextSelectionState = {
  clientRect?: ClientRect,
  isCollapsed?: boolean,
  textContent?: string,
  selectedVerses?: Verse[],
  clearSelection?: () => void
}

const defaultState: TextSelectionState = {}

/**
 * useTextSelection(ref)
 * 
 * @description
 * hook to get information about the current text selection
 * 
 */
import { RefObject } from 'react';

export function useTextSelection(target?: HTMLElement | RefObject<HTMLElement>) {

  const [{
    clientRect,
    isCollapsed,
    textContent,
    selectedVerses
  }, setState] = useState<TextSelectionState>(defaultState)

  const reset = useCallback(() => {
    setState(defaultState)
  }, [])

  const clearSelection = useCallback(() => {
    //@ts-ignore
    var sel = window.getSelection ? window.getSelection() : document.selection;
    if (sel) {
      if (sel.removeAllRanges) {
        sel.removeAllRanges();
      } else if (sel.empty) {
        sel.empty();
      }
    }
  }, [])

  const handler = useCallback(() => {
    let newRect: ClientRect
    const selection = window.getSelection()
    let newState: TextSelectionState = { }

    if (selection == null || !selection.rangeCount) {
      setState(newState)
      return
    } 

    const range = selection.getRangeAt(0)
    let targetElement: HTMLElement | null | undefined = null;
    if (target) {
      if ('current' in target) {
        targetElement = target.current;
      } else {
        targetElement = target;
      }
    }
    if (targetElement != null && !targetElement.contains(range.commonAncestorContainer)) {
      setState(newState)
      return
    }

    if (range == null) {
      setState(newState)
      return
    }

    const contents = range.cloneContents();

    if (contents.textContent != null) {
      newState.textContent = contents.textContent;
      newState.selectedVerses = Array.from(contents.querySelectorAll('span[verseno]')).map((span) => {
        const surahNo = parseInt(span.getAttribute('surahno') || '0'); 
        const verseNo = parseInt(span.getAttribute('verseno') || '0'); 
        const verseText = span.textContent || '';
        return { surahNo, verseNo, verseText };
      });
    }

    const rects = range.getClientRects()

    if (rects.length === 0 && range.commonAncestorContainer != null) {
      const el = range.commonAncestorContainer as HTMLElement
      newRect = roundValues(el.getBoundingClientRect().toJSON())
    } else {
      if (rects.length < 1) return
      newRect = roundValues(rects[0].toJSON())
    }
    if (shallowDiff(clientRect, newRect)) {
      newState.clientRect = newRect
    }
    
    newState.isCollapsed = targetElement !== null ? range.collapsed : true;

    setState(newState)
  }, [target])

  useLayoutEffect(() => {
    document.addEventListener('selectionchange', handler)
    document.addEventListener('keydown', handler)
    document.addEventListener('keyup', handler)
    window.addEventListener('resize', handler)

    return () => {
      document.removeEventListener('selectionchange', handler)
      document.removeEventListener('keydown', handler)
      document.removeEventListener('keyup', handler)
      window.removeEventListener('resize', handler)
    }
  }, [target])

  return {
    clientRect,
    isCollapsed,
    textContent,
    selectedVerses,
    clearSelection
  }
}