import React, {
  ReactNode,
  ComponentType,
  useState,
  useRef,
  useEffect,
  useCallback,
} from "react";
import styles from "./OrganizedContent.module.css";
import NextLink from "next/link";

export interface LinkProps { //todo check if we need this
  className?: string;
  id: string;
  children: ReactNode;
  setMobileNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
}

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;
}

export function OrganizedContent({
  sections,
  id,
  children,
  pageTitle,
  link: Link,
}: 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);
  const navWrapperStyles = mobileNavOpen
    ? [styles.navWrapper, styles.mobileNavOpen]
    : [styles.navWrapper];

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

  return (
    <div className={styles.wrapper} ref={ref}>
      <div
        className={navWrapperStyles.join(" ")}
        onClick={() => setMobileNavOpen(false)}
      >
        <Nav
          sections={sections}
          currentIndex={currentIndex}
          link={Link}
          pageTitle={pageTitle}
          setMobileNavOpen={setMobileNavOpen}
        />
      </div>
      <div className={styles.content}>
        {isReadAll ? (
          children
        ) : (
          <>
            <section>
              <h1>{section.title}</h1>
              {children}
            </section>
            <Footer
              sections={sections}
              currentIndex={currentIndex}
              link={Link}
            />
          </>
        )}
      </div>
      <button
        className={`${styles.burger} ${
          burgerVisible ? styles.burgerVisible : ""
        }`}
        onClick={() => setMobileNavOpen(!mobileNavOpen)}
      >
        {/* this is copied from hamburger.svg with changed colors */}
        <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>
      </button>
    </div>
  );
}

interface NavProps {
  sections: Section[];
  currentIndex: number;
  link: Link;
  pageTitle: string;
  setMobileNavOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

function Nav({
  sections,
  currentIndex,
  link: Link,
  pageTitle,
  setMobileNavOpen,
}: NavProps) {
  return (
    <nav className={styles.nav} 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 (
          <Link
            className={classNames.join(" ")}
            id={section.id}
            key={section.id}
            setMobileNavOpen={setMobileNavOpen}
          >
            <span className={styles.marker} />
            <div>{section.title}</div>
          </Link>
        );
      })}
    </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
): Section;
export function createReadAllSection(
  sections: SectionWithContent[],
  content: true
): SectionWithContent;
export function createReadAllSection(
  sections: SectionWithContent[] | Section[],
  content = true
): 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 }) => (
                  <section key={id}>
                    <h1>{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;
}
