import { ConnectionCursor, fromGlobalId } from 'graphql-relay';
import { PaginationArgs } from './pagination.args';

/**
 * use to get the id of the cursor
 * by manipulate the cursor
 *
 * @param cursor
 * @returns
 */
const getId = (cursor: ConnectionCursor) =>
  parseInt(fromGlobalId(cursor).id, 10);

/**
 * get the nextid
 * so we can use the id to get the next cursor
 * by manipulate the cursor
 *
 *
 * @param cursor
 * @returns
 */
const nextId = (cursor: ConnectionCursor) => getId(cursor) + 1;

/**
 * ## PaginationPageParam
 *
 * The param for the pagination page
 * combine the limit and offset
 *
 * @param limit - limit of the data will be showed
 * @param offset - offset of the data will be showed
 *
 */
export type PaginationPageParam = {
  limit?: number;
  offset?: number;
};

/**
 * get the paging meta
 * get the pagination parameters and meta
 */
export function getPagingParameters(args: PaginationArgs): PaginationPageParam {
  // find the meta, by checking the sanity of the pagination
  const meta = checkPagingSanity(args);

  // start by switch the paging type
  // and get the paging parameters
  switch (meta.pagingType) {
    case 'forward': {
      return {
        limit: meta.first,
        offset: meta.after ? nextId(meta.after) : 0,
      };
    }
    case 'backward': {
      const { last, before } = meta;
      let limit = last;
      let offset = getId(before!) - last;

      if (offset < 0) {
        limit = Math.max(last + offset, 0);
        offset = 0;
      }

      return { offset, limit };
    }
    default:
      return {
        limit: 8,
        offset: 0,
      };
  }
}

/**
 * ## PagingMeta
 *
 * Paging meta type
 * allow to manage the type of forward, backward, none
 *
 *
 */
type PagingMeta =
  | { pagingType: 'forward'; after?: string; first: number }
  | { pagingType: 'backward'; before?: string; last: number }
  | { pagingType: 'none' };

/**
 * ### checkPagingSanity
 *
 * Check the pagination sanity
 * @param args
 * @returns
 */
function checkPagingSanity(args: PaginationArgs): PagingMeta {
  const { first = 0, last = 0, after, before } = args;

  const isForwardPaging = !!first || !!after;
  const isBackwardPaging = !!last || !!before;
  if (isForwardPaging && isBackwardPaging) {
    throw new Error('Relay pagination cannot be forwards AND backwards!');
  }
  if ((isForwardPaging && before) || (isBackwardPaging && after)) {
    throw new Error('Paging must use either first/after or last/before!');
  }
  if ((isForwardPaging && first < 0) || (isBackwardPaging && last < 0)) {
    throw new Error('Paging limit must be positive!');
  }
  if (last && !before) {
    throw new Error("When paging backwards, a 'before' argument is required!");
  }

  return isForwardPaging
    ? { pagingType: 'forward', after, first }
    : isBackwardPaging
    ? { pagingType: 'backward', before, last }
    : { pagingType: 'none' };
}
