import React, {
  createContext,
  useEffect,
  useRef,
  useState,
  forwardRef,
  useContext,
  useMemo,
} from "react";
import classNames from "classnames";
import { callBoth } from "../../../_util/call-both";
import { mergeStyle } from "../../../_util/merge-style";
import { mergeRefs } from "../../../_util/merge-refs";
import { getScrollBarSize } from "../../../_util/get-scrollbar-size";
import { useConfig } from "../../../_util/config-context";
import { throttle } from "../../../affix/util";

/**
 * 滚动 Context
 */
interface ScrollContextValue {
  /**
   * 供表格内容区设置其 DOM Ref，拿到的 DOM 实例用于检测是否在滚动状态
   */
  setBodyRef: (element: HTMLDivElement) => void;

  /**
   * 确定的滚动状态放到上下文中
   */
  bodyHasScroll: boolean;

  /**
   * 滚动事件回调
   */
  onScrollCapture: (event: React.UIEvent<HTMLElement>) => void;
}

/**
 * 滚动 Context 实例
 */
const ScrollContext = createContext<ScrollContextValue>(null);
ScrollContext.displayName = "Scrollable";

/**
 * 注入到外层容器，提供滚动上下文
 */
export function ScrollableTable({
  table,
  scrollHeightFactor,
  onScrollBottom = () => null,
  ...props
}: ScrollableTableProps) {
  const { classPrefix } = useConfig();
  // 当前滚动区域是否在滚动状态
  const [bodyHasScroll, setBodyHasScroll] = useState<boolean>(false);

  // 滚动区域的 DOM 引用
  const bodyRef = useRef<HTMLDivElement>(null);

  // 决定滚动高度的因子改变后，重新检测滚动状态，设置到 bodyHasScroll 中
  useEffect(() => {
    const bodyBox = bodyRef.current;
    if (bodyBox) {
      // 滚动状态下，scrollHeight > clientHeight
      const nextBodyHasScroll = bodyBox.scrollHeight > bodyBox.clientHeight;
      if (bodyHasScroll !== nextBodyHasScroll) {
        setBodyHasScroll(nextBodyHasScroll);
      }
    }
  }, [bodyHasScroll, scrollHeightFactor]);

  const scrollBottom = useMemo(
    () =>
      throttle(event => {
        onScrollBottom(event);
      }),
    [onScrollBottom]
  );

  function handleBodyScroll(event: React.UIEvent<HTMLElement>) {
    const bodyBox = event.target as HTMLElement;
    const { scrollHeight, scrollTop, clientHeight } = bodyBox;
    if (
      bodyHasScroll &&
      scrollHeight <= Math.ceil(clientHeight + scrollTop + 1)
    ) {
      scrollBottom(event);
    }
  }

  // 滚动上下文
  const scrollContext: ScrollContextValue = {
    // 提供给 body 使用，提供 body DOM 实例
    setBodyRef: body => {
      bodyRef.current = body;
    },
    // 提供给 head 使用，通过滚动状态决定是否增加右侧间距
    bodyHasScroll,
    onScrollCapture: handleBodyScroll,
  };

  return (
    <ScrollContext.Provider value={scrollContext}>
      {React.cloneElement(table, {
        ...props,
        // 设计通过提供 tea-table--scrollable 类来开启滚动区域的 overflow: auto
        className: classNames(
          table.props.className,
          `${classPrefix}-table--scrollable`
        ),
      })}
    </ScrollContext.Provider>
  );
}
interface ScrollableTableProps {
  table: JSX.Element;
  scrollHeightFactor: any[];
  onScrollBottom?: (event: React.UIEvent) => void;
}

/**
 * 包装表格内容区（滚动部分），回调内容区 DOM 的实例
 */
export const ScrollableTableBody = forwardRef(
  (
    {
      body,
      style,
      bodyDeps,
      scrollToTopOnChange,
      ...props
    }: ScrollableTableBodyProps,
    ref: React.Ref<HTMLDivElement>
  ) => {
    const bodyRef = useRef<HTMLDivElement>(null);
    useEffect(() => {
      if (scrollToTopOnChange && bodyRef.current) {
        bodyRef.current.scrollTop = 0;
        bodyRef.current.scrollLeft = 0;
      }
    }, [scrollToTopOnChange, ...bodyDeps]); // eslint-disable-line react-hooks/exhaustive-deps

    const { setBodyRef, onScrollCapture } = useContext(ScrollContext);
    return React.cloneElement(body, {
      ...props,
      // 使用 mergeRefs 可以使得其他插件的 ref 也能被调用到
      ref: mergeRefs(setBodyRef, body.ref, ref, bodyRef),
      style: mergeStyle(body.props.style, style),
      onScrollCapture: callBoth(body.props.onScrollCapture, onScrollCapture),
    });
  }
);

interface ScrollableTableBodyProps {
  body: React.FunctionComponentElement<{
    ref: React.Ref<HTMLDivElement>;
    style: React.CSSProperties;
    onScrollCapture: (event: React.UIEvent<HTMLDivElement>) => void;
  }>;
  style: React.CSSProperties;
  bodyDeps: any[];
  scrollToTopOnChange: boolean;
}

/**
 * 包装表格头部，通过滚动状态调整右侧间距
 */
export const ScrollableTableHead = forwardRef(
  (
    { head, ...props }: ScrollableTableHeadProps,
    ref: React.Ref<HTMLDivElement>
  ) => {
    const { bodyHasScroll } = useContext(ScrollContext);
    return React.cloneElement(head, {
      ...props,
      ref: mergeRefs(head.ref, ref),
      style: mergeStyle(head.props.style, {
        marginRight: bodyHasScroll ? getScrollBarSize() : null,
      }),
    });
  }
);
interface ScrollableTableHeadProps {
  head: React.FunctionComponentElement<{
    ref: React.Ref<HTMLDivElement>;
    style: React.CSSProperties;
  }>;
}

ScrollableTable.displayName = "ScrollableTable";
