import { useLayoutEffect, useRef, useState } from 'react';
import { useWindowSize } from '@openedx/paragon';

const invisibleStyle = {
  position: 'absolute',
  left: 0,
  pointerEvents: 'none',
  visibility: 'hidden',
  maxWidth: '100%',
};

/**
 * This hook will find the index of the last child of a containing element
 * that fits within its bounding rectangle. This is done by summing the widths
 * of the children until they exceed the width of the container.
 *
 * The hook returns an array containing:
 * [indexOfLastVisibleChild, containerElementRef, invisibleStyle, overflowElementRef]
 *
 * indexOfLastVisibleChild - the index of the last visible child
 * containerElementRef - a ref to be added to the containing html node
 * invisibleStyle - a set of styles to be applied to child of the containing node
 *    if it needs to be hidden. These styles remove the element visually, from
 *    screen readers, and from normal layout flow. But, importantly, these styles
 *    preserve the width of the element, so that future width calculations will
 *    still be accurate.
 * overflowElementRef - a ref to be added to an html node inside the container
 *    that is likely to be used to contain a "More" type dropdown or other
 *    mechanism to reveal hidden children. The width of this element is always
 *    included when determining which children will fit or not. Usage of this ref
 *    is optional.
 */
export default function useIndexOfLastVisibleChild(isSidebarOpen) {
  const containerElementRef = useRef(null);
  const overflowElementRef = useRef(null);
  const containingRectRef = useRef({});
  const [indexOfLastVisibleChild, setIndexOfLastVisibleChild] = useState(-1);
  const windowSize = useWindowSize();

  useLayoutEffect(() => {
    const containingRect = containerElementRef.current.getBoundingClientRect();

    // No-op if the width is unchanged.
    // (Assumes tabs themselves don't change count or width).
    if (!containingRect.width === containingRectRef.current.width) {
      return;
    }
    // Update for future comparison
    containingRectRef.current = containingRect;

    // Get array of child nodes from NodeList form
    const childNodesArr = Array.prototype.slice.call(containerElementRef.current.children);
    const { nextIndexOfLastVisibleChild } = childNodesArr
      // filter out the overflow element
      .filter(childNode => childNode !== overflowElementRef.current)
      // sum the widths to find the last visible element's index
      .reduce((acc, childNode, index) => {
        // use floor to prevent rounding errors
        acc.sumWidth += Math.floor(childNode.getBoundingClientRect().width);
        if (acc.sumWidth <= containingRect.width) {
          acc.nextIndexOfLastVisibleChild = index;
        }
        return acc;
      }, {
        // Include the overflow element's width to begin with. Doing this means
        // sometimes we'll show a dropdown with one item in it when it would fit,
        // but allowing this case dramatically simplifies the calculations we need
        // to do above.
        sumWidth: overflowElementRef.current ? overflowElementRef.current.getBoundingClientRect().width : 0,
        nextIndexOfLastVisibleChild: -1,
      });

    setIndexOfLastVisibleChild(nextIndexOfLastVisibleChild);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [windowSize, containerElementRef.current, isSidebarOpen]);

  return [indexOfLastVisibleChild, containerElementRef, invisibleStyle, overflowElementRef];
}
