import { useReducer } from 'react';

interface Page {
  page: number;
  current: boolean;
}

interface IUsePagination<T> {
  items: T[];
  changeOffset: (offset: Offset, newValue?: number) => void;
  /**
   * Returns an array with the aviables pages to directly navigate.
   * This pages are centered in the current page and offest to 5 items to each side.

   * @returns {Page}
   */
  getPaginationRange: () => Page[];
}

interface Props<T> {
  items: T[];
  limit: number;
}

interface State {
  offset: number;
  total: number;
  limit: number;
}

type ActionType =
  | { type: 'update'; value: any; name: string }
  | { type: 'update'; value: any; name: string };

export enum Offset {
  Next,
  Prev,
  First,
  Last,
  To,
}

export default function usePagination<T>({
  items,
  limit = 30,
}: Props<T>): IUsePagination<T> {
  const reducer = (state: State, action: ActionType): State => {
    switch (action.type) {
      case 'update':
        return {
          ...state,
          [action.name]: action.value,
        };
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(reducer, {
    offset: 0,
    total: 0,
    limit,
  });

  const changeOffset = (offset: Offset, newValue: number = 1): void => {
    let value = 0;
    switch (offset) {
      case Offset.Next:
        value = state.offset + state.limit;
        break;
      case Offset.Prev:
        value = state.offset - state.limit;
        break;
      case Offset.First:
        value = 0;
        break;
      case Offset.Last:
        value = items.length - state.limit;
        break;
      case Offset.To:
        value = (newValue - 1) * state.limit;
        break;
      default:
        break;
    }

    if (value < 0 || value > items.length - state.limit) {
      return;
    }

    dispatch({
      type: 'update',
      name: 'offset',
      value,
    });
  };

  function getPaginationRange(): Page[] {
    // NOTE: this is made to work with uneven numbers,
    // So the current page is always aligned in the center.
    const paginationToSides = 5;

    const currentPage = Math.ceil(state.offset / state.limit) + 1;
    const lastPage = Math.ceil(items.length / state.limit);
    const start = Math.max(currentPage - paginationToSides - 1, 0);
    const end = Math.min(currentPage + paginationToSides, lastPage);

    return Array.from(
      {
        length: end - start,
      },
      (_, idx) => {
        const page = idx + 1 + start;
        return { page, current: page === currentPage };
      },
    );
  }

  return {
    changeOffset,
    getPaginationRange,
    items: items.slice(state.offset, state.offset + state.limit),
  };
}
