"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 = exports.default = void 0;

var _immutable = _interopRequireDefault(require("immutable"));

var _get = _interopRequireDefault(require("lodash/get"));

var _actionCodes = require("../constants/actionCodes");

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

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

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) => {
  const {
    csid,
    path,
    position,
    recordTypeConfig
  } = action.meta;
  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const value = (0, _recordDataHelpers.deepGet)(data, path);
  const list = _immutable.default.List.isList(value) ? value : _immutable.default.List.of(value);
  const fieldDescriptor = (0, _get.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) => {
  const {
    config,
    csid,
    path,
    byField
  } = action.meta;
  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

  const value = (0, _recordDataHelpers.deepGet)(data, path);
  const list = _immutable.default.List.isList(value) ? value : _immutable.default.List.of(value); // TODO: Check for a custom sort comparator function in field config.
  // For now just use the default.

  const comparator = (str1, str2) => str1.localeCompare(str2, config.locale);

  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, options = {}) => {
  const {
    cloneCsid,
    subrecordName,
    stickyFields
  } = options;
  let data;

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

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

    if (stickyFields) {
      // Merge in the user's saved sticky fields for this record type, if any.
      const fields = stickyFields.get(recordTypeConfig.name);

      if (fields) {
        data = data.mergeDeep(fields);
      }
    }
  }

  const csid = unsavedRecordKey(subrecordName);
  let nextState = state.delete(csid);
  const {
    subrecords
  } = recordTypeConfig;

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

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

      if (!cloneSubrecordCsid) {
        const subrecordType = subrecordConfig.recordType;
        const subrecordTypeConfig = (0, _get.default)(config, ['recordTypes', subrecordType]);
        const subrecordCsid = state.getIn([cloneCsid, 'subrecord', name]);
        nextState = doCreateNew(nextState, config, subrecordTypeConfig, {
          cloneCsid: subrecordCsid,
          subrecordName: name,
          stickyFields
        });
        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) => {
  const {
    config,
    recordTypeConfig,
    cloneCsid,
    stickyFields
  } = action.meta;
  return doCreateNew(state, config, recordTypeConfig, {
    cloneCsid,
    stickyFields
  });
};

const deleteFieldValue = (state, action) => {
  const {
    csid,
    path
  } = action.meta;
  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) => {
  const {
    csid,
    path,
    newPosition
  } = action.meta;
  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 (!_immutable.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) => {
  const {
    csid,
    path
  } = action.meta;
  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) => {
  const {
    csid,
    path
  } = action.meta;
  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) => {
  const {
    csid,
    recordTypeConfig
  } = action.meta;
  const data = (0, _recordDataHelpers.normalizeRecordData)(recordTypeConfig, _immutable.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) => {
  const {
    csid,
    recordTypeConfig,
    relatedSubjectCsid,
    recordPagePrimaryCsid
  } = action.meta;
  const data = (0, _recordDataHelpers.normalizeRecordData)(recordTypeConfig, _immutable.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 // Don't clear unsaved record data
  ];
  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) => {
  const {
    recordTypeConfig,
    csid
  } = action.meta;
  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, _get.default)(recordTypeConfig, 'subrecords');

  if (configuredSubrecords) {
    Object.entries(configuredSubrecords).forEach(entry => {
      const [subrecordName, subrecordConfig] = entry;
      const {
        csidField
      } = subrecordConfig;

      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) => {
  const {
    csid,
    csidField,
    subrecordName,
    subrecordCsid,
    isDefault
  } = action.meta;
  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) => {
  const {
    config,
    csid,
    csidField,
    subrecordName,
    subrecordTypeConfig,
    cloneCsid,
    isDefault,
    stickyFields
  } = action.meta;
  let nextState = doCreateNew(state, config, subrecordTypeConfig, {
    cloneCsid,
    subrecordName,
    stickyFields
  });
  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.
  const {
    subject,
    updatedTime
  } = action.meta;
  const subjectCsid = subject.csid;

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

  return state;
};

const handleCreateIDFulfilled = (state, action) => {
  const {
    csid,
    path,
    transform
  } = action.meta;
  const data = getCurrentData(state, csid);

  if (!data) {
    return state;
  }

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

const handleValidationFailed = (state, action) => {
  const errors = action.payload;
  const {
    csid,
    path
  } = action.meta;
  return state.setIn([csid, 'validation', ...path], errors);
};

const handleValidationPassed = (state, action) => {
  const {
    csid,
    path
  } = action.meta;
  return state.deleteIn([csid, 'validation', ...path]);
};

const handleTransitionFulfilled = (state, action) => {
  const {
    csid,
    transitionName,
    recordPagePrimaryCsid,
    recordTypeConfig,
    relatedSubjectCsid,
    updatedTimestamp
  } = action.meta;
  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, _get.default)(action, ['payload', 'data']);

    if (newData) {
      const data = (0, _recordDataHelpers.normalizeRecordData)(recordTypeConfig, _immutable.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) => {
  const {
    csid,
    relatedSubjectCsid,
    updatedTimestamp
  } = action.meta;
  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) => {
  const {
    prevUsername,
    username
  } = action.meta;

  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, _get.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 (!_immutable.default.List.isList(existingItems)) {
    existingItems = _immutable.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;
  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;
};

var _default = (state = _immutable.default.Map(), action) => {
  switch (action.type) {
    case _actionCodes.VALIDATION_FAILED:
      return handleValidationFailed(state, action);

    case _actionCodes.VALIDATION_PASSED:
      return handleValidationPassed(state, action);

    case _actionCodes.ADD_FIELD_INSTANCE:
      return addFieldInstance(state, action);

    case _actionCodes.CREATE_NEW_RECORD:
      return createNewRecord(state, action);

    case _actionCodes.CREATE_NEW_SUBRECORD:
      return createNewSubrecord(state, action);

    case _actionCodes.DELETE_FIELD_VALUE:
      return deleteFieldValue(state, action);

    case _actionCodes.MOVE_FIELD_VALUE:
      return moveFieldValue(state, action);

    case _actionCodes.SET_FIELD_VALUE:
      return setFieldValue(state, action);

    case _actionCodes.FIELD_COMPUTE_FULFILLED:
      return handleFieldComputeFulfilled(state, action);

    case _actionCodes.RECORD_CREATED:
      return state.set(action.meta.newRecordCsid, state.get(action.meta.currentCsid)).delete(action.meta.currentCsid);

    case _actionCodes.RECORD_READ_STARTED:
      return state.setIn([action.meta.csid, 'isReadPending'], true);

    case _actionCodes.RECORD_READ_FULFILLED:
      return handleRecordReadFulfilled(state, action);

    case _actionCodes.RECORD_READ_REJECTED:
      return state.setIn([action.meta.csid, 'error'], _immutable.default.fromJS(action.payload)).deleteIn([action.meta.csid, 'isReadPending']);

    case _actionCodes.RECORD_SAVE_STARTED:
      return state.setIn([action.meta.csid, 'isSavePending'], true);

    case _actionCodes.RECORD_SAVE_FULFILLED:
      return handleRecordSaveFulfilled(state, action);

    case _actionCodes.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 _actionCodes.RECORD_TRANSITION_STARTED:
      return state.setIn([action.meta.csid, 'isSavePending'], true);

    case _actionCodes.RECORD_TRANSITION_FULFILLED:
      return handleTransitionFulfilled(state, action);

    case _actionCodes.RECORD_TRANSITION_REJECTED:
      return state.deleteIn([action.meta.csid, 'isSavePending']);

    case _actionCodes.RECORD_DELETE_STARTED:
      return state.setIn([action.meta.csid, 'isSavePending'], true);

    case _actionCodes.RECORD_DELETE_FULFILLED:
      return handleDeleteFulfilled(state, action);

    case _actionCodes.RECORD_DELETE_REJECTED:
      return state.deleteIn([action.meta.csid, 'isSavePending']);

    case _actionCodes.SUBRECORD_CREATED:
      return handleSubrecordCreated(state, action);

    case _actionCodes.SUBRECORD_READ_FULFILLED:
      return state.setIn([action.meta.csid, 'subrecord', action.meta.subrecordName], action.meta.subrecordCsid);

    case _actionCodes.REVERT_RECORD:
      return revertRecord(state, action);

    case _actionCodes.SUBJECT_RELATIONS_UPDATED:
      return handleSubjectRelationsUpdated(state, action);

    case _actionCodes.CREATE_ID_FULFILLED:
      return handleCreateIDFulfilled(state, action);

    case _actionCodes.DETACH_SUBRECORD:
      return detachSubrecord(state, action);

    case _actionCodes.CLEAR_RECORD:
      return clear(state, action.meta.csid);

    case _actionCodes.LOGIN_FULFILLED:
      return handleLoginFulfilled(state, action);

    case _actionCodes.LOGOUT_FULFILLED:
      return clearAll(state);

    case _actionCodes.SORT_FIELD_INSTANCES:
      return sortFieldInstances(state, action);

    case _actionCodes.READ_VOCABULARY_ITEM_REFS_STARTED:
      return state.setIn([action.meta.csid, 'isReadVocabularyItemRefsPending'], true);

    case _actionCodes.READ_VOCABULARY_ITEM_REFS_FULFILLED:
      return handleReadVocabularyItemRefsFulfilled(state, action);

    case _actionCodes.READ_VOCABULARY_ITEM_REFS_REJECTED:
      return state.setIn([action.meta.csid, 'error'], _immutable.default.fromJS(action.payload)).deleteIn([action.meta.csid, 'isReadVocabularyItemRefsPending']);

    default:
      return state;
  }
};

exports.default = _default;

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

exports.getData = getData;

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

exports.getError = getError;

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

exports.getSubrecordCsid = getSubrecordCsid;
let subrecordDataMemo = null;

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

  if (subrecords) {
    subrecordData = subrecords.map(subrecordCsid => getData(state, subrecordCsid));

    if (_immutable.default.is(subrecordDataMemo, subrecordData)) {
      return subrecordDataMemo;
    }
  }

  subrecordDataMemo = subrecordData;
  return subrecordData;
};

exports.getSubrecordData = getSubrecordData;

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

exports.getRelationUpdatedTimestamp = getRelationUpdatedTimestamp;

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

exports.getNewData = getNewData;

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

exports.getNewSubrecordCsid = getNewSubrecordCsid;

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

exports.getValidationErrors = getValidationErrors;

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

exports.isReadPending = isReadPending;

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

exports.isSavePending = isSavePending;

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

exports.isReadVocabularyItemRefsPending = isReadVocabularyItemRefsPending;

const 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;
};

exports.isModified = isModified;

const 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;
};

exports.isModifiedExceptPart = isModifiedExceptPart;