/**
 * Sets specific properties of a specific state to specific values and prevents
 * unnecessary state changes.
 *
 * @param {Object} target - The state on which the specified properties are to
 * be set.
 * @param {Object} source - The map of properties to values which are to be set
 * on the specified target.
 * @returns {Object} The specified target if the values of the specified
 * properties equal the specified values; otherwise, a new state constructed
 * from the specified target by setting the specified properties to the
 * specified values.
 */
export function setStateProperties(target, source) {
    let t = target;
    for (const property in source) { // eslint-disable-line guard-for-in
        t = setStateProperty(t, property, source[property], t === target);
    }
    return t;
}
/**
 * Sets a specific property of a specific state to a specific value. Prevents
 * unnecessary state changes (when the specified value is equal to the
 * value of the specified property of the specified state).
 *
 * @param {Object} state - The (Redux) state from which a new state is to be
 * constructed by setting the specified property to the specified
 * value.
 * @param {string} property - The property of state which is to be
 * assigned the specified value (in the new state).
 * @param {*} value - The value to assign to the specified property.
 * @returns {Object} The specified state if the value of the specified
 * property equals the specified value/tt>; otherwise, a new state
 * constructed from the specified state by setting the specified
 * property to the specified value.
 */
export function setStateProperty(state, property, value) {
    return _setStateProperty(state, property, value, /* copyOnWrite */ true);
}
/* eslint-disable max-params */
/**
 * Sets a specific property of a specific state to a specific value. Prevents
 * unnecessary state changes (when the specified value is equal to the
 * value of the specified property of the specified state).
 *
 * @param {Object} state - The (Redux) state from which a state is to be
 * constructed by setting the specified property to the specified
 * value.
 * @param {string} property - The property of state which is to be
 * assigned the specified value.
 * @param {*} value - The value to assign to the specified property.
 * @param {boolean} copyOnWrite - If the specified state is to not be
 * modified, true; otherwise, false.
 * @returns {Object} The specified state if the value of the specified
 * property equals the specified value/tt> or copyOnWrite
 * is truthy; otherwise, a new state constructed from the specified
 * state by setting the specified property to the specified
 * value.
 */
function _setStateProperty(state, property, value, copyOnWrite) {
    // Delete state properties that are to be set to undefined. (It is a matter
    // of personal preference, mostly.)
    if (typeof value === 'undefined'
            && Object.prototype.hasOwnProperty.call(state, property)) {
        const newState = copyOnWrite ? { ...state } : state;
        if (delete newState[property]) {
            return newState;
        }
    }
    if (state[property] !== value) {
        if (copyOnWrite) {
            return {
                ...state,
                [property]: value
            };
        }
        state[property] = value;
    }
    return state;
}
/* eslint-enable max-params */