import { StartAction, SuccessAction, FailureAction, ApiStatus, ApiState } from './types';

export const INITIAL_API_STATUS: ApiStatus = {
  isEmpty: true,
  isLoading: false,
  isValid: false,
  isError: false,
  dateTime: Date.now(),
  error: null,
  errorMessage: null,
};

export const INITIAL_API_STATE: ApiState = {
  _status: INITIAL_API_STATUS,
  items: [],
};

type AllActions = StartAction | SuccessAction | FailureAction;

/**
 * Use this function to create any reducer which handles api communication
 * @param {Array} actionTypes Array of action types associated with fetchThunk e.g. USER_REQUEST,
 * USER_SUCCESS and USER_ERROR
 * @param {function} dataAdapter function which will be called to transform response body
 * to format you want to use in state
 * @param {function} errorAdapter function which will be called to transform error
 * to format you want to use in state
 * @returns {function} reducer handles api requests, saves response body as data and status
 */
export const createApiReducer =
  (
    actionTypes: string[],
    dataAdapter: (data: any, state?: any) => any = (data: any) => data,
    errorAdapter = (data: any) => data,
  ) =>
  (state = INITIAL_API_STATE, action: AllActions) => {
    const [REQUEST, SUCCESS, ERROR] = actionTypes;

    switch (action.type) {
      case REQUEST:
        return {
          ...state,
          _status: {
            ...state._status,
            isLoading: true,
            dateTime: Date.now(),
          },
        };
      case SUCCESS:
        return {
          _status: {
            isEmpty: false,
            isLoading: false,
            isValid: true,
            isError: false,
            dateTime: Date.now(),
            error: null,
            errorMessage: null,
          },
          ...dataAdapter(action.payload, state),
        };
      case ERROR:
        return {
          ...state,
          _status: {
            isEmpty: false,
            isLoading: false,
            isValid: false,
            isError: true,
            error: action.payload,
            errorMessage: errorAdapter(action.payload),
          },
        };

      default:
        return state;
    }
  };

/**
 * This is higher order reducer
 * Use this if you want your state is combination of results of multiple reducers
 * under the same key, e.g. you can get the same data from multiple endpoints
 */
export const chainReducers =
  (initialState: any, ...args: any[]) =>
  (state = initialState, action: any) =>
    args.reduce((newState, reducer) => reducer(newState, action), state);

/**
 * Creates an opinionated CRUD reducer
 */
export function createCrudApiReducer<T>(
  startActionType: string,
  failureActionType: string,
  fetchActionType: string,
  addActionType: string,
  updateActionType: string,
  deleteActionType: string,
  transformItem: (data: any) => T,
  keyField = 'id',
) {
  const fetchReducer = createApiReducer(
    [startActionType, fetchActionType, failureActionType],
    (data) => ({ _meta: data.meta, items: (data.data || data).map(transformItem) }),
    (data) => data,
  );

  const addReducer = createApiReducer(
    [startActionType, addActionType, failureActionType],
    (data, state) => ({ items: [...state.items, transformItem(data)] }),
    (data) => data,
  );

  const updateReducer = createApiReducer(
    [startActionType, updateActionType, failureActionType],
    (data, state) => ({
      items: state.items.map((i: any) => (i[keyField] === data[keyField] ? transformItem(data) : i)),
    }),
    (data) => data,
  );

  const deleteReducer = createApiReducer(
    [startActionType, deleteActionType, failureActionType],
    (data, state) => ({
      items: state.items.filter((i: any) => i[keyField] !== data[keyField]),
    }),
    (data) => data,
  );

  return chainReducers(INITIAL_API_STATE, fetchReducer, addReducer, updateReducer, deleteReducer);
}

/**
 * Creates an opinionated CRUD reducer without paging
 */
export function createCrudApiReducerWithoutPaging<T>(
  startActionType: string,
  failureActionType: string,
  fetchActionType: string,
  addActionType: string,
  updateActionType: string,
  deleteActionType: string,
  transformItem: (data: any) => T,
  keyField = 'id',
) {
  const fetchReducer = createApiReducer(
    [startActionType, fetchActionType, failureActionType],
    (data) => ({ items: data.map(transformItem) }),
    (data) => data,
  );

  const addReducer = createApiReducer(
    [startActionType, addActionType, failureActionType],
    (data, state) => ({ items: [...state.items, transformItem(data)] }),
    (data) => data,
  );

  const updateReducer = createApiReducer(
    [startActionType, updateActionType, failureActionType],
    (data, state) => ({
      items: state.items.map((i: any) => (i[keyField] === data[keyField] ? transformItem(data) : i)),
    }),
    (data) => data,
  );

  const deleteReducer = createApiReducer(
    [startActionType, deleteActionType, failureActionType],
    (data, state) => ({
      items: state.items.filter((i: any) => i[keyField] !== data[keyField]),
    }),
    (data) => data,
  );

  return chainReducers(INITIAL_API_STATE, fetchReducer, addReducer, updateReducer, deleteReducer);
}
