import {
  Fragment,
  Key,
  useCallback,
  useContext,
  useEffect,
  useState,
} from "react";
import styles from "./ScrollContainer.module.scss";
import classNames from "classnames";
import { getWeek } from "date-fns";
import { CalendarContext } from "src/calendar/Calendar";
import { monthNameOf, weekString } from "./date";

interface ScrollContainerProps<T> {
  direction: "horizontal" | "vertical";
  className?: string;

  /** A list of things managed */
  list: T[];

  renderItem: (_: T) => JSX.Element;
  keyOf: (_: T) => Key;
  generatePreviousObject: (_: HTMLElement) => void;
  generateNextObject: (_: HTMLElement) => void;
}

interface OldScrollState {
  el: HTMLElement;
  offsetLeft: number;
  offsetTop: number;
}

/**
 * Generic infinite scrolling container
 */
function ScrollContainer<T>({
  direction,
  className,
  list,
  renderItem,
  generateNextObject,
  generatePreviousObject,
  keyOf,
}: ScrollContainerProps<T>) {
  const [resetScrollToEl, setResetScrollToEl] = useState<OldScrollState | null>(
    null
  );

  // Viewport state
  const { viewportWidth, viewportHeight, setViewportSize } = useContext(
    CalendarContext
  );
  const viewportMetric =
    direction === "horizontal" ? viewportWidth : viewportHeight;

  // Scroll element state
  const [scrollyEl, setScrollyEl] = useState<HTMLDivElement | null>(null);
  const scrollyRef = useCallback((node: HTMLDivElement) => {
    if (node) {
      setScrollyEl(node);
      setViewportSize([node.clientWidth, node.clientHeight]);
    }
  }, []);

  // Scroll to middle on first load
  const [firstLoad, setFirstLoad] = useState(true);

  // Add resize observer on the viewport
  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      if (entries && scrollyEl) {
        setViewportSize([scrollyEl.clientWidth, scrollyEl.clientHeight]);
      }
    });

    if (scrollyEl) observer.observe(scrollyEl);

    return () => {
      if (scrollyEl) observer.unobserve(scrollyEl);
    };
  }, [scrollyEl]);

  // Add intersection observer to tell when we scrolled too far
  useEffect(() => {
    if (viewportWidth === 0 || viewportHeight === 0) return;
    if (!scrollyEl) return;

    if (firstLoad) {
      const scrollLen = scrollyEl.children.length;
      const middleIdx = (scrollLen + 1) / 2 - 1;
      console.log("middle idx", middleIdx);

      // Identify the middle month
      const el = scrollyEl.children[middleIdx];
      if (el) {
        // const date = new Date(el.dataset.isodate!);
        // console.log("first run, scrolling into view", date.getMonth());

        el.scrollIntoView();
        setFirstLoad(false);
      }
      return;
    }

    const children = [...scrollyEl.children] as HTMLElement[];

    const firstChild = children[0];
    const lastChild = children[children.length - 1];

    const observer = new IntersectionObserver((entries) => {
      if (viewportMetric === 0) return;

      for (const entry of entries) {
        if (!entry.isIntersecting) continue;

        // const intersectionArea =
        //   entry.intersectionRect.width * entry.intersectionRect.height;
        // if (intersectionArea === 0) continue;

        // const date = new Date(entry.target.dataset.isodate!);
        // console.log("intersected with", weekString(date), entry);

        if (entry.target === firstChild) {
          const newResetScrollToEl = {
            el: firstChild,
            offsetLeft: scrollyEl.scrollLeft - firstChild.offsetLeft,
            offsetTop: scrollyEl.scrollTop - firstChild.offsetTop,
          };
          setResetScrollToEl(newResetScrollToEl);
          generatePreviousObject(firstChild);
        }

        if (entry.target === lastChild) {
          console.log("child offset left", lastChild.offsetLeft);
          const newResetScrollToEl = {
            el: lastChild,
            offsetLeft: scrollyEl.scrollLeft - lastChild.offsetLeft,
            offsetTop: scrollyEl.scrollTop - lastChild.offsetTop,
          };
          setResetScrollToEl(newResetScrollToEl);

          setTimeout(() => {
            generateNextObject(lastChild);
          }, 1000);
        }
      }
    });

    // const firstDate = new Date(firstChild.dataset.isodate!);
    // const lastDate = new Date(lastChild.dataset.isodate!);
    // console.log("observing", weekString(firstDate), weekString(lastDate));

    observer.observe(firstChild);
    observer.observe(lastChild);

    return () => {
      observer.unobserve(firstChild);
      observer.unobserve(lastChild);
    };
  }, [firstLoad, list, scrollyEl, viewportHeight]);

  useEffect(() => {
    if (!resetScrollToEl) return;
    if (!scrollyEl) return;

    const { el, offsetLeft, offsetTop } = resetScrollToEl;
    if (direction === "horizontal")
      scrollyEl.scrollLeft = el.offsetLeft + offsetLeft;
    else if (direction === "vertical")
      scrollyEl.scrollTop = el.offsetTop + offsetTop;

    setResetScrollToEl(null);
  }, [resetScrollToEl, scrollyEl]);

  return (
    <>
      {JSON.stringify([firstLoad, viewportWidth, viewportHeight])}
      <div
        className={classNames(styles.scrollyEl, styles[direction], className)}
        ref={scrollyRef}
      >
        {list.map((x) => (
          <Fragment key={keyOf(x)}>{renderItem(x)}</Fragment>
        ))}
      </div>
    </>
  );
}

export default ScrollContainer;
