// @flow

import { get, union, mergeWith } from 'lodash';
import joinReducers from 'app/utils/joinReducers';

import type { ActionTypeObject } from 'app/utils/promiseMiddleware';
import type { Reducer } from 'app/types';

type EntityReducerOptions = {
  key: string,
  types: {
    fetch?: ActionTypeObject,
    mutate?: ActionTypeObject
  },
  mutate?: Reducer<*, *>,
  initialState?: Object
};

export function fetching(fetchType?: ActionTypeObject) {
  return (state: any, action: any) => {
    if (!fetchType) {
      return state;
    }

    switch (action.type) {
      case fetchType.BEGIN:
        return { ...state, fetching: true };

      case fetchType.SUCCESS:
      case fetchType.FAILURE:
        return { ...state, fetching: false };

      default:
        return state;
    }
  };
}

function merge(old, updated) {
  return mergeWith(
    {},
    old,
    updated,
    (oldValue, newValue) => (Array.isArray(oldValue) ? newValue : undefined)
  );
}

export function entities(key: string, fetchType?: ActionTypeObject) {
  return (
    state: any = {
      actionGrant: [],
      byId: {},
      items: []
    },
    action: any
  ) => {
    const result = get(action, ['payload', 'entities', key], {});
    const actionGrant = get(action, ['payload', 'actionGrant'], []);

    if (
      !action.payload ||
      action.type !== get(fetchType, 'SUCCESS', action.type)
    )
      return state;

    return {
      ...state,
      byId: merge(state.byId, result),
      items: union(
        state.items,
        Object.keys(result).map(i => parseInt(i, 10) || i)
      ),
      actionGrant: union(state.actionGrant, actionGrant)
    };
  };
}

export function optimistic(mutateType?: ActionTypeObject) {
  return (state: any, action: any) => {
    if (
      !mutateType ||
      ![mutateType.FAILURE, mutateType.SUCCESS].includes(action.type)
    ) {
      return state;
    }

    if (!action.meta.optimisticId) {
      return state;
    }

    return {
      ...state,
      items: state.items.filter(item => item !== action.meta.optimisticId)
    };
  };
}

/**
 * Create reducers for common crud actions
 */
export default function createEntityReducer({
  key,
  types,
  mutate,
  initialState = {}
}: EntityReducerOptions) {
  const { fetch: fetchType, mutate: mutateType } = types;

  const finalInitialState = {
    actionGrant: [],
    byId: {},
    items: [],
    fetching: false,
    ...initialState
  };

  const reduce = joinReducers(
    fetching(fetchType),
    entities(key, fetchType),
    optimistic(mutateType),
    mutate
  );

  return (state: any = finalInitialState, action: any) => reduce(state, action);
}

@@ -1152,20 +1152,18 @@
 %3E %7B%0A    
-cons
+le
 t result
@@ -1209,23 +1209,17 @@
 key%5D
-, %7B%7D
 );%0A    
-cons
+le
 t ac
@@ -1272,12 +1272,8 @@
 nt'%5D
-, %5B%5D
 );%0A%0A
@@ -1307,24 +1307,51 @@
 oad %7C%7C%0A     
+ (!result && actionGrant &&
  action.type
@@ -1383,21 +1383,9 @@
 ESS'
-, action.type
+)
 )%0A  
@@ -1409,16 +1409,80 @@
 state;%0A%0A
+    result = result %7C%7C %7B%7D;%0A    actionGrant = actionGrant %7C%7C %5B%5D;%0A
     retu

Handle actionGrants correctly when result is undefined