import clsx from "clsx";
import {
  ElementType,
  UIEventHandler,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import styles from "./scroll.module.scss";

interface VirtualScrollProps {
  component?: ElementType;
  className?: string | string[];
  itemHeight: number;
  total: number;
  tolerance?: number;
  startIndex?: number;
  getItems: (offset: number, limit: number) => JSX.Element | JSX.Element[];
}

export default function VirtualScroll(props: VirtualScrollProps) {
  const {
    component: Component = "ul",
    className,
    itemHeight,
    total,
    startIndex = 0,
    getItems,
  } = props;

  const [wrapHeight, setWrapHeight] = useState(0);
  const [scrollY, setScrollY] = useState(startIndex * itemHeight);
  const offset = useMemo(
    () => Math.max(0, Math.floor(scrollY / itemHeight)),
    [itemHeight, scrollY]
  );
  const limit = useMemo(() => {
    return Math.floor(wrapHeight / itemHeight) + 2;
  }, [itemHeight, wrapHeight]);
  const [wrapElement, setWrapElement] = useState<HTMLDivElement | null>(null);

  const handleScroll: UIEventHandler<HTMLDivElement> = (e) => {
    setScrollY((e.target as HTMLDivElement).scrollTop);
  };

  const resize = useCallback(() => {
    if (wrapElement) setWrapHeight(wrapElement.clientHeight);
  }, [wrapElement]);

  const setWrapRef = useCallback((ele: HTMLDivElement) => {
    if (ele) {
      setWrapElement(ele);
      if (ele) setWrapHeight(ele.clientHeight);
    } else {
      setWrapElement(null);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (window) {
      window.addEventListener("resize", resize);
    }

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

  useEffect(() => {
    if (wrapElement) {
      const y = startIndex * itemHeight;
      setScrollY(y);
      wrapElement.scrollTo({ left: 0, top: y, behavior: "auto" });
    }
  }, [itemHeight, startIndex, wrapElement]);

  return (
    <div
      ref={setWrapRef}
      className={clsx(styles["wrap"], className)}
      onScroll={handleScroll}
    >
      <div
        className={styles["space"]}
        style={{ height: `${total * itemHeight}px` }}
      />
      <Component
        className={styles["inner"]}
        style={{
          transform: `translateY(${(scrollY % itemHeight) * -1}px)`,
        }}
      >
        {getItems(offset, limit)}
      </Component>
    </div>
  );
}
