"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.unrelateBidirectional = exports.unrelate = exports.showRelationNotification = exports.find = exports.deleteRelation = exports.createBidirectional = exports.create = exports.clearState = exports.checkForRelations = exports.batchUnrelateBidirectional = exports.batchUnrelate = exports.batchCreateBidirectional = exports.batchCreate = void 0;
var _immutable = _interopRequireDefault(require("immutable"));
var _reactIntl = require("react-intl");
var _get = _interopRequireDefault(require("lodash/get"));
var _session = _interopRequireDefault(require("../helpers/session"));
var _notification = require("./notification");
var _getErrorDescription = _interopRequireDefault(require("../helpers/getErrorDescription"));
var _reducers = require("../reducers");
var _errorCodes = require("../constants/errorCodes");
var _notificationStatusCodes = require("../constants/notificationStatusCodes");
var _actionCodes = require("../constants/actionCodes");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const clearState = () => ({
  type: _actionCodes.CLEAR_RELATION_STATE
});
exports.clearState = clearState;
const messages = (0, _reactIntl.defineMessages)({
  related: {
    "id": "action.relation.related",
    "defaultMessage": "{objectCount, plural, =0 {No records} one {# record} other {# records}} related to {subjectTitle}."
  },
  batchCreateError: {
    "id": "action.relation.batchCreateError",
    "defaultMessage": "Some records could not be related: {error}"
  },
  batchUnrelateError: {
    "id": "action.relation.batchUnrelateError",
    "defaultMessage": "Some records could not be unrelated: {error}"
  }
});
const notificationID = 'action.relation';
const showRelationNotification = (message, values) => (0, _notification.showNotification)({
  items: [{
    message,
    values
  }],
  date: new Date(),
  status: _notificationStatusCodes.STATUS_SUCCESS,
  autoClose: true
}, notificationID);

/*
 * Find a relation, given at least the subject csid and object csid, and optionally the subject
 * record type, object record type, and predicate. This function assumes that there will be very
 * few (typically zero or one) results, and retrieves them all without paginating.
 */
exports.showRelationNotification = showRelationNotification;
const find = (config, subject, object, predicate) => (dispatch, getState) => {
  if (!(subject.csid || object.csid)) {
    throw new Error('subject csid or object csid must be supplied');
  }
  if ((0, _reducers.getRelationFindResult)(getState(), subject, object, predicate)) {
    // Already have a result for this descriptor. Do nothing.
    // TODO: Also check for a pending find request.

    return null;
  }
  dispatch({
    type: _actionCodes.RELATION_FIND_STARTED,
    meta: {
      subject,
      object,
      predicate
    }
  });
  const params = {
    prd: predicate,
    wf_deleted: 'false',
    pgSz: '0'
  };
  if (subject) {
    const {
      csid,
      recordType
    } = subject;
    params.sbj = csid;
    if (recordType) {
      params.sbjType = (0, _get.default)(config, ['recordTypes', recordType, 'serviceConfig', 'objectName']);
    }
  }
  if (object) {
    const {
      csid,
      recordType
    } = object;
    params.obj = csid;
    if (recordType) {
      params.objType = (0, _get.default)(config, ['recordTypes', recordType, 'serviceConfig', 'objectName']);
    }
  }
  const requestConfig = {
    params
  };
  return (0, _session.default)().read('/relations', requestConfig).then(response => dispatch({
    type: _actionCodes.RELATION_FIND_FULFILLED,
    payload: response,
    meta: {
      subject,
      object,
      predicate
    }
  })).catch(error => dispatch({
    type: _actionCodes.RELATION_FIND_REJECTED,
    payload: {
      code: _errorCodes.ERR_API,
      error
    },
    meta: {
      subject,
      object,
      predicate
    }
  }));
};

/**
 * Check if any relations exist for a given record csid and predicate, and optionally, a related
 * csid. Resolves to true or false.
 */
exports.find = find;
const checkForRelations = (csid1, predicate, csid2) => () => {
  const params = {
    prd: predicate,
    sbj: csid1,
    andReciprocal: 'true',
    wf_deleted: 'false',
    pgSz: '1'
  };
  if (typeof csid2 !== 'undefined') {
    params.obj = csid2;
  }
  const requestConfig = {
    params
  };
  return (0, _session.default)().read('/relations', requestConfig).then(response => {
    const totalItems = (0, _get.default)(response, ['data', 'rel:relations-common-list', 'totalItems']);
    return totalItems && parseInt(totalItems, 10) > 0;
  });
};
exports.checkForRelations = checkForRelations;
const deleteRelation = csid => dispatch => {
  if (!csid) {
    throw new Error('csid must be supplied');
  }

  // return Promise.resolve();
  dispatch({
    type: _actionCodes.RELATION_DELETE_STARTED,
    meta: {
      csid
    }
  });
  return (0, _session.default)().delete(`/relations/${csid}`).then(response => dispatch({
    type: _actionCodes.RELATION_DELETE_FULFILLED,
    payload: response,
    meta: {
      csid
    }
  })).catch(error => {
    dispatch({
      type: _actionCodes.RELATION_DELETE_REJECTED,
      payload: {
        code: _errorCodes.ERR_API,
        error
      },
      meta: {
        csid
      }
    });
    return Promise.reject(error);
  });
};
exports.deleteRelation = deleteRelation;
const doUnrelate = (config, subject, object, predicate) => (dispatch, getState) => {
  if (!(subject.csid && object.csid)) {
    throw new Error('subject csid and object csid must be supplied');
  }
  const existingResult = (0, _reducers.getRelationFindResult)(getState(), subject, object, predicate);
  let promise;
  if (existingResult) {
    promise = Promise.resolve(existingResult);
  } else {
    promise = dispatch(find(config, subject, object, predicate)).then(() => (0, _reducers.getRelationFindResult)(getState(), subject, object, predicate));
  }
  return promise.then(findResult => {
    const list = findResult.get('rel:relations-common-list');
    let items = list.get('relation-list-item');
    if (!_immutable.default.List.isList(items)) {
      items = _immutable.default.List.of(items);
    }
    return Promise.all(items.map(item => dispatch(deleteRelation(item.get('csid')))));
  });
};
const unrelate = (config, subject, object, predicate) => dispatch => dispatch(doUnrelate(config, subject, object, predicate)).then(() => dispatch({
  type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
  meta: {
    subject,
    updatedTime: new Date().toISOString()
  }
})).catch(() => {});
exports.unrelate = unrelate;
const unrelateBidirectional = (config, subject, object, predicate) => dispatch => dispatch(unrelate(config, subject, object, predicate)).then(() => dispatch(unrelate(config, object, subject, predicate))).catch(() => {});
exports.unrelateBidirectional = unrelateBidirectional;
const batchUnrelate = (config, subject, objects, predicate) => dispatch => objects.reduce((promise, object) => promise.then(() => dispatch(doUnrelate(config, subject, object, predicate))), Promise.resolve()).then(() => dispatch({
  type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
  meta: {
    subject,
    updatedTime: new Date().toISOString()
  }
})).catch(error => {
  dispatch((0, _notification.showNotification)({
    items: [{
      message: messages.batchUnrelateError,
      values: {
        error: (0, _getErrorDescription.default)(error)
      }
    }],
    date: new Date(),
    status: _notificationStatusCodes.STATUS_ERROR
  }, notificationID));
});
exports.batchUnrelate = batchUnrelate;
const batchUnrelateBidirectional = (config, subject, objects, predicate) => dispatch =>
// For the passed subject, we only want to dispatch SUBJECT_RELATIONS_UPDATED once at the end, so
// doUnrelate is used. The passed objects should be unique; for the reverse relations (where the
// object becomes the subject), SUBJECT_RELATIONS_UPDATED may be dispatched immediately, so
// unrelate is used.

// Send these requests one at a time, to avoid DOSing the server.
objects.reduce((promise, object) => promise.then(() => dispatch(doUnrelate(config, subject, object, predicate))).then(() => dispatch(unrelate(config, object, subject, predicate))), Promise.resolve()).then(() => dispatch({
  type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
  meta: {
    subject,
    updatedTime: new Date().toISOString()
  }
})).catch(error => {
  dispatch((0, _notification.showNotification)({
    items: [{
      message: messages.batchUnrelateError,
      values: {
        error: (0, _getErrorDescription.default)(error)
      }
    }],
    date: new Date(),
    status: _notificationStatusCodes.STATUS_ERROR
  }, notificationID));
});
exports.batchUnrelateBidirectional = batchUnrelateBidirectional;
const doCreate = (subject, object, predicate) => dispatch => {
  dispatch({
    type: _actionCodes.RELATION_SAVE_STARTED,
    meta: {
      subject,
      object,
      predicate
    }
  });
  const config = {
    data: {
      document: {
        'rel:relations_common': {
          '@xmlns:rel': 'http://collectionspace.org/services/relation',
          subjectCsid: subject.csid,
          objectCsid: object.csid,
          relationshipType: predicate
        }
      }
    }
  };
  return (0, _session.default)().create('/relations', config).then(response => dispatch({
    type: _actionCodes.RELATION_SAVE_FULFILLED,
    payload: response,
    meta: {
      subject,
      object,
      predicate
    }
  })).catch(error => {
    dispatch({
      type: _actionCodes.RELATION_SAVE_REJECTED,
      payload: {
        code: _errorCodes.ERR_API,
        error
      },
      meta: {
        subject,
        object,
        predicate
      }
    });
    return Promise.reject(error);
  });
};
const batchCreate = (subject, objects, predicate) => dispatch => objects.reduce((promise, object) => promise.then(() => dispatch(checkForRelations(subject.csid, predicate, object.csid))).then(relationExists => {
  if (relationExists) {
    return Promise.resolve();
  }
  return dispatch(doCreate(subject, object, predicate));
}), Promise.resolve()).then(() => dispatch({
  type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
  meta: {
    subject,
    updatedTime: new Date().toISOString()
  }
})).catch(error => {
  dispatch((0, _notification.showNotification)({
    items: [{
      message: messages.batchCreateError,
      values: {
        error: (0, _getErrorDescription.default)(error)
      }
    }],
    date: new Date(),
    status: _notificationStatusCodes.STATUS_ERROR
  }, notificationID));
});
exports.batchCreate = batchCreate;
const batchCreateBidirectional = (subject, objects, predicate) => dispatch =>
// Send these requests one at a time, to avoid DOSing the server.
objects.reduce((promise, object) => promise.then(() => dispatch(checkForRelations(subject.csid, predicate, object.csid))).then(relationExists => {
  if (relationExists) {
    return Promise.resolve();
  }
  return dispatch(doCreate(subject, object, predicate)).then(() => dispatch(doCreate(object, subject, predicate)));
}), Promise.resolve()).then(() => {
  dispatch(showRelationNotification(messages.related, {
    objectCount: objects.length,
    subjectTitle: subject.title
  }));
  dispatch({
    type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
    meta: {
      subject,
      updatedTime: new Date().toISOString()
    }
  });
}).catch(error => {
  dispatch((0, _notification.showNotification)({
    items: [{
      message: messages.batchCreateError,
      values: {
        error: (0, _getErrorDescription.default)(error)
      }
    }],
    date: new Date(),
    status: _notificationStatusCodes.STATUS_ERROR
  }, notificationID));
});
exports.batchCreateBidirectional = batchCreateBidirectional;
const create = (subject, object, predicate) => dispatch => dispatch(doCreate(subject, object, predicate)).then(() => dispatch({
  type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
  meta: {
    subject,
    updatedTime: new Date().toISOString()
  }
})).catch(() => {});
exports.create = create;
const createBidirectional = (subject, object, predicate) => dispatch => dispatch(doCreate(subject, object, predicate)).then(() => dispatch(doCreate(object, subject, predicate))).then(() => dispatch({
  type: _actionCodes.SUBJECT_RELATIONS_UPDATED,
  meta: {
    subject,
    updatedTime: new Date().toISOString()
  }
})).catch(() => {});
exports.createBidirectional = createBidirectional;