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

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

type Link = ComponentType<LinkProps>;

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

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

interface Props {
  sections: Section[];
  currentId: string;
  pageTitle: string;
  link: Link;
}

export function OrganizedContent(props: Props) {
  const sections = createSections(props.sections);
  const currentIndex = sections.findIndex(({ id }) => id === props.currentId);
  const [open, setOpen] = useState(false);

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

  const section = sections[currentIndex];
  const isReadAll = section.id === READ_ALL_ID;

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

  const ref = useRef<HTMLDivElement>(null);
  const isVisible = useOnScreen(ref);

  return (
    <div className={styles.wrapper} ref={ref}>
      <Nav sections={sections} currentIndex={currentIndex} link={props.link} />
      <div className={styles.content}>
        {isReadAll ? (
          <section.Content />
        ) : (
          <>
            <div>
              <h1>{section.title}</h1>
              <section.Content />
            </div>
            <Footer
              sections={sections}
              currentIndex={currentIndex}
              link={props.link}
            />
          </>
        )}
      </div>
      <MobileWrapper
        open={open}
        setOpen={setOpen}
        sections={sections}
        currentIndex={currentIndex}
        link={props.link}
        pageTitle={props.pageTitle}
        componentIsVisible={isVisible}
      />
    </div>
  );
}

interface NavProps {
  sections: Section[];
  currentIndex: number;
  link: Link;
  pageTitle?: string;
}

function Nav({ sections, currentIndex, link: Link, pageTitle }: NavProps) {
  return (
    <div className={styles.nav}>
      {pageTitle && <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}
          >
            <span className={styles.marker} />
            <div>{section.title}</div>
          </Link>
        );
      })}
    </div>
  );
}

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

interface MobileProps {
  open: boolean;
  setOpen: React.Dispatch<React.SetStateAction<boolean>>;
  sections: Section[];
  currentIndex: number;
  link: Link;
  pageTitle: string;
  componentIsVisible: boolean;
}

function MobileWrapper(mobileProps: MobileProps) {
  const wrapperRef = useRef<HTMLDivElement>(null);
  useOutsideAlerter(wrapperRef, mobileProps.setOpen);

  return (
    <div ref={wrapperRef}>
      <Burger {...mobileProps} />
      <Menu {...mobileProps} />
    </div>
  );
}

interface BurgerProps {
  open: boolean;
  setOpen: React.Dispatch<React.SetStateAction<boolean>>;
  componentIsVisible: boolean;
}

const Burger = ({ open, setOpen, componentIsVisible }: BurgerProps) => {
  const [prevScrollPos, setPrevScrollPos] = useState(0);
  const [burgerVisible, setBurgerVisible] = useState(true);
  const debouncedPrevScrollPos = useDebounce<number>(prevScrollPos, 100);

  useEffect(() => {
    const handleScroll = () => {
      // find current scroll position
      const currentScrollPos = window.pageYOffset;
      // set state based on location info (explained in more detail below)
      setBurgerVisible(
        componentIsVisible &&
          ((debouncedPrevScrollPos > currentScrollPos &&
            debouncedPrevScrollPos - currentScrollPos > 70) ||
            currentScrollPos < 10)
      );

      // set state to new scroll position
      setPrevScrollPos(currentScrollPos);
    };
    window.addEventListener("scroll", handleScroll);

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

  return (
    <div
      className={
        styles.burger + " " + (burgerVisible ? "" : styles.hiddenBurger)
      }
      onClick={() => setOpen(!open)}
    >
      <div>
        <div />
        <div />
        <div />
      </div>
    </div>
  );
};

interface MenuProps {
  open: boolean;
  sections: Section[];
  currentIndex: number;
  link: Link;
  pageTitle: string;
}

const Menu = ({ open, sections, currentIndex, link, pageTitle }: MenuProps) => {
  const mobileNav = open
    ? styles.mobileNav
    : styles.mobileNav + " " + styles.mobileNavClosed;
  return (
    <div className={mobileNav}>
      <Nav
        sections={sections}
        currentIndex={currentIndex}
        link={link}
        pageTitle={pageTitle}
      />
    </div>
  );
};

function useOutsideAlerter(
  ref: React.RefObject<HTMLDivElement>,
  setOpen: React.Dispatch<React.SetStateAction<boolean>>
) {
  useEffect(() => {
    const handleClickOutside = (event: Event) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        setOpen(false);
      }
    };

    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref, setOpen]);
}

function useDebounce<T>(value: T, delay?: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay || 500);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
}

function createSections(sections: Section[]) {
  return [
    {
      id: READ_ALL_ID,
      title: READ_ALL_TITLE,
      Content() {
        return (
          <>
            {sections.map(({ id, title, Content: SectionContent }) => (
              <div key={id}>
                <h1>{title}</h1>
                <SectionContent />
              </div>
            ))}
          </>
        );
      },
    },
    ...sections,
  ];
}

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(ref: React.RefObject<HTMLDivElement>) {
  const [isIntersecting, setIntersecting] = useState(false);

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

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

  return isIntersecting;
}
