import { keepPreviousData, useQuery } from '@tanstack/react-query';
import { useDebounceFn, useMemoizedFn } from 'ahooks';
import { useState, useRef } from 'react';

type ParamsType<Q = any> = {
  index: number;
  limit: number;
  query: Q;
};

type PropsType<T = any, Q = any> = {
  queryKey: string;
  defaultQuery?: Q;
  defaultIndex?: number;
  limit?: number;
  isRowSame?: (d: T, n: T) => boolean;
};

type RequestType<T = any, Q = any> = (
  params: ParamsType<Q>
) => Promise<{ rows: T[]; total: number } | undefined>;

type UseInfiniteScrollValue<T = any, Q = any> = {
  query: Q;
  onLoadMore: () => void;
  onSearch: (query: Q) => void;
  dataSource: T[];
  total: number;
  // 是否正在加载第一页，包括 初始化加载 和 参数改变时加载
  isFirstLoading: boolean;
  isLoadingMore: boolean;
  noMoreData: boolean;
};

type DataType<T = any> = {
  rows: T[];
  total: number;
  index: number;
};

const useSmartInfiniteQuery = <T = any, Q = any>(
  request: RequestType<T, Q>,
  props: PropsType<T, Q>
): UseInfiniteScrollValue<T, Q> => {
  const {
    queryKey,
    defaultQuery,
    defaultIndex = 1,
    limit = 20,
    isRowSame = (d, n) => {
      return (d as any)?.id === (n as any)?.id;
    },
  } = props;
  const [query, setQuery] = useState<Q>(defaultQuery as Q);
  const index = useRef<number>(defaultIndex);

  // 判断第一页数据和缓存中的第一页数据是否相等
  const isSameData = useMemoizedFn((prevData: DataType<T>, result: Omit<DataType<T>, 'index'>) => {
    // 没有缓存，必不相等
    if (prevData.total <= 0 || !prevData.rows?.length) {
      return false;
    }
    // 如果第一个元素相等，则认为数据相等（实际上并不一定）
    if (!isRowSame(prevData.rows[0], result?.rows[0])) {
      return false;
    }
    return true;
  });

  const { data, refetch, isFetching } = useQuery({
    queryKey: [queryKey, query],
    placeholderData: keepPreviousData,
    queryFn: async ({ client }) => {
      const params = {
        index: index.current,
        limit,
        query,
      };
      const result = await request(params);
      const prevData = (client.getQueryData([queryKey, query]) as DataType<T>) ?? {
        rows: [],
        total: 0,
        index: 1,
      };

      // 如果是第一页
      if (params.index === defaultIndex) {
        // 第一页数据和缓存中相等
        if (isSameData(prevData, result!)) {
          // 更新索引
          index.current = prevData.index;
          // 直接返回缓存里的
          return prevData;
        } else {
          // 第一页数据和缓存中不等， 返回新的数据
          return {
            total: result?.total || 0,
            rows: result?.rows || [],
            index: params.index,
          };
        }
      }

      // 不是第一页， 返回合并后的数据
      return {
        total: result?.total || 0,
        rows: [...(prevData?.rows || []), ...(result?.rows || [])],
        index: params.index,
      };
    },
  });

  const { run: onLoadMore } = useDebounceFn(
    () => {
      if (isFetching) return;
      index.current = index.current + 1;
      refetch();
    },
    {
      wait: 300,
      maxWait: 500,
    }
  );

  const onSearch = useMemoizedFn((query: Q) => {
    index.current = defaultIndex;
    setQuery(query);
  });

  return {
    query,
    onLoadMore,
    onSearch,
    dataSource: data?.rows || [],
    total: data?.total || 0,
    isFirstLoading: isFetching && index.current === defaultIndex,
    isLoadingMore: isFetching && index.current > defaultIndex,
    noMoreData: data?.total === data?.rows?.length && !isFetching,
  };
};

export default useSmartInfiniteQuery;
