text: import uuid from 'uuid/v4';
  import { actions as notifActions } from 'redux-notifications';
  import { BEGIN, COMMIT, REVERT } from 'redux-optimist';
  import { serializeValues } from 'Lib/serializeEntryValues';
  import { currentBackend } from 'src/backend';
  import { getAsset } from 'Reducers';
  import { selectFields } from 'Reducers/collections';
  import { EDITORIAL_WORKFLOW } from 'Constants/publishModes';
  import { EditorialWorkflowError } from '@rrpm/netlify-cms-lib-util';
  import { loadEntry } from './entries';
  import ValidationErrorTypes from 'Constants/validationErrorTypes';
  
  const { notifSend } = notifActions;
  
  /*
   * Contant Declarations
   */
  export const UNPUBLISHED_ENTRY_REQUEST = 'UNPUBLISHED_ENTRY_REQUEST';
  export const UNPUBLISHED_ENTRY_SUCCESS = 'UNPUBLISHED_ENTRY_SUCCESS';
  export const UNPUBLISHED_ENTRY_REDIRECT = 'UNPUBLISHED_ENTRY_REDIRECT';
  
  export const UNPUBLISHED_ENTRIES_REQUEST = 'UNPUBLISHED_ENTRIES_REQUEST';
  export const UNPUBLISHED_ENTRIES_SUCCESS = 'UNPUBLISHED_ENTRIES_SUCCESS';
  export const UNPUBLISHED_ENTRIES_FAILURE = 'UNPUBLISHED_ENTRIES_FAILURE';
  
  export const UNPUBLISHED_ENTRY_PERSIST_REQUEST = 'UNPUBLISHED_ENTRY_PERSIST_REQUEST';
  export const UNPUBLISHED_ENTRY_PERSIST_SUCCESS = 'UNPUBLISHED_ENTRY_PERSIST_SUCCESS';
  export const UNPUBLISHED_ENTRY_PERSIST_FAILURE = 'UNPUBLISHED_ENTRY_PERSIST_FAILURE';
  
  export const UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST';
  export const UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS';
  export const UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE';
  
  export const UNPUBLISHED_ENTRY_PUBLISH_REQUEST = 'UNPUBLISHED_ENTRY_PUBLISH_REQUEST';
  export const UNPUBLISHED_ENTRY_PUBLISH_SUCCESS = 'UNPUBLISHED_ENTRY_PUBLISH_SUCCESS';
  export const UNPUBLISHED_ENTRY_PUBLISH_FAILURE = 'UNPUBLISHED_ENTRY_PUBLISH_FAILURE';
  
  export const UNPUBLISHED_ENTRY_DELETE_REQUEST = 'UNPUBLISHED_ENTRY_DELETE_REQUEST';
  export const UNPUBLISHED_ENTRY_DELETE_SUCCESS = 'UNPUBLISHED_ENTRY_DELETE_SUCCESS';
  export const UNPUBLISHED_ENTRY_DELETE_FAILURE = 'UNPUBLISHED_ENTRY_DELETE_FAILURE';
  
  /*
   * Simple Action Creators (Internal)
   */
  
  function unpublishedEntryLoading(collection, slug) {
    return {
      type: UNPUBLISHED_ENTRY_REQUEST,
      payload: {
        collection: collection.get('name'),
        slug,
      },
    };
  }
  
  function unpublishedEntryLoaded(collection, entry) {
    return {
      type: UNPUBLISHED_ENTRY_SUCCESS,
      payload: {
        collection: collection.get('name'),
        entry,
      },
    };
  }
  
  function unpublishedEntryRedirected(collection, slug) {
    return {
      type: UNPUBLISHED_ENTRY_REDIRECT,
      payload: {
        collection: collection.get('name'),
        slug,
      },
    };
  }
  
  function unpublishedEntriesLoading() {
    return {
      type: UNPUBLISHED_ENTRIES_REQUEST,
    };
  }
  
  function unpublishedEntriesLoaded(entries, pagination) {
    return {
      type: UNPUBLISHED_ENTRIES_SUCCESS,
      payload: {
        entries,
        pages: pagination,
      },
    };
  }
  
  function unpublishedEntriesFailed(error) {
    return {
      type: UNPUBLISHED_ENTRIES_FAILURE,
      error: 'Failed to load entries',
      payload: error,
    };
  }
  
  function unpublishedEntryPersisting(collection, entry, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_PERSIST_REQUEST,
      payload: {
        collection: collection.get('name'),
        entry,
      },
      optimist: { type: BEGIN, id: transactionID },
    };
  }
  
  function unpublishedEntryPersisted(collection, entry, transactionID, slug) {
    return {
      type: UNPUBLISHED_ENTRY_PERSIST_SUCCESS,
      payload: {
        collection: collection.get('name'),
        entry,
        slug,
      },
      optimist: { type: COMMIT, id: transactionID },
    };
  }
  
  function unpublishedEntryPersistedFail(error, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_PERSIST_FAILURE,
      payload: { error },
      optimist: { type: REVERT, id: transactionID },
      error,
    };
  }
  
  function unpublishedEntryStatusChangeRequest(
    collection,
    slug,
    oldStatus,
    newStatus,
    transactionID,
  ) {
    return {
      type: UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST,
      payload: {
        collection,
        slug,
        oldStatus,
        newStatus,
      },
      optimist: { type: BEGIN, id: transactionID },
    };
  }
  
  function unpublishedEntryStatusChangePersisted(
    collection,
    slug,
    oldStatus,
    newStatus,
    transactionID,
  ) {
    return {
      type: UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS,
      payload: {
        collection,
        slug,
        oldStatus,
        newStatus,
      },
      optimist: { type: COMMIT, id: transactionID },
    };
  }
  
  function unpublishedEntryStatusChangeError(collection, slug, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE,
      payload: { collection, slug },
      optimist: { type: REVERT, id: transactionID },
    };
  }
  
  function unpublishedEntryPublishRequest(collection, slug, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_PUBLISH_REQUEST,
      payload: { collection, slug },
      optimist: { type: BEGIN, id: transactionID },
    };
  }
  
  function unpublishedEntryPublished(collection, slug, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_PUBLISH_SUCCESS,
      payload: { collection, slug },
      optimist: { type: COMMIT, id: transactionID },
    };
  }
  
  function unpublishedEntryPublishError(collection, slug, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_PUBLISH_FAILURE,
      payload: { collection, slug },
      optimist: { type: REVERT, id: transactionID },
    };
  }
  
  function unpublishedEntryDeleteRequest(collection, slug, transactionID) {
    // The reducer doesn't handle this action -- it is for `optimist`.
    return {
      type: UNPUBLISHED_ENTRY_DELETE_REQUEST,
      payload: { collection, slug },
      optimist: { type: BEGIN, id: transactionID },
    };
  }
  
  function unpublishedEntryDeleted(collection, slug, transactionID) {
    return {
      type: UNPUBLISHED_ENTRY_DELETE_SUCCESS,
      payload: { collection, slug },
      optimist: { type: COMMIT, id: transactionID },
    };
  }
  
  function unpublishedEntryDeleteError(collection, slug, transactionID) {
    // The reducer doesn't handle this action -- it is for `optimist`.
    return {
      type: UNPUBLISHED_ENTRY_DELETE_FAILURE,
      payload: { collection, slug },
      optimist: { type: REVERT, id: transactionID },
    };
  }
  
  /*
   * Exported Thunk Action Creators
   */
  
  export function loadUnpublishedEntry(collection, slug) {
    try {
      collection = typeof collection === 'undefined' ? null : collection;
      // console.log('NC Core Editorial: loadUnpublishedEntry()', { collection, slug });
    } catch (e1) {}
  
    return (dispatch, getState) => {
      const state = getState();
      const backend = currentBackend(state.config);
      dispatch(unpublishedEntryLoading(collection, slug));
  
      try {
        collection = typeof collection === 'undefined' ? null : collection;
        // console.log('NC Core Editorial: loadUnpublishedEntry *Thunk*', { collection, slug });
      } catch (e2) {}
  
      backend
        .unpublishedEntry(collection, slug)
        .then(entry => dispatch(unpublishedEntryLoaded(collection, entry)))
        .catch(error => {
          try {
            collection = typeof collection === 'undefined' ? null : collection;
            // console.log('NC Core Editorial: loadUnpublishedEntry *Thunk* Error', { collection, slug, error });
          } catch (e3) {}
  
          if (error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) {
            dispatch(unpublishedEntryRedirected(collection, slug));
            dispatch(loadEntry(collection, slug));
          } else {
            dispatch(
              notifSend({
                message: {
                  key: 'ui.toast.onFailToLoadEntries',
                  details: error,
                },
                kind: 'danger',
                dismissAfter: 8000,
              }),
            );
          }
        });
    };
  }
  
  export function loadUnpublishedEntries(collections) {
    try {
      collections = typeof collections === 'undefined' ? null : collections;
      // console.log('NC Core Editorial: loadUnpublishedEntries()', { collections });
    } catch (e) {}
  
    return (dispatch, getState) => {
      const state = getState();
      if (state.config.get('publish_mode') !== EDITORIAL_WORKFLOW) return;
      const backend = currentBackend(state.config);
      dispatch(unpublishedEntriesLoading());
      backend
        .unpublishedEntries(collections)
        .then(response => dispatch(unpublishedEntriesLoaded(response.entries, response.pagination)))
        .catch(error => {
          dispatch(
            notifSend({
              message: {
                key: 'ui.toast.onFailToLoadEntries',
                details: error,
              },
              kind: 'danger',
              dismissAfter: 8000,
            }),
          );
          dispatch(unpublishedEntriesFailed(error));
          Promise.reject(error);
        });
    };
  }
  
  export function persistUnpublishedEntry(collection, existingUnpublishedEntry) {
    return async (dispatch, getState) => {
      const state = getState();
      const entryDraft = state.entryDraft;
      const fieldsErrors = entryDraft.get('fieldsErrors');
  
      // Early return if draft contains validation errors
      if (!fieldsErrors.isEmpty()) {
        const hasPresenceErrors = fieldsErrors.some(errors =>
          errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE),
        );
  
        if (hasPresenceErrors) {
          dispatch(
            notifSend({
              message: {
                key: 'ui.toast.missingRequiredField',
              },
              kind: 'danger',
              dismissAfter: 8000,
            }),
          );
        }
        return Promise.reject();
      }
  
      const backend = currentBackend(state.config);
      const transactionID = uuid();
      const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
      const entry = entryDraft.get('entry');
  
      /**
       * Serialize the values of any fields with registered serializers, and
       * update the entry and entryDraft with the serialized values.
       */
      const fields = selectFields(collection, entry.get('slug'));
      const serializedData = serializeValues(entryDraft.getIn(['entry', 'data']), fields);
      const serializedEntry = entry.set('data', serializedData);
      const serializedEntryDraft = entryDraft.set('entry', serializedEntry);
  
      dispatch(unpublishedEntryPersisting(collection, serializedEntry, transactionID));
      const persistAction = existingUnpublishedEntry
        ? backend.persistUnpublishedEntry
        : backend.persistEntry;
      const persistCallArgs = [
        backend,
        state.config,
        collection,
        serializedEntryDraft,
        assetProxies.toJS(),
        state.integrations,
      ];
  
      try {
        const newSlug = await persistAction.call(...persistCallArgs);
        dispatch(
          notifSend({
            message: {
              key: 'ui.toast.entrySaved',
            },
            kind: 'success',
            dismissAfter: 4000,
          }),
        );
        dispatch(unpublishedEntryPersisted(collection, serializedEntry, transactionID, newSlug));
      } catch (error) {
        dispatch(
          notifSend({
            message: {
              key: 'ui.toast.onFailToPersist',
              details: error,
            },
            kind: 'danger',
            dismissAfter: 8000,
          }),
        );
        return Promise.reject(dispatch(unpublishedEntryPersistedFail(error, transactionID)));
      }
    };
  }
  
  export function updateUnpublishedEntryStatus(collection, slug, oldStatus, newStatus) {
    return (dispatch, getState) => {
      if (oldStatus === newStatus) return;
      const state = getState();
      const backend = currentBackend(state.config);
      const transactionID = uuid();
      dispatch(
        unpublishedEntryStatusChangeRequest(collection, slug, oldStatus, newStatus, transactionID),
      );
      backend
        .updateUnpublishedEntryStatus(collection, slug, newStatus)
        .then(() => {
          dispatch(
            notifSend({
              message: {
                key: 'ui.toast.entryUpdated',
              },
              kind: 'success',
              dismissAfter: 4000,
            }),
          );
          dispatch(
            unpublishedEntryStatusChangePersisted(
              collection,
              slug,
              oldStatus,
              newStatus,
              transactionID,
            ),
          );
        })
        .catch(error => {
          dispatch(
            notifSend({
              message: {
                key: 'ui.toast.onFailToUpdateStatus',
                details: error,
              },
              kind: 'danger',
              dismissAfter: 8000,
            }),
          );
          dispatch(unpublishedEntryStatusChangeError(collection, slug, transactionID));
        });
    };
  }
  
  export function deleteUnpublishedEntry(collection, slug) {
    return (dispatch, getState) => {
      const state = getState();
      const backend = currentBackend(state.config);
      const transactionID = uuid();
      dispatch(unpublishedEntryDeleteRequest(collection, slug, transactionID));
      return backend
        .deleteUnpublishedEntry(collection, slug)
        .then(() => {
          dispatch(
            notifSend({
              message: { key: 'ui.toast.onDeleteUnpublishedChanges' },
              kind: 'success',
              dismissAfter: 4000,
            }),
          );
          dispatch(unpublishedEntryDeleted(collection, slug, transactionID));
        })
        .catch(error => {
          dispatch(
            notifSend({
              message: { key: 'ui.toast.onDeleteUnpublishedChanges', details: error },
              kind: 'danger',
              dismissAfter: 8000,
            }),
          );
          dispatch(unpublishedEntryDeleteError(collection, slug, transactionID));
        });
    };
  }
  
  export function publishUnpublishedEntry(collection, slug) {
    return (dispatch, getState) => {
      const state = getState();
      const collections = state.collections;
      const backend = currentBackend(state.config);
      const transactionID = uuid();
      dispatch(unpublishedEntryPublishRequest(collection, slug, transactionID));
      return backend
        .publishUnpublishedEntry(collection, slug)
        .then(() => {
          dispatch(
            notifSend({
              message: { key: 'ui.toast.entryPublished' },
              kind: 'success',
              dismissAfter: 4000,
            }),
          );
          dispatch(unpublishedEntryPublished(collection, slug, transactionID));
          dispatch(loadEntry(collections.get(collection), slug));
        })
        .catch(error => {
          dispatch(
            notifSend({
              message: { key: 'ui.toast.onFailToPublishEntry', details: error },
              kind: 'danger',
              dismissAfter: 8000,
            }),
          );
          dispatch(unpublishedEntryPublishError(collection, slug, transactionID));
        });
    };
  }
  