import NextLink from "next/link";
import React, {
  ReactNode,
  ComponentType,
  useState,
  useRef,
  useEffect,
  useCallback,
} from "react";

import styles from "./OrganizedContent.module.css";

type Link = ComponentType<LinkProps>;

interface Section {
  id: string;
  title: string;
}

const READ_ALL_TITLE = "Read All";
export const READ_ALL_ID = "read-all";

interface Props {
  sections: Section[];
  id: string;
  children: ReactNode;
  pageTitle: string;
  link: Link;
  numberedSections?: boolean;
}

export function OrganizedContent({
  sections,
  id,
  children,
  pageTitle,
  link: Link,
  numberedSections = false,
}: Props) {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  const currentIndex = sections.findIndex(
    ({ id: sectionId }) => sectionId === id
  );

  if (currentIndex < 0) {
    throw new Error(`Section with ID ${id} was not found`);
  }

  const section = sections[currentIndex];
  const isReadAll = section.id === READ_ALL_ID;
  const ref = useRef<HTMLDivElement>(null);
  const isVisible = useOnScreen(ref.current);
  const burgerVisible = useBurger(isVisible);

  useEffect(() => {
    mobileNavOpen
      ? (document.body.style.overflow = "hidden")
      : (document.body.style.overflow = "visible");
  }, [mobileNavOpen]);

  return (
    <div className={styles.wrapper} ref={ref}>
      <div
        className={
          mobileNavOpen
            ? `${styles.navMobileBackground} ${styles.show}`
            : styles.navMobileBackground
        }
        onClick={() => setMobileNavOpen(false)}
      />
      <Nav
        sections={sections}
        currentIndex={currentIndex}
        link={Link}
        pageTitle={pageTitle}
        numberedSections={numberedSections}
        mobileNavOpen={mobileNavOpen}
        setMobileNavOpen={setMobileNavOpen}
      />
      <div className={styles.content}>
        {isReadAll ? (
          children
        ) : (
          <>
            <section>
              <h1>
                {numberedSections
                  ? `${currentIndex}. ${section.title}`
                  : section.title}
              </h1>
              {children}
            </section>
            <Footer
              sections={sections}
              currentIndex={currentIndex}
              link={Link}
            />
          </>
        )}
      </div>
      <button
        className={`${styles.burger} ${
          burgerVisible ? styles.burgerVisible : ""
        }`}
        onClick={() => setMobileNavOpen(!mobileNavOpen)}
      >
        <Burger />
      </button>
    </div>
  );
}

interface NavProps {
  sections: Section[];
  currentIndex: number;
  link: Link;
  pageTitle: string;
  numberedSections: boolean;
  mobileNavOpen: boolean;
  setMobileNavOpen: (mobileNavOpen: boolean) => void;
}

function Nav({
  sections,
  currentIndex,
  link: Link,
  pageTitle,
  numberedSections,
  mobileNavOpen,
  setMobileNavOpen,
}: NavProps) {
  const navStyles = mobileNavOpen
    ? [styles.nav, styles.mobileNavOpen]
    : [styles.nav];

  return (
    <nav
      className={navStyles.join(" ")}
      onClick={(event) => event.stopPropagation()}
    >
      <h1 className={styles.mobileNavTitle}>{pageTitle}</h1>
      {sections.map((section, index) => {
        const classNames = [styles.navItem];

        if (index === currentIndex) {
          classNames.push(styles.selected);
        }

        if (section.id === READ_ALL_ID) {
          classNames.push(styles.readAll);
        }

        return (
          <div
            onClick={() => {
              setMobileNavOpen(false);
            }}
            key={section.id}
          >
            <Link className={classNames.join(" ")} id={section.id}>
              <span className={styles.marker} />
              <div>
                {numberedSections && section.id !== READ_ALL_ID
                  ? `${index}. ${section.title}`
                  : section.title}
              </div>
            </Link>
          </div>
        );
      })}
    </nav>
  );
}

interface FooterProps {
  sections: Section[];
  currentIndex: number;
  link: Link;
}

function Footer({ sections, currentIndex, link: Link }: FooterProps) {
  const prevSection =
    currentIndex > 0 && sections[currentIndex - 1].id !== READ_ALL_ID
      ? sections[currentIndex - 1]
      : undefined;
  const nextSection =
    currentIndex < sections.length - 1 &&
    sections[currentIndex + 1].id !== READ_ALL_ID
      ? sections[currentIndex + 1]
      : undefined;

  return (
    <div className={styles.footer}>
      {prevSection && (
        <Link className={styles.previous} id={prevSection.id}>
          <Arrow direction="left" />
          <div>
            <div>Previous</div>
            <div className={styles.arrowHeading}>{prevSection.title}</div>
          </div>
        </Link>
      )}
      {nextSection && (
        <Link className={styles.next} id={nextSection.id}>
          <div>
            <div>Next</div>
            <div className={styles.arrowHeading}>{nextSection.title}</div>
          </div>
          <Arrow direction="right" />
        </Link>
      )}
    </div>
  );
}

function useDebounce(func: () => void, delay = 300) {
  const timerRef = useRef<number | undefined>(undefined);
  return useCallback(() => {
    if (timerRef.current != null) {
      return;
    }

    timerRef.current = window.setTimeout(() => {
      func();
      timerRef.current = undefined;
    }, delay);
  }, [func, delay]);
}

export interface SectionWithContent {
  section: Section;
  Content: ComponentType;
}

export function createReadAllSection(
  sections: Section[],
  content: false,
  numberedSections?: undefined
): Section;
export function createReadAllSection(
  sections: SectionWithContent[],
  content: true,
  numberedSections: boolean
): SectionWithContent;
export function createReadAllSection(
  sections: SectionWithContent[] | Section[],
  content = true,
  numberedSections?: boolean
): SectionWithContent | Section {
  const readAllSection = {
    id: READ_ALL_ID,
    title: READ_ALL_TITLE,
  };

  return content
    ? {
        section: readAllSection,
        Content: function ReadAllContent() {
          return (
            <>
              {(sections as SectionWithContent[]).map(
                ({ section: { id, title }, Content }, index) => (
                  <section key={id}>
                    <h1>
                      {numberedSections ? `${index + 1}. ${title}` : title}
                    </h1>
                    <Content />
                  </section>
                )
              )}
            </>
          );
        },
      }
    : readAllSection;
}

export interface LinkProps {
  className?: string;
  id: string;
  children: ReactNode;
}

export function createLink(page: string) {
  let base = page.startsWith("/") ? page : `/${page}`;
  base = base.endsWith("/") ? base : `${base}/`;

  return function Link({ className, id, children }: LinkProps) {
    const href = id === READ_ALL_ID ? base : base + id;

    return (
      <NextLink href={href}>
        <a className={`${styles.link} ${className ?? ""}`}>{children}</a>
      </NextLink>
    );
  };
}

function Arrow({ direction }: { direction: "left" | "right" }) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="14"
      height="9"
      viewBox="0 0 14 9"
      className={`${styles.arrow} ${
        direction === "left" ? styles.prevArrow : styles.nextArrow
      }`}
    >
      <path d="M6.24407 8.12713C6.64284 8.58759 7.35716 8.58759 7.75593 8.12713L13.3613 1.65465C13.9221 1.00701 13.4621 0 12.6053 0H1.39467C0.537918 0 0.0778675 1.00701 0.638743 1.65465L6.24407 8.12713Z" />
    </svg>
  );
}

function useOnScreen(element: HTMLDivElement | null) {
  const [isIntersecting, setIntersecting] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) =>
      setIntersecting(entry.isIntersecting)
    );

    if (element) {
      observer.observe(element);
    }
    // Remove the observer as soon as the component is unmounted
    return () => {
      observer.disconnect();
    };
  }, [element]);

  return isIntersecting;
}

function useBurger(componentIsVisible: boolean): boolean {
  const [prevScrollPos, setPrevScrollPos] = useState(0);
  const [burgerVisible, setBurgerVisible] = useState(true);

  const handleScroll = useDebounce(() => {
    // find current scroll position
    const currentScrollPos = window.pageYOffset;
    setBurgerVisible(
      componentIsVisible &&
        ((prevScrollPos > currentScrollPos &&
          prevScrollPos - currentScrollPos > 70) ||
          currentScrollPos < 10)
    );

    // set state to new scroll position
    setPrevScrollPos(currentScrollPos);
  });

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);

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

  return burgerVisible;
}

// Inlining this svg because we want to fill in colors using css variables
function Burger() {
  return (
    <svg
      width="30"
      height="23"
      viewBox="0 0 30 23"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <line
        x1="28"
        y1="2"
        x2="2"
        y2="2"
        strokeWidth="4"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <line
        x1="28"
        y1="11.375"
        x2="2"
        y2="11.375"
        strokeWidth="4"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <line
        x1="28"
        y1="20.75"
        x2="2"
        y2="20.75"
        strokeWidth="4"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
