import { useState, useEffect, useRef, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
export type UseActiveHeadingReturnType = string | undefined;

type HeadingEntry = {
  [key: string]: IntersectionObserverEntry;
};

export function useActiveHeading(
  contentElement: HTMLDivElement | null,
  displayedHeaders: Array<string | undefined>,
): UseActiveHeadingReturnType {
  const [heading, setHeading] = useState<string | undefined>(
    displayedHeaders.length > 1 ? displayedHeaders[0] : undefined,
  );
  const [headingElements, setHeadingElements] = useState<HTMLElement[]>([]);
  const headingElementsRef = useRef<HeadingEntry>({});
  const location = useLocation();

  const getVisibleHeadings = () => {
    const visibleHeadings: IntersectionObserverEntry[] = [];

    for (const key in headingElementsRef.current) {
      const headingElement = headingElementsRef.current[key];

      if (headingElement.isIntersecting) {
        visibleHeadings.push(headingElement);
      }
    }

    return visibleHeadings;
  };

  const getIndexFromId = useCallback(
    (id: string) => {
      return headingElements.findIndex((item) => item.id === id);
    },
    [headingElements],
  );

  const findHeaders = (allContent: HTMLDivElement) => {
    const allHeaders = allContent.querySelectorAll<HTMLElement>('.heading-anchor');
    return Array.from(allHeaders);
  };

  const intersectionCallback = useCallback(
    (headings: IntersectionObserverEntry[]) => {
      headingElementsRef.current = headings.reduce(
        (map: HeadingEntry, headingElement: IntersectionObserverEntry) => {
          map[headingElement.target.id] = headingElement;
          return map;
        },
        headingElementsRef.current,
      );

      const totalHeight = window.scrollY + window.innerHeight;
      // handle bottom of the page
      if (totalHeight >= document.body.scrollHeight) {
        const newHeading = headingElements[headingElements?.length - 1]?.id || undefined;
        setHeading(newHeading);
        return;
      }

      const visibleHeadings = getVisibleHeadings();
      if (!visibleHeadings.length) {
        return;
      }

      if (visibleHeadings.length === 1) {
        setHeading(visibleHeadings[0].target.id);
        return;
      }

      visibleHeadings.sort((a, b) => {
        return getIndexFromId(a.target.id) - getIndexFromId(b.target.id);
      });
      setHeading(visibleHeadings[0].target.id);
    },
    [getIndexFromId, headingElements],
  );

  useEffect(() => {
    if (!contentElement) {
      return;
    }
    setHeadingElements(findHeaders(contentElement));
  }, [contentElement, location]);

  useEffect(() => {
    if (!headingElements?.length) {
      return;
    }
    headingElementsRef.current = {};

    // Bottom rootMargin -30% changes part of the view where IntersectionObserver starts to detect headers
    const observer = new IntersectionObserver(intersectionCallback, {
      rootMargin: '0px 0px -30% 0px',
      threshold: 1,
    });
    headingElements?.forEach((element) => {
      if (displayedHeaders.includes(element.id)) {
        observer.observe(element);
      }
    });

    return () => observer.disconnect();
  }, [headingElements, displayedHeaders, intersectionCallback]);

  return heading;
}
