'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.isModifiedExceptPart = exports.isModified = exports.isReadVocabularyItemRefsPending = exports.isSavePending = exports.isReadPending = exports.getValidationErrors = exports.getNewSubrecordCsid = exports.getNewData = exports.getRelationUpdatedTimestamp = exports.getSubrecordData = exports.getSubrecordCsid = exports.getError = exports.getData = 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"); } }; }();

var _immutable = require('immutable');

var _immutable2 = _interopRequireDefault(_immutable);

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

var _get2 = _interopRequireDefault(_get);

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

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

var _login = require('../actions/login');

var _logout = require('../actions/logout');

var _record = require('../actions/record');

var _relation = require('../actions/relation');

var _idGenerator = require('../actions/idGenerator');

var _vocabulary = require('../actions/vocabulary');

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

const BASE_NEW_RECORD_KEY = '';

const unsavedRecordKey = subrecordName => subrecordName ? `${BASE_NEW_RECORD_KEY}/${subrecordName}` : BASE_NEW_RECORD_KEY;

const getCurrentData = (state, csid) => state.getIn([csid, 'data', 'current']);
const setCurrentData = (state, csid, data) => state.setIn([csid, 'data', 'current'], data);

const getBaselineData = (state, csid) => state.getIn([csid, 'data', 'baseline']);
const setBaselineData = (state, csid, data) => state.setIn([csid, 'data', 'baseline'], data);

const clear = (state, csid) => {
  const recordState = state.get(csid);

  if (!recordState) {
    return state;
  }

  let nextState = state;

  const subrecord = recordState.get('subrecord');

  if (subrecord) {
    nextState = subrecord.reduce((reducedState, subrecordCsid) => clear(reducedState, subrecordCsid), nextState);
  }

  return nextState.delete(csid);
};

const clearAll = state => state.clear();

const clearFiltered = (state, filter) => {
  let nextState = state;

  state.filter(filter).forEach((recordState, csid) => {
    nextState = clear(nextState, csid);
  });

  return nextState;
};

const addFieldInstance = (state, action) => {
  var _action$meta = action.meta;
  const csid = _action$meta.csid,
        path = _action$meta.path,
        position = _action$meta.position,
        recordTypeConfig = _action$meta.recordTypeConfig;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const value = (0, _recordDataHelpers.deepGet)(data, path);
  const list = _immutable2.default.List.isList(value) ? value : _immutable2.default.List.of(value);

  const fieldDescriptor = (0, _get2.default)(recordTypeConfig, ['fields', ...(0, _configHelpers.dataPathToFieldDescriptorPath)(path)]);
  const defaultData = (0, _recordDataHelpers.initializeChildren)(fieldDescriptor, (0, _recordDataHelpers.applyDefaults)(fieldDescriptor));

  const updatedList = typeof position === 'undefined' || position < 0 || position >= list.size ? list.push(defaultData) : list.insert(position, defaultData);

  const updatedData = (0, _recordDataHelpers.deepSet)(data, path, updatedList);

  return setCurrentData(state, csid, updatedData);
};

const sortFieldInstances = (state, action) => {
  var _action$meta2 = action.meta;
  const csid = _action$meta2.csid,
        path = _action$meta2.path,
        byField = _action$meta2.byField;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const value = (0, _recordDataHelpers.deepGet)(data, path);
  const list = _immutable2.default.List.isList(value) ? value : _immutable2.default.List.of(value);

  // TODO: Check for a custom sort comparator function in field config.
  // For now just use the default.

  const comparator = undefined;

  const sortedList = byField ? list.sortBy(item => item.get(byField), comparator) : list.sort(comparator);

  const updatedData = (0, _recordDataHelpers.deepSet)(data, path, sortedList);

  return setCurrentData(state, csid, updatedData);
};

const doCreateNew = (state, config, recordTypeConfig, cloneCsid, subrecordName) => {
  let data;

  if (cloneCsid) {
    data = (0, _recordDataHelpers.cloneRecordData)(recordTypeConfig, cloneCsid, getCurrentData(state, cloneCsid));
  }

  if (!data) {
    data = (0, _recordDataHelpers.createRecordData)(recordTypeConfig);
  }

  const csid = unsavedRecordKey(subrecordName);

  let nextState = state.delete(csid);

  const subrecords = recordTypeConfig.subrecords;


  if (subrecords) {
    Object.keys(subrecords).forEach(name => {
      const subrecordConfig = subrecords[name];
      const csidField = subrecordConfig.csidField;


      let cloneSubrecordCsid;

      if (csidField) {
        cloneSubrecordCsid = (0, _recordDataHelpers.deepGet)(data, csidField);
      }

      if (!cloneSubrecordCsid) {
        const subrecordType = subrecordConfig.recordType;
        const subrecordTypeConfig = (0, _get2.default)(config, ['recordTypes', subrecordType]);
        const subrecordCsid = state.getIn([cloneCsid, 'subrecord', name]);

        nextState = doCreateNew(nextState, config, subrecordTypeConfig, subrecordCsid, name);

        cloneSubrecordCsid = `${csid}/${name}`;

        if (csidField) {
          data = (0, _recordDataHelpers.deepSet)(data, csidField, cloneSubrecordCsid);
        }
      }

      nextState = nextState.setIn([csid, 'subrecord', name], cloneSubrecordCsid);
    });
  }

  nextState = setBaselineData(nextState, csid, data);
  nextState = setCurrentData(nextState, csid, data);

  return nextState;
};

const createNewRecord = (state, action) => {
  var _action$meta3 = action.meta;
  const config = _action$meta3.config,
        recordTypeConfig = _action$meta3.recordTypeConfig,
        cloneCsid = _action$meta3.cloneCsid;


  return doCreateNew(state, config, recordTypeConfig, cloneCsid);
};

const deleteFieldValue = (state, action) => {
  var _action$meta4 = action.meta;
  const csid = _action$meta4.csid,
        path = _action$meta4.path;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const updatedData = (0, _recordDataHelpers.deepDelete)(data, path);

  return setCurrentData(state, csid, updatedData);
};

const moveFieldValue = (state, action) => {
  var _action$meta5 = action.meta;
  const csid = _action$meta5.csid,
        path = _action$meta5.path,
        newPosition = _action$meta5.newPosition;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const listPath = path.slice(0, -1);
  const oldPosition = path[path.length - 1];

  let list = (0, _recordDataHelpers.deepGet)(data, listPath);

  if (!_immutable2.default.List.isList(list)) {
    return state;
  }

  const value = list.get(oldPosition);

  list = list.delete(oldPosition);
  list = list.insert(newPosition, value);

  const updatedData = (0, _recordDataHelpers.deepSet)(data, listPath, list);

  return setCurrentData(state, csid, updatedData);
};

const setFieldValue = (state, action) => {
  var _action$meta6 = action.meta;
  const csid = _action$meta6.csid,
        path = _action$meta6.path;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const newValue = action.payload;
  const updatedData = (0, _recordDataHelpers.deepSet)(data, path, newValue);
  const nextState = setCurrentData(state, csid, updatedData);

  return nextState;
};

const handleFieldComputeFulfilled = (state, action) => {
  var _action$meta7 = action.meta;
  const csid = _action$meta7.csid,
        path = _action$meta7.path;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  if (path.length === 0) {
    // The entire record was computed.

    const computedData = action.payload;
    const updatedData = data.mergeDeep(computedData);
    const nextState = setCurrentData(state, csid, updatedData);

    return nextState;
  }

  // TODO: Handle an individual field being computed.

  return state;
};

const handleRecordReadFulfilled = (state, action) => {
  var _action$meta8 = action.meta;
  const csid = _action$meta8.csid,
        recordTypeConfig = _action$meta8.recordTypeConfig;


  const data = (0, _recordDataHelpers.normalizeRecordData)(recordTypeConfig, _immutable2.default.fromJS(action.payload.data));

  let nextState = state.deleteIn([csid, 'isReadPending']).deleteIn([csid, 'error']);

  nextState = setBaselineData(nextState, csid, data);
  nextState = setCurrentData(nextState, csid, data);

  return nextState;
};

const handleRecordSaveFulfilled = (state, action) => {
  var _action$meta9 = action.meta;
  const csid = _action$meta9.csid,
        recordTypeConfig = _action$meta9.recordTypeConfig,
        relatedSubjectCsid = _action$meta9.relatedSubjectCsid,
        recordPagePrimaryCsid = _action$meta9.recordPagePrimaryCsid;


  const data = (0, _recordDataHelpers.normalizeRecordData)(recordTypeConfig, _immutable2.default.fromJS(action.payload.data));

  let nextState = state;

  nextState = nextState.deleteIn([csid, 'isSavePending']);

  nextState = setBaselineData(nextState, csid, data);
  nextState = setCurrentData(nextState, csid, data);

  if (relatedSubjectCsid) {
    nextState = nextState.setIn([relatedSubjectCsid, 'relationUpdatedTime'], (0, _recordDataHelpers.getUpdatedTimestamp)(data));
  }

  // Remove all record state besides the record that was just saved, any of its subrecords, and any
  // related record. This isn't strictly necessary, but it's a good time to expire record data,
  // since other records may have fields computed from this record via service layer handlers.

  let persistCsids = [csid, relatedSubjectCsid, BASE_NEW_RECORD_KEY];

  const subrecord = nextState.getIn([csid, 'subrecord']);

  if (subrecord) {
    subrecord.valueSeq().forEach(subrecordCsid => {
      persistCsids.push(subrecordCsid);
    });
  }

  persistCsids = new Set(persistCsids.filter(value => value !== null && typeof value !== 'undefined'));

  nextState = clearFiltered(nextState, (recordState, candidateCsid) => !persistCsids.has(candidateCsid) && !candidateCsid.startsWith(`${BASE_NEW_RECORD_KEY}/`) && // Don't clear unsaved subrecord data
  !recordState.get('isSavePending') && // Don't clear records that are being saved
  candidateCsid !== recordPagePrimaryCsid // Don't clear the primary record data
  );

  return nextState;
};

const revertRecord = (state, action) => {
  var _action$meta10 = action.meta;
  const recordTypeConfig = _action$meta10.recordTypeConfig,
        csid = _action$meta10.csid;


  const baselineData = getBaselineData(state, csid);

  let nextState = setCurrentData(state, csid, baselineData);

  // Revert subrecords.

  const subrecords = nextState.getIn([csid, 'subrecord']);

  if (subrecords) {
    subrecords.forEach(subrecordCsid => {
      nextState = revertRecord(nextState, {
        meta: {
          csid: subrecordCsid
        }
      });
    });
  }

  // Revert any cached subrecord csids that originated from fields in the record.

  const configuredSubrecords = (0, _get2.default)(recordTypeConfig, 'subrecords');

  if (configuredSubrecords) {
    Object.entries(configuredSubrecords).forEach(entry => {
      var _entry = _slicedToArray(entry, 2);

      const subrecordName = _entry[0],
            subrecordConfig = _entry[1];
      const csidField = subrecordConfig.csidField;


      if (csidField) {
        const revertedSubrecordCsid = (0, _recordDataHelpers.deepGet)(baselineData, csidField);

        nextState = nextState.setIn([csid, 'subrecord', subrecordName], revertedSubrecordCsid);

        // Revert the reattached subrecord.

        nextState = revertRecord(nextState, {
          meta: {
            csid: revertedSubrecordCsid
          }
        });
      }
    });
  }

  return nextState;
};

const handleSubrecordCreated = (state, action) => {
  var _action$meta11 = action.meta;
  const csid = _action$meta11.csid,
        csidField = _action$meta11.csidField,
        subrecordName = _action$meta11.subrecordName,
        subrecordCsid = _action$meta11.subrecordCsid,
        isDefault = _action$meta11.isDefault;


  let nextState = state.setIn([csid, 'subrecord', subrecordName], subrecordCsid);

  if (csidField) {
    const currentData = getCurrentData(state, csid);
    const baselineData = getBaselineData(state, csid);

    if (isDefault && currentData === baselineData) {
      // This subrecord was created as the default for the container. Set the csid field in both
      // the baseline and current data, so it won't be considered a modification.

      const updatedData = (0, _recordDataHelpers.deepSet)(baselineData, csidField, subrecordCsid);

      nextState = setBaselineData(nextState, csid, updatedData);
      nextState = setCurrentData(nextState, csid, updatedData);
    } else {
      const updatedData = (0, _recordDataHelpers.deepSet)(currentData, csidField, subrecordCsid);

      nextState = setCurrentData(nextState, csid, updatedData);
    }
  }

  return nextState;
};

const createNewSubrecord = (state, action) => {
  var _action$meta12 = action.meta;
  const config = _action$meta12.config,
        csid = _action$meta12.csid,
        csidField = _action$meta12.csidField,
        subrecordName = _action$meta12.subrecordName,
        subrecordTypeConfig = _action$meta12.subrecordTypeConfig,
        cloneCsid = _action$meta12.cloneCsid,
        isDefault = _action$meta12.isDefault;


  let nextState = doCreateNew(state, config, subrecordTypeConfig, cloneCsid, subrecordName);

  const subrecordCsid = unsavedRecordKey(subrecordName);

  nextState = handleSubrecordCreated(nextState, {
    meta: {
      csid,
      csidField,
      subrecordName,
      subrecordCsid,
      isDefault
    }
  });

  return nextState;
};

const handleSubjectRelationsUpdated = (state, action) => {
  // Currently relations are only ever created (not updated), and we don't bother to retrieve
  // the relation record after creation. Technically we should retrieve the new relation
  // record and use its updatedAt value, but that's an extra request. For now the action creator
  // sets the updated time as a meta field.

  var _action$meta13 = action.meta;
  const subject = _action$meta13.subject,
        updatedTime = _action$meta13.updatedTime;


  const subjectCsid = subject.csid;

  if (state.has(subjectCsid)) {
    return state.setIn([subjectCsid, 'relationUpdatedTime'], updatedTime);
  }

  return state;
};

const handleCreateIDFulfilled = (state, action) => {
  var _action$meta14 = action.meta;
  const csid = _action$meta14.csid,
        path = _action$meta14.path;


  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const value = action.payload.data;
  const createdID = typeof value === 'number' ? value.toString() : value;
  const updatedData = (0, _recordDataHelpers.deepSet)(data, path, createdID);
  const nextState = setCurrentData(state, csid, updatedData);

  return nextState;
};

const handleValidationFailed = (state, action) => {
  const errors = action.payload;

  var _action$meta15 = action.meta;
  const csid = _action$meta15.csid,
        path = _action$meta15.path;


  return state.setIn([csid, 'validation', ...path], errors);
};

const handleValidationPassed = (state, action) => {
  var _action$meta16 = action.meta;
  const csid = _action$meta16.csid,
        path = _action$meta16.path;


  return state.deleteIn([csid, 'validation', ...path]);
};

const handleTransitionFulfilled = (state, action) => {
  var _action$meta17 = action.meta;
  const csid = _action$meta17.csid,
        transitionName = _action$meta17.transitionName,
        recordPagePrimaryCsid = _action$meta17.recordPagePrimaryCsid,
        recordTypeConfig = _action$meta17.recordTypeConfig,
        relatedSubjectCsid = _action$meta17.relatedSubjectCsid,
        updatedTimestamp = _action$meta17.updatedTimestamp;


  let nextState = state.deleteIn([csid, 'isSavePending']);

  if (transitionName === 'delete') {
    nextState = nextState.delete(csid);

    // Take this opportunity to expire other record data. This isn't strictly necessary, but it's a
    // good time, since the deletion of a record may affect other records via service layer
    // handlers.

    nextState = clearFiltered(nextState, (recordState, candidateCsid) => !recordState.get('isSavePending') && // Don't clear records that are being saved
    candidateCsid !== recordPagePrimaryCsid // Don't clear the primary record data
    );
  } else {
    const newData = (0, _get2.default)(action, ['payload', 'data']);

    if (newData) {
      const data = (0, _recordDataHelpers.normalizeRecordData)(recordTypeConfig, _immutable2.default.fromJS(newData));

      nextState = setBaselineData(nextState, csid, data);
      nextState = setCurrentData(nextState, csid, data);
    }
  }

  if (relatedSubjectCsid) {
    nextState = nextState.setIn([relatedSubjectCsid, 'relationUpdatedTime'], updatedTimestamp);
  }

  return nextState;
};

const handleDeleteFulfilled = (state, action) => {
  var _action$meta18 = action.meta;
  const csid = _action$meta18.csid,
        relatedSubjectCsid = _action$meta18.relatedSubjectCsid,
        updatedTimestamp = _action$meta18.updatedTimestamp;


  let nextState = state.delete(csid);

  if (relatedSubjectCsid) {
    nextState = nextState.setIn([relatedSubjectCsid, 'relationUpdatedTime'], updatedTimestamp);
  }

  return nextState;
};

const detachSubrecord = (state, action) => createNewSubrecord(state, action);

const handleLoginFulfilled = (state, action) => {
  var _action$meta19 = action.meta;
  const prevUsername = _action$meta19.prevUsername,
        username = _action$meta19.username;


  if (prevUsername !== username) {
    // The logged in user has changed. Remove all record state, because the new user may not be
    // permitted to read some records that the previous user could.

    return clearAll(state);
  }

  return state;
};

const getRefMap = data => {
  let items = (0, _get2.default)(data, ['ns2:abstract-common-list', 'list-item']) || [];

  if (!Array.isArray(items)) {
    items = [items];
  }

  const refMap = {};

  items.forEach(({ csid, referenced }) => {
    refMap[csid] = referenced;
  });

  return refMap;
};

const updateItemRefStates = (data, refMap) => {
  let existingItems = data && data.getIn(['document', 'ns2:abstract-common-list', 'list-item']);

  if (!existingItems) {
    return data;
  }

  if (!_immutable2.default.List.isList(existingItems)) {
    existingItems = _immutable2.default.List.of(existingItems);
  }

  const updatedItems = existingItems.map(item => item.set('referenced', refMap[item.get('csid')]));

  return data.setIn(['document', 'ns2:abstract-common-list', 'list-item'], updatedItems);
};

const handleReadVocabularyItemRefsFulfilled = (state, action) => {
  const csid = action.meta.csid;


  let nextState = state.deleteIn([csid, 'isReadVocabularyItemRefsPending']);

  const refMap = getRefMap(action.payload.data);

  const baselineData = getBaselineData(state, csid);
  const currentData = getCurrentData(state, csid);

  let nextBaselineData;
  let nextCurrentData;

  if (baselineData === currentData) {
    nextBaselineData = updateItemRefStates(baselineData, refMap);
    nextCurrentData = nextBaselineData;
  } else {
    nextBaselineData = updateItemRefStates(baselineData, refMap);
    nextCurrentData = updateItemRefStates(currentData, refMap);
  }

  nextState = setBaselineData(nextState, csid, nextBaselineData);
  nextState = setCurrentData(nextState, csid, nextCurrentData);

  return nextState;
};

exports.default = (state = _immutable2.default.Map(), action) => {
  switch (action.type) {
    case _record.VALIDATION_FAILED:
      return handleValidationFailed(state, action);
    case _record.VALIDATION_PASSED:
      return handleValidationPassed(state, action);
    case _record.ADD_FIELD_INSTANCE:
      return addFieldInstance(state, action);
    case _record.CREATE_NEW_RECORD:
      return createNewRecord(state, action);
    case _record.CREATE_NEW_SUBRECORD:
      return createNewSubrecord(state, action);
    case _record.DELETE_FIELD_VALUE:
      return deleteFieldValue(state, action);
    case _record.MOVE_FIELD_VALUE:
      return moveFieldValue(state, action);
    case _record.SET_FIELD_VALUE:
      return setFieldValue(state, action);
    case _record.FIELD_COMPUTE_FULFILLED:
      return handleFieldComputeFulfilled(state, action);
    case _record.RECORD_CREATED:
      return state.set(action.meta.newRecordCsid, state.get(action.meta.currentCsid)).delete(action.meta.currentCsid);
    case _record.RECORD_READ_STARTED:
      return state.setIn([action.meta.csid, 'isReadPending'], true);
    case _record.RECORD_READ_FULFILLED:
      return handleRecordReadFulfilled(state, action);
    case _record.RECORD_READ_REJECTED:
      return state.setIn([action.meta.csid, 'error'], _immutable2.default.fromJS(action.payload)).deleteIn([action.meta.csid, 'isReadPending']);
    case _record.RECORD_SAVE_STARTED:
      return state.setIn([action.meta.csid, 'isSavePending'], true);
    case _record.RECORD_SAVE_FULFILLED:
      return handleRecordSaveFulfilled(state, action);
    case _record.RECORD_SAVE_REJECTED:
      return state
      // I don't think there's any reason to store the save error.
      // .setIn([action.meta.csid, 'error'], Immutable.fromJS(action.payload))
      .deleteIn([action.meta.csid, 'isSavePending']);
    case _record.RECORD_TRANSITION_STARTED:
      return state.setIn([action.meta.csid, 'isSavePending'], true);
    case _record.RECORD_TRANSITION_FULFILLED:
      return handleTransitionFulfilled(state, action);
    case _record.RECORD_TRANSITION_REJECTED:
      return state.deleteIn([action.meta.csid, 'isSavePending']);
    case _record.RECORD_DELETE_STARTED:
      return state.setIn([action.meta.csid, 'isSavePending'], true);
    case _record.RECORD_DELETE_FULFILLED:
      return handleDeleteFulfilled(state, action);
    case _record.RECORD_DELETE_REJECTED:
      return state.deleteIn([action.meta.csid, 'isSavePending']);
    case _record.SUBRECORD_CREATED:
      return handleSubrecordCreated(state, action);
    case _record.SUBRECORD_READ_FULFILLED:
      return state.setIn([action.meta.csid, 'subrecord', action.meta.subrecordName], action.meta.subrecordCsid);
    case _record.REVERT_RECORD:
      return revertRecord(state, action);
    case _relation.SUBJECT_RELATIONS_UPDATED:
      return handleSubjectRelationsUpdated(state, action);
    case _idGenerator.CREATE_ID_FULFILLED:
      return handleCreateIDFulfilled(state, action);
    case _record.DETACH_SUBRECORD:
      return detachSubrecord(state, action);
    case _record.CLEAR_RECORD:
      return clear(state, action.meta.csid);
    case _login.LOGIN_FULFILLED:
      return handleLoginFulfilled(state, action);
    case _logout.LOGOUT_FULFILLED:
      return clearAll(state);
    case _record.SORT_FIELD_INSTANCES:
      return sortFieldInstances(state, action);
    case _vocabulary.READ_VOCABULARY_ITEM_REFS_STARTED:
      return state.setIn([action.meta.csid, 'isReadVocabularyItemRefsPending'], true);
    case _vocabulary.READ_VOCABULARY_ITEM_REFS_FULFILLED:
      return handleReadVocabularyItemRefsFulfilled(state, action);
    case _vocabulary.READ_VOCABULARY_ITEM_REFS_REJECTED:
      return state.setIn([action.meta.csid, 'error'], _immutable2.default.fromJS(action.payload)).deleteIn([action.meta.csid, 'isReadVocabularyItemRefsPending']);
    default:
      return state;
  }
};

const getData = exports.getData = (state, csid) => getCurrentData(state, csid);

const getError = exports.getError = (state, csid) => state.getIn([csid, 'error']);

const getSubrecordCsid = exports.getSubrecordCsid = (state, csid, subrecordName) => state.getIn([csid, 'subrecord', subrecordName]);

const getSubrecordData = exports.getSubrecordData = (state, csid) => {
  const subrecords = state.getIn([csid, 'subrecord']);

  return subrecords ? subrecords.map(subrecordCsid => getData(state, subrecordCsid)) : null;
};

const getRelationUpdatedTimestamp = exports.getRelationUpdatedTimestamp = (state, csid) => state.getIn([csid, 'relationUpdatedTime']);

const getNewData = exports.getNewData = state => getData(state, unsavedRecordKey());

const getNewSubrecordCsid = exports.getNewSubrecordCsid = (state, subrecordName) => getSubrecordCsid(state, unsavedRecordKey(), subrecordName);

const getValidationErrors = exports.getValidationErrors = (state, csid) => state.getIn([csid, 'validation']);

const isReadPending = exports.isReadPending = (state, csid) => state.getIn([csid, 'isReadPending']);

const isSavePending = exports.isSavePending = (state, csid) => state.getIn([csid, 'isSavePending']);

const isReadVocabularyItemRefsPending = exports.isReadVocabularyItemRefsPending = (state, csid) => state.getIn([csid, 'isReadVocabularyItemRefsPending']);

const isModified = exports.isModified = (state, csid) => {
  // Do a reference equality test between the current and baseline data. This will not detect if
  // a change is made, then another change is made that undoes the first. But it's more efficient
  // than a deep value equality test.

  if (getCurrentData(state, csid) !== getBaselineData(state, csid)) {
    return true;
  }

  // Check subrecords.

  const subrecords = state.getIn([csid, 'subrecord']);

  if (subrecords && subrecords.find(subrecordCsid => isModified(state, subrecordCsid))) {
    return true;
  }

  return false;
};

const isModifiedExceptPart = exports.isModifiedExceptPart = (state, csid, exceptPart) => {
  // Check subrecords.

  const subrecords = state.getIn([csid, 'subrecord']);

  if (subrecords && subrecords.find(subrecordCsid => isModified(state, subrecordCsid))) {
    return true;
  }

  // Check parts, except for the given exception.

  const data = state.getIn([csid, 'data']);

  if (!data) {
    return false;
  }

  const baselineData = data.get('baseline');
  const currentData = data.get('current');

  if (currentData === baselineData) {
    return false;
  }

  const baselineDocument = baselineData.get('document');
  const currentDocument = currentData.get('document');

  if (currentDocument === baselineDocument) {
    return false;
  }

  const modifiedPart = currentDocument.keySeq().filter(part => part !== exceptPart && !part.startsWith('@')).find(part => currentDocument.get(part) !== baselineDocument.get(part));

  return !!modifiedPart;
};