'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.clearRecord = exports.detachSubrecord = exports.saveRecordWithTransition = exports.transitionRecord = exports.deleteRecord = exports.revertRecord = exports.setFieldValue = exports.moveFieldValue = exports.deleteFieldValue = exports.sortFieldInstances = exports.addFieldInstance = exports.saveRecord = exports.createNewSubrecord = exports.createNewRecord = exports.readRecord = exports.validateRecordData = exports.validateFieldValue = exports.computeRecordData = exports.computeFieldValue = exports.DETACH_SUBRECORD = exports.VALIDATION_PASSED = exports.VALIDATION_FAILED = exports.REVERT_RECORD = exports.SET_FIELD_VALUE = exports.MOVE_FIELD_VALUE = exports.DELETE_FIELD_VALUE = exports.SORT_FIELD_INSTANCES = exports.ADD_FIELD_INSTANCE = exports.RECORD_TRANSITION_REJECTED = exports.RECORD_TRANSITION_FULFILLED = exports.RECORD_TRANSITION_STARTED = exports.RECORD_SAVE_REJECTED = exports.RECORD_SAVE_FULFILLED = exports.RECORD_SAVE_STARTED = exports.SUBRECORD_READ_FULFILLED = exports.RECORD_READ_REJECTED = exports.RECORD_READ_FULFILLED = exports.RECORD_READ_STARTED = exports.RECORD_DELETE_REJECTED = exports.RECORD_DELETE_FULFILLED = exports.RECORD_DELETE_STARTED = exports.SUBRECORD_CREATED = exports.RECORD_CREATED = exports.FIELD_COMPUTE_REJECTED = exports.FIELD_COMPUTE_FULFILLED = exports.CREATE_NEW_SUBRECORD = exports.CREATE_NEW_RECORD = exports.CLEAR_RECORD = undefined;

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /* global window */

var _reactIntl = require('react-intl');

var _get = require('lodash/get');

var _get2 = _interopRequireDefault(_get);

var _merge = require('lodash/merge');

var _merge2 = _interopRequireDefault(_merge);

var _immutable = require('immutable');

var _immutable2 = _interopRequireDefault(_immutable);

var _cspace = require('./cspace');

var _cspace2 = _interopRequireDefault(_cspace);

var _notificationHelpers = require('../helpers/notificationHelpers');

var _notificationHelpers2 = _interopRequireDefault(_notificationHelpers);

var _getErrorDescription = require('../helpers/getErrorDescription');

var _getErrorDescription2 = _interopRequireDefault(_getErrorDescription);

var _HierarchyReparentNotifier = require('../components/record/HierarchyReparentNotifier');

var _HierarchyReparentNotifier2 = _interopRequireDefault(_HierarchyReparentNotifier);

var _notification = require('./notification');

var _search = require('./search');

var _reducers = require('../reducers');

var _configHelpers = require('../helpers/configHelpers');

var _recordDataHelpers = require('../helpers/recordDataHelpers');

var _searchHelpers = require('../helpers/searchHelpers');

var _errorCodes = require('../constants/errorCodes');

var _notificationStatusCodes = require('../constants/notificationStatusCodes');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const deleteMessages = (0, _reactIntl.defineMessages)({
  deleting: {
    'id': 'action.record.deleting',
    'defaultMessage': '{hasTitle, select, yes {Deleting {title}\u2026} other {Deleting record\u2026}}'
  },
  errorDeleting: {
    'id': 'action.record.errorDeleting',
    'defaultMessage': '{hasTitle, select, yes {Error deleting {title}: {error}} other {Error deleting record: {error}}}'
  },
  deleted: {
    'id': 'action.record.deleted',
    'defaultMessage': '{hasTitle, select, yes {Deleted {title}} other {Deleted record}}'
  }
});

const saveMessages = (0, _reactIntl.defineMessages)({
  saving: {
    'id': 'action.record.saving',
    'defaultMessage': '{hasTitle, select, yes {Saving {title}\u2026} other {Saving record\u2026}}'
  },
  errorSaving: {
    'id': 'action.record.errorSaving',
    'defaultMessage': '{hasTitle, select, yes {Error saving {title}: {error}} other {Error saving record: {error}}}'
  },
  saved: {
    'id': 'action.record.saved',
    'defaultMessage': '{hasTitle, select, yes {Saved {title}} other {Saved record}}'
  }
});

const transitionMessages = {
  delete: (0, _reactIntl.defineMessages)({
    transitioning: {
      'id': 'action.record.transition.delete.transitioning',
      'defaultMessage': '{hasTitle, select, yes {Deleting {title}\u2026} other {Deleting record\u2026}}'
    },
    errorTransitioning: {
      'id': 'action.record.transition.delete.errorTransitioning',
      'defaultMessage': '{hasTitle, select, yes {Error deleting {title}: {error}} other {Error deleting record: {error}}}'
    },
    transitioned: {
      'id': 'action.record.transition.delete.transitioned',
      'defaultMessage': '{hasTitle, select, yes {Deleted {title}} other {Deleted record}}'
    }
  }),
  lock: (0, _reactIntl.defineMessages)({
    transitioning: {
      'id': 'action.record.transition.lock.transitioning',
      'defaultMessage': '{hasTitle, select, yes {Locking {title}\u2026} other {Locking record\u2026}}'
    },
    errorTransitioning: {
      'id': 'action.record.transition.lock.errorTransitioning',
      'defaultMessage': '{hasTitle, select, yes {Error locking {title}: {error}} other {Error locking record: {error}}}'
    },
    transitioned: {
      'id': 'action.record.transition.lock.transitioned',
      'defaultMessage': '{hasTitle, select, yes {Locked {title}} other {Locked record}}'
    }
  })
};

const CLEAR_RECORD = exports.CLEAR_RECORD = 'CLEAR_RECORD';
const CREATE_NEW_RECORD = exports.CREATE_NEW_RECORD = 'CREATE_NEW_RECORD';
const CREATE_NEW_SUBRECORD = exports.CREATE_NEW_SUBRECORD = 'CREATE_NEW_SUBRECORD';
const FIELD_COMPUTE_FULFILLED = exports.FIELD_COMPUTE_FULFILLED = 'FIELD_COMPUTE_FULFILLED';
const FIELD_COMPUTE_REJECTED = exports.FIELD_COMPUTE_REJECTED = 'FIELD_COMPUTE_REJECTED';
const RECORD_CREATED = exports.RECORD_CREATED = 'RECORD_CREATED';
const SUBRECORD_CREATED = exports.SUBRECORD_CREATED = 'SUBRECORD_CREATED';
const RECORD_DELETE_STARTED = exports.RECORD_DELETE_STARTED = 'RECORD_DELETE_STARTED';
const RECORD_DELETE_FULFILLED = exports.RECORD_DELETE_FULFILLED = 'RECORD_DELETE_FULFILLED';
const RECORD_DELETE_REJECTED = exports.RECORD_DELETE_REJECTED = 'RECORD_DELETE_REJECTED';
const RECORD_READ_STARTED = exports.RECORD_READ_STARTED = 'RECORD_READ_STARTED';
const RECORD_READ_FULFILLED = exports.RECORD_READ_FULFILLED = 'RECORD_READ_FULFILLED';
const RECORD_READ_REJECTED = exports.RECORD_READ_REJECTED = 'RECORD_READ_REJECTED';
const SUBRECORD_READ_FULFILLED = exports.SUBRECORD_READ_FULFILLED = 'SUBRECORD_READ_FULFILLED';
const RECORD_SAVE_STARTED = exports.RECORD_SAVE_STARTED = 'RECORD_SAVE_STARTED';
const RECORD_SAVE_FULFILLED = exports.RECORD_SAVE_FULFILLED = 'RECORD_SAVE_FULFILLED';
const RECORD_SAVE_REJECTED = exports.RECORD_SAVE_REJECTED = 'RECORD_SAVE_REJECTED';
const RECORD_TRANSITION_STARTED = exports.RECORD_TRANSITION_STARTED = 'RECORD_TRANSITION_STARTED';
const RECORD_TRANSITION_FULFILLED = exports.RECORD_TRANSITION_FULFILLED = 'RECORD_TRANSITION_FULFILLED';
const RECORD_TRANSITION_REJECTED = exports.RECORD_TRANSITION_REJECTED = 'RECORD_TRANSITION_REJECTED';
const ADD_FIELD_INSTANCE = exports.ADD_FIELD_INSTANCE = 'ADD_FIELD_INSTANCE';
const SORT_FIELD_INSTANCES = exports.SORT_FIELD_INSTANCES = 'SORT_FIELD_INSTANCES';
const DELETE_FIELD_VALUE = exports.DELETE_FIELD_VALUE = 'DELETE_FIELD_VALUE';
const MOVE_FIELD_VALUE = exports.MOVE_FIELD_VALUE = 'MOVE_FIELD_VALUE';
const SET_FIELD_VALUE = exports.SET_FIELD_VALUE = 'SET_FIELD_VALUE';
const REVERT_RECORD = exports.REVERT_RECORD = 'REVERT_RECORD';
const VALIDATION_FAILED = exports.VALIDATION_FAILED = 'VALIDATION_FAILED';
const VALIDATION_PASSED = exports.VALIDATION_PASSED = 'VALIDATION_PASSED';
const DETACH_SUBRECORD = exports.DETACH_SUBRECORD = 'DETACH_SUBRECORD';

const computeFieldValue = exports.computeFieldValue = (recordTypeConfig, csid, path, value) => (dispatch, getState) => {
  const fieldDescriptor = (0, _get2.default)(recordTypeConfig, ['fields', ...(0, _configHelpers.dataPathToFieldDescriptorPath)(path)]);
  const recordData = (0, _reducers.getRecordData)(getState(), csid);
  const subrecordData = (0, _reducers.getSubrecordData)(getState(), csid);

  return (0, _recordDataHelpers.computeField)(value, [], recordData, subrecordData, fieldDescriptor, true).then(computedValue => {
    if (typeof computedValue !== 'undefined') {
      dispatch({
        type: FIELD_COMPUTE_FULFILLED,
        payload: computedValue,
        meta: {
          csid,
          path
        }
      });
    }
  }).catch(error => {
    dispatch({
      type: FIELD_COMPUTE_REJECTED,
      payload: {
        code: _errorCodes.ERR_COMPUTE,
        error
      },
      meta: {
        csid,
        path
      }
    });

    // TODO: Show an error notification?
  });
};

const computeRecordData = exports.computeRecordData = (recordTypeConfig, csid) => (dispatch, getState) => {
  const recordData = (0, _reducers.getRecordData)(getState(), csid);

  return dispatch(computeFieldValue(recordTypeConfig, csid, [], recordData));
};

const validateFieldValue = exports.validateFieldValue = (recordTypeConfig, csid, path, value) => (dispatch, getState) => {
  const fieldDescriptor = (0, _get2.default)(recordTypeConfig, ['fields', ...(0, _configHelpers.dataPathToFieldDescriptorPath)(path)]);
  const recordData = (0, _reducers.getRecordData)(getState(), csid);

  return (0, _recordDataHelpers.validateField)(value, [], recordData, fieldDescriptor, true).then(errors => {
    if (errors) {
      dispatch({
        type: VALIDATION_FAILED,
        payload: errors,
        meta: {
          csid,
          path
        }
      });

      dispatch((0, _notification.showValidationNotification)(recordTypeConfig.name, csid));
    } else {
      dispatch({
        type: VALIDATION_PASSED,
        meta: {
          csid,
          path
        }
      });

      dispatch((0, _notification.removeValidationNotification)());
    }
  });
};

const validateRecordData = exports.validateRecordData = (recordTypeConfig, csid) => (dispatch, getState) => {
  const recordData = (0, _reducers.getRecordData)(getState(), csid);

  return dispatch(validateFieldValue(recordTypeConfig, csid, [], recordData));
};

const initializeSubrecords = (config, recordTypeConfig, vocabularyConfig, csid) => (dispatch, getState) => {
  const subrecords = recordTypeConfig.subrecords;


  if (!subrecords) {
    return Promise.resolve();
  }

  const data = (0, _reducers.getRecordData)(getState(), csid);

  return Promise.all(Object.entries(subrecords).map(entry => {
    var _entry = _slicedToArray(entry, 2);

    const subrecordName = _entry[0],
          subrecordConfig = _entry[1];
    const csidField = subrecordConfig.csidField,
          subresource = subrecordConfig.subresource,
          subrecordType = subrecordConfig.recordType,
          subrecordVocabulary = subrecordConfig.vocabulary;


    let subrecordCsidPromise = null;

    if (csidField) {
      subrecordCsidPromise = Promise.resolve((0, _recordDataHelpers.deepGet)(data, csidField));
    } else if (subresource) {
      const searchDescriptor = _immutable2.default.fromJS({
        csid,
        subresource,
        recordType: recordTypeConfig.name,
        vocabulary: vocabularyConfig.name,
        searchQuery: {
          // Set page size to 1, and page to 0. This assumes we'll only ever care about the first
          // result.
          p: 0,
          size: 1
        }
      });

      const searchName = (0, _searchHelpers.getSubrecordSearchName)(csid, subrecordName);

      const listType = subresource ? (0, _get2.default)(config, ['subresources', subresource, 'listType']) : null;

      subrecordCsidPromise = dispatch((0, _search.search)(config, searchName, searchDescriptor, listType)).then(() => {
        const result = (0, _reducers.getSearchResult)(getState(), searchName, searchDescriptor);

        let subrecordCsid;

        if (result) {
          // Read the csid of the first item.

          const firstItem = (0, _searchHelpers.getFirstItem)(config, result, listType);

          if (firstItem) {
            subrecordCsid = firstItem.get('csid');
          }
        }

        return subrecordCsid;
      });
    }

    if (subrecordCsidPromise) {
      return subrecordCsidPromise.then(subrecordCsid => {
        const subrecordTypeConfig = (0, _get2.default)(config, ['recordTypes', subrecordType]);

        const subrecordVocabularyConfig = (0, _get2.default)(subrecordTypeConfig, ['vocabularies', subrecordVocabulary]);

        if (subrecordCsid) {
          return (
            // eslint-disable-next-line no-use-before-define
            dispatch(readRecord(config, subrecordTypeConfig, subrecordVocabularyConfig, subrecordCsid)).then(() => dispatch({
              type: SUBRECORD_READ_FULFILLED,
              meta: {
                csid,
                subrecordCsid,
                subrecordName
              }
            }))
          );
        }

        // No existing subrecord. Create one as a default.

        // eslint-disable-next-line no-use-before-define
        return dispatch(createNewSubrecord(config, csid, csidField, subrecordName, subrecordTypeConfig, subrecordVocabularyConfig, undefined, true));
      });
    }

    return Promise.resolve();
  }));
};

const doRead = (recordTypeConfig, vocabularyConfig, csid) => {
  var _recordTypeConfig$ser = recordTypeConfig.serviceConfig;
  const serviceType = _recordTypeConfig$ser.serviceType,
        recordServicePath = _recordTypeConfig$ser.servicePath;


  const vocabularyServicePath = vocabularyConfig ? vocabularyConfig.serviceConfig.servicePath : null;

  const pathParts = [recordServicePath];

  if (vocabularyServicePath) {
    pathParts.push(vocabularyServicePath);
    pathParts.push('items');
  }

  pathParts.push(csid);

  const path = pathParts.join('/');

  const requestConfig = {
    params: {
      wf_deleted: false
    }
  };

  if (serviceType === 'authority' || serviceType === 'object') {
    requestConfig.params.showRelations = true;
  }

  if (recordTypeConfig.requestConfig) {
    (0, _merge2.default)(requestConfig, recordTypeConfig.requestConfig('read'));
  }

  return (0, _cspace2.default)().read(path, requestConfig);
};

const readRecord = exports.readRecord = (config, recordTypeConfig, vocabularyConfig, csid) => (dispatch, getState) => {
  if ((0, _reducers.isRecordReadPending)(getState(), csid) || (0, _reducers.getRecordData)(getState(), csid)) {
    // We already have data for this record, or a request is already pending. Do nothing.

    return Promise.resolve();
  }

  dispatch({
    type: RECORD_READ_STARTED,
    meta: {
      recordTypeConfig,
      csid
    }
  });

  return doRead(recordTypeConfig, vocabularyConfig, csid).then(response => dispatch({
    type: RECORD_READ_FULFILLED,
    payload: response,
    meta: {
      config,
      recordTypeConfig,
      csid
    }
  })).then(() => dispatch(initializeSubrecords(config, recordTypeConfig, vocabularyConfig, csid))).catch(error => dispatch({
    type: RECORD_READ_REJECTED,
    payload: {
      code: _errorCodes.ERR_API,
      error
    },
    meta: {
      recordTypeConfig,
      csid
    }
  }));
};

const createNewRecord = exports.createNewRecord = (config, recordTypeConfig, vocabularyConfig, cloneCsid) => (dispatch, getState) => {
  let readClone;

  if (cloneCsid) {
    const data = (0, _reducers.getRecordData)(getState(), cloneCsid);

    if (!data) {
      // We don't have data for the record to be cloned. Read it first.

      readClone = dispatch(readRecord(config, recordTypeConfig, vocabularyConfig, cloneCsid));
    }
  }

  if (!readClone) {
    // There's nothing to clone, or we already have the record data to be cloned. Perform an
    // async noop, so this function will be consistently async.

    readClone = new Promise(resolve => {
      window.setTimeout(() => {
        resolve();
      }, 0);
    });
  }

  return readClone.then(() => dispatch({
    type: CREATE_NEW_RECORD,
    meta: {
      config,
      recordTypeConfig,
      cloneCsid
    }
  }));
};

const createNewSubrecord = exports.createNewSubrecord = (config, csid, csidField, subrecordName, subrecordTypeConfig, subrecordVocabularyConfig, cloneCsid, isDefault) => (dispatch, getState) => {
  let readClone;

  if (cloneCsid) {
    const data = (0, _reducers.getRecordData)(getState(), cloneCsid);

    if (!data) {
      // We don't have data for the record to be cloned. Read it first.

      readClone = dispatch(readRecord(config, subrecordTypeConfig, subrecordVocabularyConfig, cloneCsid));
    }
  }

  if (!readClone) {
    // There's nothing to clone, or we already have the record data to be cloned. Perform an
    // async noop, so this function will be consistently async.

    readClone = new Promise(resolve => {
      window.setTimeout(() => {
        resolve();
      }, 0);
    });
  }

  return readClone.then(() => dispatch({
    type: CREATE_NEW_SUBRECORD,
    meta: {
      config,
      csid,
      csidField,
      subrecordName,
      subrecordTypeConfig,
      cloneCsid,
      isDefault
    }
  }));
};

const saveSubrecords = (config, recordTypeConfig, vocabularyConfig, csid, saveStage) => (dispatch, getState) => {
  const subrecords = recordTypeConfig.subrecords;


  if (!subrecords) {
    return Promise.resolve();
  }

  return Promise.all(Object.entries(subrecords).filter(entry => entry[1].saveStage === saveStage).map(entry => {
    var _entry2 = _slicedToArray(entry, 2);

    const subrecordName = _entry2[0],
          subrecordConfig = _entry2[1];

    const subrecordCsid = (0, _reducers.getRecordSubrecordCsid)(getState(), csid, subrecordName);

    if (subrecordCsid) {
      const csidField = subrecordConfig.csidField,
            saveCondition = subrecordConfig.saveCondition,
            subresource = subrecordConfig.subresource;


      if (saveCondition) {
        const subrecordData = (0, _reducers.getRecordData)(getState(), subrecordCsid);

        if (!saveCondition(subrecordData)) {
          return Promise.resolve();
        }
      }

      if (csidField) {
        const subrecordTypeConfig = (0, _get2.default)(config, ['recordTypes', subrecordConfig.recordType]);

        const subrecordVocabularyConfig = (0, _get2.default)(subrecordTypeConfig, ['vocabularies', subrecordConfig.vocabulary]);

        // eslint-disable-next-line no-use-before-define
        return dispatch(saveRecord(config, subrecordTypeConfig, subrecordVocabularyConfig, subrecordCsid, undefined, undefined, undefined, newRecordCsid => {
          dispatch({
            type: SUBRECORD_CREATED,
            meta: {
              csid,
              csidField,
              subrecordName,
              subrecordCsid: newRecordCsid
            }
          });
        }, false));
      }

      if (subresource) {
        const searchName = (0, _searchHelpers.getSubrecordSearchName)(csid, subrecordName);
        const subrecordSubresourceConfig = (0, _get2.default)(config, ['subresources', subresource]);

        if (subrecordSubresourceConfig) {
          // eslint-disable-next-line no-use-before-define
          return dispatch(saveRecord(config, recordTypeConfig, vocabularyConfig, csid, subrecordSubresourceConfig, subrecordCsid, undefined, newRecordCsid => {
            dispatch({
              type: SUBRECORD_CREATED,
              meta: {
                csid,
                searchName,
                subrecordName,
                subrecordCsid: newRecordCsid
              }
            });
          }, false));
        }
      }
    }

    return Promise.resolve();
  }));
};

const saveRecord = exports.saveRecord = (config, recordTypeConfig, vocabularyConfig, csid, subresourceConfig, subresourceCsid, relatedSubjectCsid, onRecordCreated, showNotifications = true) => (dispatch, getState, intl) => {
  let currentRecordTypeConfig;
  let currentVocabularyConfig;
  let currentCsid;

  if (subresourceConfig) {
    currentRecordTypeConfig = (0, _get2.default)(config, ['recordTypes', subresourceConfig.recordType]);

    currentVocabularyConfig = (0, _get2.default)(currentRecordTypeConfig, ['vocabularies', subresourceConfig.vocabulary]);

    currentCsid = subresourceCsid;
  } else {
    currentRecordTypeConfig = recordTypeConfig;
    currentVocabularyConfig = vocabularyConfig;
    currentCsid = csid;
  }

  return dispatch(computeRecordData(currentRecordTypeConfig, currentCsid)).then(() => dispatch(validateRecordData(currentRecordTypeConfig, currentCsid))).then(() => {
    if ((0, _reducers.getRecordValidationErrors)(getState(), currentCsid)) {
      return null;
    }

    dispatch({
      type: RECORD_SAVE_STARTED,
      meta: {
        csid: currentCsid
      }
    });

    const title = currentRecordTypeConfig.title ? currentRecordTypeConfig.title((0, _reducers.getRecordData)(getState(), currentCsid), { config, intl }) : null;

    const notificationID = (0, _notificationHelpers2.default)();

    if (showNotifications) {
      dispatch((0, _notification.showNotification)({
        items: [{
          message: saveMessages.saving,
          values: {
            title,
            hasTitle: title ? 'yes' : ''
          }
        }],
        date: new Date(),
        status: _notificationStatusCodes.STATUS_PENDING
      }, notificationID));
    }

    return dispatch(saveSubrecords(config, currentRecordTypeConfig, currentVocabularyConfig, currentCsid, 'before')).then(() => {
      const data = (0, _reducers.getRecordData)(getState(), currentCsid);
      const isExisting = (0, _recordDataHelpers.isExistingRecord)(data);

      const recordServicePath = (0, _get2.default)(recordTypeConfig, ['serviceConfig', 'servicePath']);
      const vocabularyServicePath = (0, _get2.default)(vocabularyConfig, ['serviceConfig', 'servicePath']);

      const pathParts = [recordServicePath];

      if (vocabularyServicePath) {
        pathParts.push(vocabularyServicePath);
        pathParts.push('items');
      }

      if (subresourceConfig) {
        if (csid) {
          pathParts.push(csid);
        }

        const subresourceServicePath = (0, _get2.default)(subresourceConfig, ['serviceConfig', 'servicePath']);

        if (subresourceServicePath) {
          pathParts.push(subresourceServicePath);
        }
      }

      if (isExisting && currentCsid) {
        pathParts.push(currentCsid);
      }

      const path = pathParts.join('/');

      const requestConfig = {
        data: (0, _recordDataHelpers.prepareForSending)(data, currentRecordTypeConfig).toJS()
      };

      if (recordTypeConfig.requestConfig) {
        (0, _merge2.default)(requestConfig, recordTypeConfig.requestConfig('save', data));
      }

      if (isExisting) {
        return (0, _cspace2.default)().update(path, requestConfig).then(response => dispatch(saveSubrecords(config, currentRecordTypeConfig, currentVocabularyConfig, currentCsid, 'after')).then(() => {
          if (showNotifications) {
            dispatch((0, _notification.showNotification)({
              items: [{
                message: saveMessages.saved,
                values: {
                  title,
                  hasTitle: title ? 'yes' : ''
                }
              }],
              date: new Date(),
              status: _notificationStatusCodes.STATUS_SUCCESS,
              autoClose: true
            }, notificationID));
          }

          dispatch({
            type: RECORD_SAVE_FULFILLED,
            payload: response,
            meta: {
              relatedSubjectCsid,
              recordTypeConfig: currentRecordTypeConfig,
              csid: currentCsid,
              recordPagePrimaryCsid: (0, _reducers.getRecordPagePrimaryCsid)(getState())
            }
          });
        }).then(() => dispatch(initializeSubrecords(config, currentRecordTypeConfig, currentVocabularyConfig, currentCsid))).then(() => currentCsid).catch(error => {
          throw error;
        })).catch(error => Promise.reject({
          code: _errorCodes.ERR_API,
          error
        }));
      }

      return (0, _cspace2.default)().create(path, requestConfig).then(response => {
        if (response.status === 201 && response.headers.location) {
          const location = response.headers.location;
          const newRecordCsid = location.substring(location.lastIndexOf('/') + 1);

          dispatch({
            type: RECORD_CREATED,
            meta: {
              currentCsid,
              newRecordCsid,
              recordTypeConfig: currentRecordTypeConfig
            }
          });

          return dispatch(saveSubrecords(config, currentRecordTypeConfig, currentVocabularyConfig, newRecordCsid, 'after')).then(() => doRead(currentRecordTypeConfig, currentVocabularyConfig, newRecordCsid)).then(readResponse => {
            if (showNotifications) {
              dispatch((0, _notification.showNotification)({
                items: [{
                  message: saveMessages.saved,
                  values: {
                    title,
                    hasTitle: title ? 'yes' : ''
                  }
                }],
                date: new Date(),
                status: _notificationStatusCodes.STATUS_SUCCESS,
                autoClose: true
              }, notificationID));
            }

            return dispatch({
              type: RECORD_SAVE_FULFILLED,
              payload: readResponse,
              meta: {
                relatedSubjectCsid,
                recordTypeConfig: currentRecordTypeConfig,
                csid: newRecordCsid,
                recordPagePrimaryCsid: (0, _reducers.getRecordPagePrimaryCsid)(getState())
              }
            });
          }).then(() => dispatch(initializeSubrecords(config, currentRecordTypeConfig, currentVocabularyConfig, newRecordCsid))).then(() => Promise.resolve(onRecordCreated ? onRecordCreated(newRecordCsid) : null)).then(() => newRecordCsid);
        }

        const error = new Error('Expected response with status 201 and a location header');
        error.response = response;

        throw error;
      }).catch(error => Promise.reject({
        code: _errorCodes.ERR_API,
        error
      }));
    }).catch(error => {
      if (showNotifications) {
        dispatch((0, _notification.showNotification)({
          items: [{
            message: saveMessages.errorSaving,
            values: {
              title,
              hasTitle: title ? 'yes' : '',
              error: (0, _getErrorDescription2.default)(error)
            }
          }],
          date: new Date(),
          status: _notificationStatusCodes.STATUS_ERROR
        }, notificationID));
      }

      dispatch({
        type: RECORD_SAVE_REJECTED,
        payload: error,
        meta: {
          csid: currentCsid
        }
      });

      throw error;
    });
  });
};

const addFieldInstance = exports.addFieldInstance = (recordTypeConfig, csid, path, position) => dispatch => {
  dispatch({
    type: ADD_FIELD_INSTANCE,
    meta: {
      csid,
      path,
      position,
      recordTypeConfig
    }
  });

  return dispatch(computeRecordData(recordTypeConfig, csid)).then(() => dispatch(validateRecordData(recordTypeConfig, csid)));
};

const sortFieldInstances = exports.sortFieldInstances = (recordTypeConfig, csid, path, byField) => dispatch => {
  dispatch({
    type: SORT_FIELD_INSTANCES,
    meta: {
      csid,
      path,
      byField,
      recordTypeConfig
    }
  });

  return dispatch(computeRecordData(recordTypeConfig, csid)).then(() => dispatch(validateRecordData(recordTypeConfig, csid)));
};

const deleteFieldValue = exports.deleteFieldValue = (recordTypeConfig, csid, path) => dispatch => {
  dispatch({
    type: DELETE_FIELD_VALUE,
    meta: {
      csid,
      path
    }
  });

  return dispatch(computeRecordData(recordTypeConfig, csid)).then(() => dispatch(validateRecordData(recordTypeConfig, csid)));
};

const moveFieldValue = exports.moveFieldValue = (recordTypeConfig, csid, path, newPosition) => dispatch => {
  dispatch({
    type: MOVE_FIELD_VALUE,
    meta: {
      csid,
      path,
      newPosition
    }
  });

  return dispatch(computeRecordData(recordTypeConfig, csid)).then(() => dispatch(validateRecordData(recordTypeConfig, csid)));
};

const setFieldValue = exports.setFieldValue = (recordTypeConfig, csid, path, value) => dispatch => {
  dispatch({
    type: SET_FIELD_VALUE,
    payload: value,
    meta: {
      csid,
      path
    }
  });

  return dispatch(computeRecordData(recordTypeConfig, csid)).then(() => dispatch(validateRecordData(recordTypeConfig, csid)));
};

const revertRecord = exports.revertRecord = (recordTypeConfig, csid) => dispatch => {
  dispatch({
    type: REVERT_RECORD,
    meta: {
      recordTypeConfig,
      csid
    }
  });

  // Clear validation errors. Could maybe revalidate here, but to be consistent, we should also
  // validate when a record is first loaded.

  dispatch({
    type: VALIDATION_PASSED,
    meta: {
      csid,
      path: []
    }
  });

  dispatch((0, _notification.removeValidationNotification)());
  dispatch((0, _notification.removeNotification)(_HierarchyReparentNotifier2.default.notificationID));
};

const deleteRecord = exports.deleteRecord = (config, recordTypeConfig, vocabularyConfig, csid, relatedSubjectCsid) => (dispatch, getState, intl) => {
  const data = (0, _reducers.getRecordData)(getState(), csid);
  const title = recordTypeConfig.title(data, { config, intl });
  const notificationID = (0, _notificationHelpers2.default)();

  dispatch((0, _notification.showNotification)({
    items: [{
      message: deleteMessages.deleting,
      values: {
        title,
        hasTitle: title ? 'yes' : ''
      }
    }],
    date: new Date(),
    status: _notificationStatusCodes.STATUS_PENDING
  }, notificationID));

  dispatch({
    type: RECORD_DELETE_STARTED,
    meta: {
      recordTypeConfig,
      csid
    }
  });

  const recordServicePath = recordTypeConfig.serviceConfig.servicePath;

  const vocabularyServicePath = vocabularyConfig ? vocabularyConfig.serviceConfig.servicePath : null;

  const pathParts = [recordServicePath];

  if (vocabularyServicePath) {
    pathParts.push(vocabularyServicePath);
    pathParts.push('items');
  }

  if (csid) {
    pathParts.push(csid);
  }

  const path = pathParts.join('/');

  return (0, _cspace2.default)().delete(path).then(response => {
    dispatch((0, _notification.showNotification)({
      items: [{
        message: deleteMessages.deleted,
        values: {
          title,
          hasTitle: title ? 'yes' : ''
        }
      }],
      date: new Date(),
      status: _notificationStatusCodes.STATUS_SUCCESS,
      autoClose: true
    }, notificationID));

    return dispatch({
      type: RECORD_DELETE_FULFILLED,
      payload: response,
      meta: {
        recordTypeConfig,
        csid,
        relatedSubjectCsid
      }
    });
  }).catch(error => {
    dispatch((0, _notification.showNotification)({
      items: [{
        message: deleteMessages.errorDeleting,
        values: {
          title,
          hasTitle: title ? 'yes' : '',
          error: (0, _getErrorDescription2.default)(error)
        }
      }],
      date: new Date(),
      status: _notificationStatusCodes.STATUS_ERROR
    }, notificationID));

    return dispatch({
      type: RECORD_DELETE_REJECTED,
      payload: {
        code: _errorCodes.ERR_API,
        error
      },
      meta: {
        recordTypeConfig,
        csid
      }
    });
  });
};

const transitionRecord = exports.transitionRecord = (config, recordTypeConfig, vocabularyConfig, csid, transitionName, relatedSubjectCsid) => (dispatch, getState, intl) => {
  const data = (0, _reducers.getRecordData)(getState(), csid);
  const title = recordTypeConfig.title(data, { config, intl });
  const notificationID = (0, _notificationHelpers2.default)();

  const messages = transitionMessages[transitionName];

  if (messages) {
    dispatch((0, _notification.showNotification)({
      items: [{
        message: messages.transitioning,
        values: {
          title,
          hasTitle: title ? 'yes' : ''
        }
      }],
      date: new Date(),
      status: _notificationStatusCodes.STATUS_PENDING
    }, notificationID));
  }

  dispatch({
    type: RECORD_TRANSITION_STARTED,
    meta: {
      recordTypeConfig,
      csid,
      transitionName
    }
  });

  const recordServicePath = recordTypeConfig.serviceConfig.servicePath;

  const vocabularyServicePath = vocabularyConfig ? vocabularyConfig.serviceConfig.servicePath : null;

  const pathParts = [recordServicePath];

  if (vocabularyServicePath) {
    pathParts.push(vocabularyServicePath);
    pathParts.push('items');
  }

  if (csid) {
    pathParts.push(csid);
  }

  pathParts.push('workflow');
  pathParts.push(transitionName);

  const path = pathParts.join('/');

  return (0, _cspace2.default)().update(path).then(response => transitionName === 'delete' ? response
  // For all transitions other than delete, re-read the record to obtain the new workflow
  // state.
  : doRead(recordTypeConfig, vocabularyConfig, csid)).then(response => {
    if (messages) {
      dispatch((0, _notification.showNotification)({
        items: [{
          message: messages.transitioned,
          values: {
            transitionName,
            title,
            hasTitle: title ? 'yes' : ''
          }
        }],
        date: new Date(),
        status: _notificationStatusCodes.STATUS_SUCCESS,
        autoClose: true
      }, notificationID));
    }

    return dispatch({
      type: RECORD_TRANSITION_FULFILLED,
      payload: response,
      meta: {
        recordTypeConfig,
        csid,
        transitionName,
        relatedSubjectCsid,
        // We don't get data back from the transition request. Rather than making a separate
        // request to get the actual updated time of the record, just make it the current time.
        updatedTimestamp: new Date().toISOString()
      }
    });
  }).catch(error => {
    if (messages) {
      dispatch((0, _notification.showNotification)({
        items: [{
          message: messages.errorTransitioning,
          values: {
            transitionName,
            title,
            hasTitle: title ? 'yes' : '',
            error: (0, _getErrorDescription2.default)(error)
          }
        }],
        date: new Date(),
        status: _notificationStatusCodes.STATUS_ERROR
      }, notificationID));
    }

    return dispatch({
      type: RECORD_TRANSITION_REJECTED,
      payload: {
        code: _errorCodes.ERR_API,
        error
      },
      meta: {
        recordTypeConfig,
        csid,
        transitionName
      }
    });
  });
};

const saveRecordWithTransition = exports.saveRecordWithTransition = (config, recordTypeConfig, vocabularyConfig, csid, subresourceConfig, subresourceCsid, relatedSubjectCsid, transitionName, onRecordCreated, showNotifications = true) => dispatch => dispatch(saveRecord(config, recordTypeConfig, vocabularyConfig, csid, subresourceConfig, subresourceCsid, relatedSubjectCsid, onRecordCreated, showNotifications)).then(savedCsid => dispatch(transitionRecord(config, recordTypeConfig, vocabularyConfig, savedCsid, transitionName, relatedSubjectCsid)));

const detachSubrecord = exports.detachSubrecord = (config, csid, csidField, subrecordName, subrecordTypeConfig) => ({
  type: DETACH_SUBRECORD,
  meta: {
    config,
    csid,
    csidField,
    subrecordName,
    subrecordTypeConfig
  }
});

const clearRecord = exports.clearRecord = csid => ({
  type: CLEAR_RECORD,
  meta: {
    csid
  }
});