// Constants used for the status fields of standard actions
export const ACTION_STARTED = 0;
export const ACTION_SUCCEEDED = 1;
export const ACTION_FAILED = 2;

/**
 * standardAction
 * Creates a standard action to be dispatched.
 * Contains the type of the action and a status code for the action.
 * A payload can also be optionally set.
 *
 * @name standardAction
 * @function
 * @param {String} type The type of the action being dispatched
 * @param {int} status The status code of the action. Should be one of the const ints declared in this file.
 * @param {Object} payload An optional payload
 * @returns {Object} An Object to be dispatched
 */
function standardAction(type, status, payload = undefined) {
  const standAct = {
    type,
    status,
  };
  // Check that payload isn't undefined since null is a valid payload
  if (payload !== undefined) {
    standAct.payload = payload;
  }
  return standAct;
}

/**
 * createAsyncAction
 * Creates asynchronous action creators. That means when this is dispatched it
 * first dispatches an action saying that it started. When the asynchronous job
 * resolves it then dispatches an action saying that it either succeeded or failed
 * with the contents of the resolved Promise.
 * The payload of the action dispatched when the job starts can be set by passing
 * a startingPayloadCreator. startingPayloadCreator will be passed the same arguments
 * as the function for the asynchronous job and should return the object to be
 * used as the payload.
 *
 * @name createAsyncAction
 * @function
 * @param {String} type The type of the action being dispatched
 * @param {function(...) -> Object} func An action creator. A.K.A. function that creates an object to be dispatched
 * @param {function(...) -> Object} startingPayloadCreator An action creator for the object that will be dispatched when starting the async action.
 * @returns {function -> Promise(dispatch)} A function that returns a Promise that resolves to the dispatch method
 */
export function createAsyncAction(type, func, startingPayloadCreator = null) {
  return (...args) => (dispatch) => {
    let startingPayload = undefined;
    if (startingPayloadCreator !== null) {
      startingPayload = startingPayloadCreator(...args);
    }
    dispatch(standardAction(type, ACTION_STARTED, startingPayload));

    return func(...args)
      .then(data => dispatch(standardAction(type, ACTION_SUCCEEDED, data)))
      .catch(err => dispatch(standardAction(type, ACTION_FAILED, err)));
  };
}

/**
 * createSyncAction
 * Creates a synchronous action creator. That means it does a job when it is
 * dispatched (or just called) and returns an action saying that the job either
 * succeeded or failed, with the results of the job or the error as its payload respectively.
 *
 * @name createSyncAction
 * @function
 * @param {String} type The type of the action being dispatched
 * @param {function(...) -> Object} func An action creator. A.K.A. function that creates an object to be dispatched
 * @returns {function -> Object} A function that returns an object to be dispatched
 */
export function createSyncAction(type, func) {
  return (...args) => {
    try {
      const data = func(...args);
      return standardAction(type, ACTION_SUCCEEDED, data);
    } catch (err) {
      return standardAction(type, ACTION_FAILED, err);
    }
  };
}

/**
 * unstandardizeAction
 * Makes a standard action a non-standard action.
 * Used for compatability with reducers that have yet to be refactored.
 *
 * @name unstandardizeAction
 * @function
 * @param {Object} stdAction A standard action
 * @returns {Object} An unstandardized version of the passed action
 */
export function unstandardizeAction(stdAction) {
  return Object.assign({type: stdAction.type}, stdAction.payload);
}
