import { useState, useMemo, useEffect } from 'react';

/**
 * @typedef {Object} PaginationState
 * @property {number} pageSize - items per page
 * @property {number} currentPage - current page number
 * @property {number} total - total items count
 */

/**
 * @typedef Pagination
 * @property {number} pageSize - items per page
 * @property {number} currentPage - current page number
 * @property {number} total - total items count
 * @property {number} count - items count of the current page
 * @property {function} setPage - set current page
 * @property {function} setPageSize - set items per page
 * @property {function} setTotal - set total items count
 * @property {function} setCount - set current page's items count
 */

/**
 * Provides state and action for pagination components, doesn't control
 * state except for resetting page when page size or total changes
 * ```
  const pagination = usePaginationState();

  return (
    <PaginationControls
      total={pagination.total}
      count={pagination.count}
      currentPage={pagination.currentPage}
      pageSize={pagination.pageSize}
      availableSizes={[20, 50, 100]}
      onPageChange={pagination.setPage}
      onPageSizeChange={pagination.setPageSize()}
    />
  )
 ```
 * @param {PaginationState} initialState - initial values that will be applied
 * on mount
 * @returns {Pagination}
 */

export function usePaginationState(config) {
  const initialSize = config?.pageSize ?? 20;
  const initialTotal = config?.total ?? 0;
  const initialCount = config?.count ?? 0;
  const initialPage = config?.currentPage ?? 0;
  const [currentPage, setPage] = useState(initialPage);
  const [total, setTotal] = useState(initialTotal);
  const [count, setCount] = useState(initialCount);
  const [pageSize, setPageSize] = useState(initialSize);
  return useMemo(() => ({
    currentPage,
    total,
    pageSize,
    count,
    // React shows warning if setState is called
    // with multiple arguments, which happens
    // when pagination component calls these
    setPage: page => setPage(page),
    setPageSize: size => {
      setPage(0);
      setPageSize(size);
    },
    setCount: newCount => setCount(newCount),
    setTotal: newTotal => {
      if (newTotal < pageSize * currentPage) {
        setPage(Math.floor(newTotal / pageSize));
      }
      setTotal(newTotal);
    }
  }), [currentPage, total, count, pageSize]);
}

/**
 * @typedef PaginatedItemsState
 * @property {array} items - visible items
 * @property {number} pageSize - items per page
 * @property {number} currentPage - current page number
 * @property {number} total - total items count
 * @property {number} count - items count of the current page
 * @property {function} setPage - set current page
 * @property {function} setPageSize - set items per page
 * @property {function} setTotal - set total items count
 * @property {function} setCount - set current page's items count
 */

/**
 * Pagination hook that provides slice of data for current page
 *
```
  const fullListOfItems = getALotOfItemsSomehow();

  const { items, ...pagination } = usePaginatedSearch(fullListOfItems);

  return (
    <React.Fragment>
      <PaginationControls
        total={pagination.total}
        count={pagination.count}
        currentPage={pagination.currentPage}
        pageSize={pagination.pageSize}
        availableSizes={[20, 50, 100]}
        onPageChange={pagination.setPage}
        onPageSizeChange={pagination.setPageSize()}
      />
      {items.map(item => <div key={item.id}>{item.name}</div>)}
    <React.Fragment>
  )
```
 * @param {array} items - any array of items that need to be paginated
 * @param {number} initialPageSize - initial page size, default is 20
 * @returns {PaginatedItemsState} - object that contains
 * items on current page and pagination state and actions
 */

export function usePagination(items, initialPageSize) {
  const {
    currentPage,
    pageSize,
    setPage,
    setPageSize,
    count
  } = usePaginationState({
    total: items.length,
    pageSize: initialPageSize
  });
  const pageData = useMemo(() => items.slice(currentPage * pageSize, currentPage * pageSize + pageSize), [items, pageSize, currentPage]);

  // Reset page when new data might not exist on current page
  useEffect(() => {
    setPage(0);
  }, [items]);
  return useMemo(() => ({
    currentPage,
    pageSize,
    setPage,
    setPageSize,
    items: pageData,
    total: items.length,
    count
  }), [pageData, currentPage, pageSize, setPage, setPageSize]);
}