import React from "react";

import Slugger from "github-slugger";

import { useWindowSize } from "../hooks/useWindowSize";
import { notEmpty } from "../utils";

import "../styles/TableOfContents.css";

type TableOfContentsSidebarProps = React.HTMLProps<HTMLDivElement> & {
  variant?: "grid-with-sidebar";
};

const TableOfContentsSidebar: React.FC<TableOfContentsSidebarProps> = ({
  variant,
  ...props
}) => {
  const windowSize = useWindowSize();

  return (
    <aside
      className={`table-of-contents ${variant ?? ""}`}
      style={{
        height:
          windowSize.height - 20 > 0 ? windowSize.height - 20 : "max-content",
        // ...(scrollPosition.y >= 100
        //   ? { transform: `translate3d(0px, ${scrollPosition.y - 80}px, 0px)` }
        //   : {}),
      }}
      {...props}
    />
  );
};

const overrideSubNavLinksWithSmoothScroll = ({
  querySelector,
}: {
  querySelector: string;
}) => {
  // Overrides the anchor behavior to smooth scroll instead
  // Came from https://css-tricks.com/sticky-smooth-active-nav/
  const subnavLinks =
    document.querySelectorAll<HTMLAnchorElement>(querySelector);
  subnavLinks.forEach((link) => {
    link.addEventListener("click", (event: MouseEvent) => {
      event.preventDefault();

      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
      const ele = event.target as HTMLAnchorElement;
      if (!ele) return;

      const target = document.querySelector(`[href='${ele["hash"]}']`);
      target?.scrollIntoView({ behavior: "smooth", block: "start" });
      document.location.hash = ele["hash"];
    });
  });
};

const openParents = (link: HTMLAnchorElement | HTMLUListElement) => {
  const closestUl = link.closest("ul");
  if (!closestUl) {
    return;
  }
  closestUl.classList.add("open");
  const parentUl: HTMLUListElement | null = (
    link.parentNode as HTMLLIElement
  ).closest("ul:not(.open)");
  if (parentUl) {
    openParents(parentUl);
  }
};

export const closeParents = (
  link: HTMLAnchorElement | HTMLUListElement,
): void => {
  const closestUl = link.closest("ul.open");
  if (!closestUl) {
    return;
  }
  closestUl.classList.remove("open");
  const parentUl: HTMLUListElement | null = (
    link.parentNode as HTMLLIElement
  ).closest("ul.open");
  if (parentUl) {
    closeParents(parentUl);
  }
};

const updateSidebarOnScroll = ({
  querySelector,
}: {
  querySelector: string;
}) => {
  const subnavLinks =
    document.querySelectorAll<HTMLAnchorElement>(querySelector);
  const fromTop = window.scrollY;
  let currentPossibleAnchor: HTMLAnchorElement | undefined;
  const offset = 100;

  // Scroll down to find the highest anchor on the screen
  subnavLinks.forEach((link) => {
    try {
      const section = document.querySelector<HTMLDivElement>(link.hash);
      if (!section) {
        return;
      }
      const isBelow = section.offsetTop - offset <= fromTop;
      const isExact = section.offsetTop === fromTop;
      if (isBelow || isExact) currentPossibleAnchor = link;
    } catch (error) {
      console.error(`issue setting sidebar ToC for ${querySelector}`);
      return;
    }
  });

  // Then set the active tag
  subnavLinks.forEach((link) => {
    if (link === currentPossibleAnchor) {
      link.classList.add("active");
      openParents(link);
    } else {
      link.classList.remove("active");
      const parentUl = link.closest("ul");
      if (parentUl) {
        const hasActiveLinkInside = parentUl.querySelector(".active");
        if (!hasActiveLinkInside) {
          parentUl.classList.remove("open");
        }
      }
    }
  });
};

export const useToC = ({ querySelector }: { querySelector: string }): void => {
  return React.useEffect(() => {
    overrideSubNavLinksWithSmoothScroll({ querySelector });
    const updateSidebar = updateSidebarOnScroll.bind(window, { querySelector });

    // Handles setting the scroll
    window.addEventListener("scroll", updateSidebar, {
      passive: true,
      capture: true,
    });
    // Sets current selection
    updateSidebar();

    return () => {
      window.removeEventListener("scroll", updateSidebar);
    };
  }, []);

  return;
};

interface ToCNode {
  title: string;
  url: string;
}

interface ToCTree extends ToCNode {
  items?: ToCNode[];
}

const ToCInfinite = ({
  tableOfContents,
  initialDepth = 0,
  depth = 1,
  indent = "5px",
}: {
  tableOfContents: ToCTree;
  initialDepth?: number;
  depth?: number;
  indent: string;
}) => {
  const Wrapper: React.FC = ({ children }) =>
    depth > initialDepth && (tableOfContents.title || depth == 1) ? (
      <ul
        role="list"
        style={{
          marginLeft:
            depth > initialDepth
              ? `calc((${indent} * ${depth - initialDepth}) - 2px)`
              : indent,
        }}
      >
        {children}
      </ul>
    ) : (
      <>{children}</>
    );
  return (
    <Wrapper>
      {tableOfContents?.items
        ?.filter(notEmpty)
        ?.map(({ title, url, items }: ToCTree) => {
          const id = new Slugger().slug(title, false);
          if (depth === initialDepth || !title) {
            return (
              <React.Fragment key={id}>
                {items && items.length > 0 && (
                  <ToCInfinite
                    tableOfContents={{ title, url, items }}
                    depth={depth + 1}
                    initialDepth={initialDepth}
                    indent={indent}
                  />
                )}
              </React.Fragment>
            );
          } else {
            return (
              <li
                key={id}
                role="listitem"
                style={{
                  marginLeft: `calc((${indent} * ${
                    depth - initialDepth
                  }) - 2px)`,
                }}
              >
                <a href={"#" + id}>{title}</a>
                {items && items.length > 0 && (
                  <ToCInfinite
                    tableOfContents={{ title, url, items }}
                    depth={depth + 1}
                    initialDepth={initialDepth}
                    indent={indent}
                  />
                )}
              </li>
            );
          }
        })}
    </Wrapper>
  );
};

type ToCProps = {
  mdx: { tableOfContents: ToCTree };
  initialDepth: number;
  depth: number;
  indent: string;
} & React.ComponentProps<typeof TableOfContentsSidebar>;

export const ToCSidebar: React.FC<ToCProps> = ({
  mdx,
  children,
  initialDepth = 0,
  indent = "5px",
  ...props
}) => {
  return (
    <TableOfContentsSidebar id="sidebar" {...props}>
      <nav>
        {mdx?.tableOfContents?.items &&
          mdx.tableOfContents.items?.length > 0 && (
            <div>
              <h5 className="heading">On this page</h5>
              <ToCInfinite
                tableOfContents={mdx.tableOfContents}
                initialDepth={initialDepth}
                indent={indent}
              />
            </div>
          )}
        <div>{children}</div>
      </nav>
    </TableOfContentsSidebar>
  );
};
