import React, { useCallback, useMemo, useRef } from "react";
import { useForwardedRef } from "@bedrock-layout/use-forwarded-ref";
import { useStatefulRef } from "@bedrock-layout/use-stateful-ref";

import { Box } from "../box";
import { Icons } from "../icons";

import { useIsomorphicLayoutEffect } from "../../utils";
import type { Breadcrumbs } from "./";

const defaultDivider = (
  <Box css={{ flexShrink: 0 }} aria-hidden="true">
    <Icons.Slash color="$off-white-72" />
  </Box>
);

// eslint-disable-next-line max-lines-per-function
export const useBreadcrumbs = ({
  crumbs,
  customDivider,
  ref,
}: {
  customDivider: React.ComponentProps<typeof Breadcrumbs>["divider"];
  ref: React.ComponentProps<typeof Breadcrumbs>["ref"];
  crumbs: React.ComponentProps<typeof Breadcrumbs>["crumbs"];
}) => {
  const divider = useMemo(() => customDivider ?? defaultDivider, [customDivider]);
  const rootRef = useForwardedRef(ref);
  const overflowAtIndexRef = useStatefulRef<number | undefined>(undefined);
  const isOverflowRef = useRef(typeof overflowAtIndexRef.current === "number");
  isOverflowRef.current = typeof overflowAtIndexRef.current === "number";

  const previousContainerWidth = useRef(0);
  // eslint-disable-next-line max-statements, max-lines-per-function
  const handleOverflow = useCallback(() => {
    const rootElem = rootRef.current;
    const crumbsListCloneElem = rootElem.children[1]?.children[0];
    const firstCrumb = crumbsListCloneElem?.children[0];
    const dividerElem = crumbsListCloneElem?.children[1];
    const lastCrumb = crumbsListCloneElem?.children[crumbsListCloneElem.children.length - 1];
    const subCrumbsListTrigger = rootElem.children[0]?.children[0]?.children[2];
    if (
      !(crumbsListCloneElem instanceof HTMLElement) ||
      !(firstCrumb instanceof HTMLElement) ||
      !(dividerElem instanceof HTMLElement) ||
      !(lastCrumb instanceof HTMLElement) ||
      !(subCrumbsListTrigger instanceof HTMLElement)
    ) {
      return;
    }

    const firstCrumbWidth = Math.ceil(firstCrumb.getBoundingClientRect().width);
    const lastCrumbWidth = Math.ceil(lastCrumb.getBoundingClientRect().width);
    const dividerWidth = Math.ceil(dividerElem.getBoundingClientRect().width);
    const gap = parseInt(getComputedStyle(crumbsListCloneElem).gap);

    const staticallyOccupiedContainerSpace =
      firstCrumbWidth + gap + dividerWidth + gap + gap + dividerWidth + gap + lastCrumbWidth;

    const subCrumbsListTriggerWidth = subCrumbsListTrigger.offsetWidth;

    const rootElemStyle = getComputedStyle(rootElem);
    const rootElemPaddingLeft = parseInt(rootElemStyle.paddingLeft);
    const rootElemPaddingRight = parseInt(rootElemStyle.paddingRight);

    let shadowListContainerWidth =
      parseInt(rootElemStyle.width) -
      rootElemPaddingLeft -
      rootElemPaddingRight -
      staticallyOccupiedContainerSpace;

    const isGrowing = shadowListContainerWidth > previousContainerWidth.current;
    previousContainerWidth.current = shadowListContainerWidth;
    const crumbsCount = crumbsListCloneElem.children.length;

    shadowListContainerWidth -=
      isOverflowRef.current && ((overflowAtIndexRef.current ?? 0) > 1 || !isGrowing)
        ? subCrumbsListTriggerWidth + gap
        : 0;

    let totalWidth = 0;

    for (let i = crumbsCount - 3; i > 1; i--) {
      const crumb = crumbsListCloneElem.children[i];
      if (!(crumb instanceof HTMLElement)) return;

      const crumbWidth =
        Math.ceil(crumb.getBoundingClientRect().width) + (i !== crumbsCount - 3 ? gap : 0);

      totalWidth += crumbWidth;

      if (totalWidth > shadowListContainerWidth) {
        overflowAtIndexRef.current = Math.ceil(i / 2);
        return;
      }
    }

    overflowAtIndexRef.current = undefined;
  }, [rootRef, overflowAtIndexRef]);

  const observerRef = useRef(
    typeof ResizeObserver === "undefined"
      ? undefined
      : new ResizeObserver(() => {
          handleOverflow();
        })
  );

  useIsomorphicLayoutEffect(() => {
    const observer = observerRef.current;
    const rootElem = rootRef.current;

    observer?.observe(rootElem);
    handleOverflow();

    return () => {
      observer?.unobserve(rootElem);
    };
  }, [rootRef, crumbs.length, handleOverflow]);

  return { rootRef, overflowAtIndex: overflowAtIndexRef.current, divider };
};
