'use client';
import type React from 'react';
import { useEffect, useRef } from 'react';
import { useDebounceFn, useUpdate } from 'ahooks';

type VirtualGridProps<T extends any = any> = {
  rowHeight?: number;
  itemRender?: (item: T) => React.ReactNode;
  dataSource?: T[];
  noMoreData?: boolean;
  noMoreDataRender?: (() => React.ReactNode) | null;
  isLoadingMore?: boolean;
  isFirstLoading?: boolean;
  loadMore?: () => void;
  gap?: number;
  padding?: {
    right?: number;
    left?: number;
  };
  columnCalc?: (width: number) => number;
  keySetter?: (item: T) => string | number;
  preRenderHeight?: number;
  header?: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

const useResizeObserver = (
  ref: React.RefObject<HTMLDivElement | null>,
  callback: () => void
) => {
  useEffect(() => {
    const resizeObserver = new ResizeObserver(() => {
      callback();
    });
    if (ref.current) {
      resizeObserver.observe(ref.current);
    }
    return () => {
      if (ref.current) {
        resizeObserver.unobserve(ref.current);
      }
      resizeObserver.disconnect();
    };
  }, []);
};

function VirtualGrid<T extends any = any>(
  props: VirtualGridProps<T>
): React.ReactNode {
  const {
    rowHeight = 0,
    itemRender,
    columnCalc,
    gap = 10,
    dataSource = [],
    noMoreData = false,
    loadMore,
    header,
    keySetter,
    preRenderHeight = 100,
    padding,
    isLoadingMore,
    isFirstLoading,
    noMoreDataRender = () => 'No more data',
    ...others
  } = props;
  const { left = 0, right = 0 } = padding || {};
  const containerRef = useRef<HTMLDivElement | null>(null);
  const headerRef = useRef<HTMLDivElement | null>(null);
  const listHeight = useRef<number>(0);
  const previousDataSourceLength = useRef<number>(0);

  // 画布数据
  const scrollState = useRef<{
    // 画布测量宽高
    viewWidth: number;
    viewHeight: number;
    // 视图的开始位置距离顶部的位置
    start: number;
    // 列数
    column: number;
    // 每个cell的实际宽度
    width: number;
    // header 的测量高度
    headerHeight: number;
    // 实际可用宽度
    usableWidth: number;
  }>({
    viewWidth: 0,
    viewHeight: 0,
    start: 0,
    column: 0,
    width: 0,
    headerHeight: 0,
    usableWidth: 0,
  });
  // 计算过布局几何属性的数据
  const queueState = useRef<
    {
      // 源数据
      item: T;
      // 到顶部的距离
      y: number;
      // 最终样式
      style: React.CSSProperties;
    }[]
  >([]);
  // 需要实际渲染的数据
  const renderList = useRef<
    {
      item: T;
      y: number;
      style: React.CSSProperties;
    }[]
  >([]);
  // 手动更新，避免多次刷新组件
  const update = useUpdate();

  // 初始化画布参数，在container size改变时需要调用
  const initScrollState = () => {
    if (!containerRef.current) return;

    scrollState.current.viewWidth = containerRef.current.clientWidth;
    scrollState.current.viewHeight = containerRef.current.clientHeight;
    // 实际可用宽度为视口宽度减去左右padding
    scrollState.current.usableWidth =
      containerRef.current.clientWidth - left - right;
    // 根据实际可用宽度计算列数
    const column = columnCalc?.(scrollState.current.usableWidth) || 1;
    scrollState.current.column = column;
    scrollState.current.start = containerRef.current.scrollTop;
    // 每个cell的实际宽度(为可用宽度除以列数)
    scrollState.current.width =
      (scrollState.current.usableWidth - (column - 1) * gap!) / column;
    // header 的测量高度
    scrollState.current.headerHeight = headerRef.current?.clientHeight || 0;
  };

  const genereateRenderList = () => {
    const finalStart = scrollState.current.start - preRenderHeight!;
    const finalEnd =
      scrollState.current.start +
      scrollState.current.viewHeight +
      preRenderHeight!;
    const newRenderList = [];

    // 注意这里只针对dataSource长度进行遍历，而不是queueState长度
    for (let i = 0; i < dataSource.length; i++) {
      if (
        queueState.current[i].y + rowHeight! >= finalStart &&
        queueState.current[i].y <= finalEnd
      ) {
        // 注意这里需要浅拷贝, 不然resize的时候会直接更新到renderList, 导致下面计算条件update不生效
        newRenderList.push({ ...queueState.current[i] });
      }
    }
    if (
      newRenderList.length !== renderList.current.length ||
      newRenderList[0]?.y !== renderList.current[0]?.y
    ) {
      update();
    }
    renderList.current = newRenderList;
  };

  // 重新计算高度和滚动位置
  const resetHeightAndScroll = () => {
    // 最小有一行
    const maxRow = Math.max(
      Math.ceil(dataSource.length / scrollState.current.column),
      1
    );
    // 高度 = 最大行数 * 行高 + (最大行数 - 1) * 间距 + header 高度
    listHeight.current =
      maxRow * rowHeight! +
      (maxRow - 1) * gap! +
      scrollState.current.headerHeight;
    // 如果数据长度小于等于之前的，则滚动到顶部
    if (dataSource.length <= previousDataSourceLength.current) {
      containerRef.current?.scrollTo({
        top: 0,
        behavior: 'instant',
      });
    }
    previousDataSourceLength.current = dataSource.length;
    genereateRenderList();
  };

  const calculateCellRect = (i: number) => {
    // 第几行， 从0开始
    const row = Math.floor(i / scrollState.current.column);
    // 第几列， 从0开始
    const col = i % scrollState.current.column;
    // 到顶部的距离
    const y = scrollState.current.headerHeight + row * rowHeight! + gap! * row;
    // 到左边的距离
    const x = left + col * scrollState.current.width + col * gap!;
    return {
      y,
      x,
    };
  };

  // 页面尺寸变化，重新计算布局
  const resizeQueueData = () => {
    for (let i = 0; i < dataSource.length; i++) {
      const { y, x } = calculateCellRect(i);
      queueState.current[i].style = {
        height: rowHeight!,
        width: scrollState.current.width,
        transform: `translate(${x}px, ${y}px)`,
      };
      queueState.current[i].y = y;
    }
    resetHeightAndScroll();
  };

  const updateQueueData = () => {
    for (let i = 0; i < dataSource.length; i++) {
      const item = dataSource[i];
      if (queueState.current[i]) {
        queueState.current[i].item = item;
        continue;
      }

      const { y, x } = calculateCellRect(i);
      queueState.current.push({
        item: item,
        y: y,
        style: {
          height: rowHeight!,
          width: scrollState.current.width,
          transform: `translate(${x}px, ${y}px)`,
        },
      });
    }
    resetHeightAndScroll();
  };

  const handleScroll = () => {
    if (!containerRef.current) return;
    scrollState.current.start = containerRef.current.scrollTop;
    genereateRenderList();
    if (noMoreData || isLoadingMore || isFirstLoading) return;
    const { scrollTop, clientHeight } = containerRef.current;
    if (scrollTop + clientHeight >= listHeight.current) {
      loadMore?.();
    }
  };

  const { run: handleResize } = useDebounceFn(
    () => {
      initScrollState();
      resizeQueueData();
    },
    {
      wait: 200,
      maxWait: 300,
    }
  );

  // 监听容器和header的尺寸变化
  useResizeObserver(containerRef, handleResize);
  useResizeObserver(headerRef, handleResize);

  // 当 dataSource 变化时，重新计算布局
  useEffect(() => {
    // 初始化布局
    if (!scrollState.current.viewWidth) {
      initScrollState();
    }
    // 添加到队列
    updateQueueData();
  }, [dataSource]);

  // 插槽高度，用于 loading 和 no more data
  const loadingHeight = 20;

  return (
    <div
      ref={containerRef}
      {...others}
      className={`relative overflow-auto ${others.className}`}
      style={{
        scrollbarGutter: 'stable', // 为滚动条预留空间，防止布局抖动
        ...others.style,
      }}
      onScroll={handleScroll}
    >
      <div
        className="relative w-full"
        style={{ height: listHeight.current + loadingHeight }}
      >
        <div ref={headerRef} className="flex">
          {header}
        </div>
        {renderList.current.map(({ item, style }) => (
          <div
            className="absolute top-0 left-0 overflow-hidden"
            key={keySetter?.(item)}
            style={style}
          >
            {itemRender?.(item)}
          </div>
        ))}

        {/* 加载更多 */}
        {!!renderList.current.length && !noMoreData && (
          <div
            className="absolute flex w-full items-center justify-center"
            style={{ top: listHeight.current, height: loadingHeight }}
          >
            {isLoadingMore ? 'Loading more...' : 'load more'}
          </div>
        )}

        {/* 没有更多数据 */}
        {noMoreData && noMoreDataRender ? (
          <div
            className="absolute flex w-full items-center justify-center"
            style={{ top: listHeight.current, height: loadingHeight }}
          >
            {noMoreDataRender()}
          </div>
        ) : null}

        {/* 没有数据 */}
        {!renderList.current.length && !isFirstLoading && (
          <div className="absolute flex w-full items-center justify-center">
            No data
          </div>
        )}
      </div>
    </div>
  );
}

export default VirtualGrid;
