import React, { useMemo, useEffect, useState } from "react";
import styled, { css } from "styled-components";
import { hoveredCellContext } from "../context/hoveredCellContext";
import { usePropsRef } from "../context/PropsContext";
import { useScrollerRef } from "../context/ScrollerContext";
import { stickyColumnContext } from "../context/StickyColumnContext";
import {
  viewportStartContext,
  viewportSizeContext,
} from "../context/ViewportContext";
import { observeResize } from "../utils/observeResize";

const debounce = <TArgs extends any[]>(f: (...args: TArgs) => void) => {
  let lastArgs: TArgs;
  let calling = false;
  return (...args: TArgs) => {
    lastArgs = args;
    if (!calling) {
      calling = true;
      requestAnimationFrame(() => {
        calling = false;
        f(...lastArgs);
      });
    }
  };
};

/**
 * This component tracks the scrolling of the table to enable row
 * virtualization. When the table changes size or scrolls, we need to change
 * which rows are rendered
 * This also adds shadows on the left and right sides of the table when content
 * is hidden and can be scrolled to
 */
export const Scroller = ({ children }: { children: React.ReactNode }) => {
  const setViewPortStart = viewportStartContext.useSetter();
  const setViewportSize = viewportSizeContext.useSetter();
  const setHoveredCell = hoveredCellContext.useSetter();
  const stickyColumnWidths = stickyColumnContext.useState();
  const { rowHeight } = usePropsRef().current;
  const scrollerRef = useScrollerRef();

  const [isLeftShadowShowing, setIsLeftShadowShowing] = useState(false);
  const [isRightShadowShowing, setIsRightShadowShowing] = useState(false);

  useEffect(() => {
    if (scrollerRef.current) {
      setIsLeftShadowShowing(scrollerRef.current.scrollLeft > 0);
      setIsRightShadowShowing(
        scrollerRef.current.scrollWidth -
          scrollerRef.current.clientWidth -
          scrollerRef.current.scrollLeft >
          0
      );
    }
  }, []);

  useEffect(() => {
    if (!scrollerRef.current) {
      return;
    }
    return observeResize(
      scrollerRef.current,
      debounce(() => {
        if (!scrollerRef.current) {
          return;
        }
        setViewportSize(
          Math.ceil(scrollerRef.current.clientHeight / rowHeight)
        );
      })
    );
  }, [rowHeight]);

  const handleScroll = useMemo(
    () =>
      debounce(() => {
        if (scrollerRef.current) {
          setViewPortStart(
            Math.floor(scrollerRef.current.scrollTop / rowHeight)
          );
          setIsLeftShadowShowing(scrollerRef.current.scrollLeft > 0);
          setIsRightShadowShowing(
            scrollerRef.current.scrollWidth -
              scrollerRef.current.clientWidth -
              scrollerRef.current.scrollLeft >
              0
          );
        }
      }),
    []
  );
  return (
    <ScrollerContainer
      ref={scrollerRef}
      data-testid="bulk-edit-scroller"
      onMouseLeave={() => setHoveredCell(null)}
      onScroll={() => {
        setHoveredCell(null);
        handleScroll();
      }}
    >
      <ContentWrapper>
        {children}
        <ShadowAbsoluteWrapper>
          <ScrollShadowComponent
            left={stickyColumnWidths.reduce((acc, width) => acc + width, 0)}
            isShowing={isLeftShadowShowing}
          />
          <ScrollShadowComponent right={0} isShowing={isRightShadowShowing} />
        </ShadowAbsoluteWrapper>
      </ContentWrapper>
    </ScrollerContainer>
  );
};

const ScrollerContainer = styled.div`
  overflow: auto;
  border-top: 1px solid ${({ theme }) => theme.cells.borderColor};
  border-left: 1px solid ${({ theme }) => theme.cells.borderColor};
  background: ${({ theme }) => theme.cells.backgroundColor};
`;

const ContentWrapper = styled.div`
  min-width: min-content;
  min-height: min-content;
  position: relative;
`;

const ShadowAbsoluteWrapper = styled.div`
  position: absolute;
  pointer-events: none;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  z-index: 10;
  justify-content: space-between;
`;

const ScrollShadowComponent = styled.div<{
  left?: number;
  right?: number;
  isShowing: boolean;
}>`
  ${({ left, right, theme }) => {
    if (left !== undefined) {
      return css`
        left: ${left}px;
        background: linear-gradient(
          to left,
          transparent,
          ${theme.scrollerShadow.color}
        );
      `;
    }
    if (right !== undefined) {
      return css`
        right: ${right}px;
        background: linear-gradient(
          to right,
          transparent,
          ${theme.scrollerShadow.color}
        );
      `;
    }
    return null;
  }}
  opacity: ${({ isShowing }) => (isShowing ? 1 : 0)};
  position: sticky;
  bottom: 0;
  height: 100%;
  width: ${({ theme }) => theme.scrollerShadow.width}px;
  transition: opacity 0.2s ease;
`;
