import React, {
  useMemo,
  forwardRef,
  useRef,
  useCallback,
  useEffect,
} from "react";
import {
  FixedSizeList,
  VariableSizeList,
  ListChildComponentProps,
  ListItemKeySelector,
  ListOnScrollProps,
} from "react-window";
import { useConfig } from "../util";
import { VirtualizedOptions } from "./addons/scrollable";
import { mergeRefs } from "../_util/merge-refs";
import { getRowExactWidth } from "./util/get-row-exact-width";
import { TableColumn } from "./TableProps";

export interface TableVirtualizedBodyProps<Record = any>
  extends VirtualizedOptions<Record> {
  itemKey?: ListItemKeySelector;
  tableBoxStyle: React.CSSProperties;
  onScrollCapture?: React.DOMAttributes<HTMLDivElement>["onScrollCapture"];
  records?: Record[];
  columns?: TableColumn[];
  renderRecord?: (record: any, recordIndex: number) => JSX.Element[];
  getRowKey?: (record: any, recordIndex: number) => string;
  topTip?: React.ReactNode;
  bottomTip?: React.ReactNode;
  /**
   * 是否使用了行展开填充 records
   * 此时实际行数大于 records 数量
   */
  expandable?: boolean;
  /**
   * Table 宽
   */
  width?: number;
}

const defaultItemHeight = 45;

export const TableVirtualizedBody = forwardRef(function TableVirtualizedBody(
  {
    tableBoxStyle,
    onScrollCapture,
    onScrollBottom,
    height,
    itemHeight = defaultItemHeight,
    records,
    columns,
    renderRecord,
    getRowKey,
    topTip,
    bottomTip,
    expandable,
    updateDeps = [],
  }: TableVirtualizedBodyProps,
  ref: React.Ref<HTMLDivElement>
) {
  const { classPrefix } = useConfig();
  const bodyRef = useRef<HTMLDivElement>(null);
  const vRef = useRef<VariableSizeList>(null);

  const OuterElement = useMemo(
    () =>
      forwardRef((props, ref: React.Ref<HTMLDivElement>) => {
        return (
          <div
            {...props}
            className={`${classPrefix}-table__body`}
            ref={ref}
            onScrollCapture={onScrollCapture}
          />
        );
      }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    []
  );

  const InnerElement = useMemo(
    () =>
      forwardRef(
        (
          { style = {}, children }: React.HTMLAttributes<HTMLTableElement>,
          ref: React.Ref<HTMLTableElement>
        ) => {
          // tableBoxStyle 可能包含横向滚动的 minWidth，如果有则覆盖计算的最小宽度
          const minWidth =
            tableBoxStyle?.minWidth || getRowExactWidth(columns) || undefined;
          return (
            <table
              ref={ref}
              className={`${classPrefix}-table__box`}
              style={{
                ...(tableBoxStyle || {}),
                ...style,
                maxHeight: undefined,
                minWidth,
              }}
            >
              <tbody>{children}</tbody>
            </table>
          );
        }
      ),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [tableBoxStyle]
  );

  const getItemKey = useCallback(
    (index: number, data) => {
      if (data[index].type === "top-tip") {
        return "__TABLE_TOP_TIP__";
      }
      if (data[index].type === "bottom-tip") {
        return "__TABLE_BOTTOM_TIP__";
      }
      if (expandable) {
        return data[index].row.key;
      }
      return getRowKey(data[index].row, index);
    },
    [expandable, getRowKey]
  );

  const items = useMemo(() => {
    // 此时要预先渲染行计算总数
    if (expandable) {
      const recordRows = [];
      records.map(renderRecord).forEach(rows => {
        rows.forEach(r => recordRows.push(r));
      });

      return [
        { type: "top-tip", row: topTip },
        ...recordRows.map(r => ({ type: "record", row: r })),
        { type: "bottom-tip", row: bottomTip },
      ].filter(i => !!i.row);
    }

    return [
      { type: "top-tip", row: topTip },
      ...records.map(r => ({ type: "record", row: r })),
      { type: "bottom-tip", row: bottomTip },
    ].filter(i => !!i.row);
  }, [bottomTip, expandable, records, renderRecord, topTip]);

  // 强刷
  useEffect(() => {
    if (updateDeps?.length && vRef.current?.resetAfterIndex) {
      items.forEach((_, i) => vRef.current.resetAfterIndex(i));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, updateDeps);

  // VariableSizeList 默认按 index 缓存 style，需手动触发重置
  useEffect(() => {
    if (vRef.current?.resetAfterIndex) {
      items.forEach((_, i) => vRef.current.resetAfterIndex(i, false));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [bottomTip, records, topTip]);

  const Row = useCallback(
    ({ data, index, style }: ListChildComponentProps) => {
      if (data[index].type === "top-tip" || data[index].type === "bottom-tip") {
        return data[index].row;
      }
      // 此时行已预先渲染计算
      if (expandable) {
        return React.cloneElement(data[index].row, {
          style: { ...style, display: "flex" },
        });
      }

      return renderRecord(data[index].row, index).map(row =>
        React.cloneElement(row, {
          style: { ...style, display: "flex" },
        })
      );
    },
    [expandable, renderRecord]
  );

  const getItemHeight = useCallback(
    (index: number) => {
      if (typeof itemHeight !== "function") {
        return itemHeight;
      }
      const offset = items[0].type === "top-tip" ? 1 : 0;
      if (
        items[index].type === "top-tip" ||
        items[index].type === "bottom-tip"
      ) {
        return defaultItemHeight;
      }
      return itemHeight(items[index].row, index - offset);
    },
    [itemHeight, items]
  );

  function handleScroll(props: ListOnScrollProps) {
    const itemsSize = items.reduce(
      (p, _, index) =>
        p +
        (typeof itemHeight === "function" ? getItemHeight(index) : itemHeight),
      0
    );
    if (onScrollBottom) {
      if (bodyRef.current && itemsSize > height) {
        const { scrollHeight, scrollTop, clientHeight } = bodyRef.current;
        if (scrollHeight <= Math.ceil(clientHeight + scrollTop + 1)) {
          onScrollBottom(props);
        }
      }
    }
  }

  if (typeof itemHeight === "function") {
    return (
      <VariableSizeList
        ref={vRef}
        outerRef={mergeRefs(ref, bodyRef)}
        outerElementType={OuterElement}
        innerElementType={InnerElement}
        height={height}
        width="100%"
        itemSize={index => getItemHeight(index)}
        itemCount={items.length}
        itemData={items}
        onScroll={handleScroll}
        itemKey={getItemKey}
      >
        {Row}
      </VariableSizeList>
    );
  }

  return (
    <FixedSizeList
      outerRef={mergeRefs(ref, bodyRef)}
      outerElementType={OuterElement}
      innerElementType={InnerElement}
      height={height}
      width="100%"
      itemSize={itemHeight}
      itemCount={items.length}
      itemData={items}
      itemKey={getItemKey}
      onScroll={handleScroll}
    >
      {Row}
    </FixedSizeList>
  );
});

TableVirtualizedBody.displayName = "TableVirtualizedBody";
