import { useEffect, useState } from "react";

/** Space made free by collapsing actions. */
const spaceFromActions = 24 - 16;
/** Space made free by reducing vertical margin from the sides of the screen. */
const spaceFromScreenMargin = (56 - 32) * 2;
/**
 * The scroll is considered "long enough" when the overflow exceeds
 * this fraction of the size of the visible part.
 */
const longEnoughScrollFraction = 0.5;

/**
 * If the long mode isn't already enforced by the prop,
 * this hook will watch the content size and apply style changes to prevent scroll
 * wherever possible or switch to "long" styles if not.
 */
export function useAutomaticSizing(
  longProp: boolean | undefined,
  contentInnerNode: HTMLDivElement | null
) {
  const [advice, setAdvice] = useState({
    keepActionsCollapsed: false,
    reduceScreenMargin: false,
    useLongStyles: false,
  });

  useEffect(() => {
    if (longProp !== undefined) {
      return;
    }

    if (!contentInnerNode) {
      return;
    }

    const observer = new ResizeObserver(([entry]) => {
      const parentStyles =
        entry.target.parentElement &&
        window.getComputedStyle(entry.target.parentElement, null);
      // The dialog sections use negative margin to position themselves.
      // That is why it's insufficient to combine the height of the content
      //   with the padding of the parent.
      const contentVerticalOffset =
        entry.target.getBoundingClientRect().y -
        (entry.target.parentElement?.getBoundingClientRect().y ?? 0);
      const parentVerticalPadding =
        (contentVerticalOffset !== 0
          ? parseFloat(parentStyles?.getPropertyValue("padding-top") ?? "0")
          : 0) +
        parseFloat(parentStyles?.getPropertyValue("padding-bottom") ?? "0");

      const totalContentSize =
        (entry.borderBoxSize
          ? entry.borderBoxSize[0].blockSize
          : entry.contentRect.height) + parentVerticalPadding;
      const visiblePartSize = entry.target.parentElement?.clientHeight ?? 0;
      const overflow = Math.floor(totalContentSize - visiblePartSize);

      if (overflow > 0) {
        setAdvice((oldAdvice) => {
          const newAdvice = {
            keepActionsCollapsed: false,
            reduceScreenMargin: false,
            useLongStyles: false,
            makeOverflowVisible: false,
          };

          if (overflow <= spaceFromActions && !oldAdvice.keepActionsCollapsed) {
            // If we can prevent overflow by collapsing actions, do that.
            //   However, if we have already done this and the content overflowed again,
            //   for example, if it grew even larger, we can't rely on collapsing actions
            //   to yield us more space, hence the check for the old advice.
            newAdvice.keepActionsCollapsed = true;
          } else if (
            !oldAdvice.reduceScreenMargin &&
            overflow <=
              spaceFromScreenMargin +
                (oldAdvice.keepActionsCollapsed ? 0 : spaceFromActions)
          ) {
            // If we can prevent overflow by collapsing actions and reducing the space from
            //   screen edges, do that. If the content grows dynamically, we need to check
            //   if we have previously reduced overflow by collapsing actions
            //   to correctly calculate how much space this can yield.
            newAdvice.keepActionsCollapsed = true;
            newAdvice.reduceScreenMargin = true;
          } else if (overflow <= visiblePartSize * longEnoughScrollFraction) {
            // If we can't prevent overflow by collapsing actions and reducing the space from
            //   screen edges, the least we can do is to prevent scrollbar jank.
            //   It happens when the overflow is not too large and automatic collapse of actions
            //   on scroll significantly changes the size of the scrollbar.
            newAdvice.keepActionsCollapsed = true;
            newAdvice.useLongStyles = true;
          }

          // If the overflow is significant, don't try reducing it or keeping the actions
          //   from collapsing automatically on scroll. Let the content scroll.
          if (
            !newAdvice.keepActionsCollapsed &&
            !newAdvice.reduceScreenMargin &&
            overflow > spaceFromScreenMargin + spaceFromActions
          ) {
            newAdvice.useLongStyles = true;
          }

          return newAdvice;
        });
      }
    });

    observer.observe(contentInnerNode);

    return () => observer.disconnect();
  }, [longProp, contentInnerNode]);

  return advice;
}
