import isArray from 'lodash/isArray';
import isObject from 'lodash/isObject';
import get from 'lodash/get';

/**
 * Sets the given value at the given property path in the given data structure
 * It's similar to the lodash (underscore) set function, except that it does not directly modifies the
 * object, it creates a new object instead through the whole given path.
 * One can say it's similar to deep-cloning (with lodash cloneDeep) the structure and simply using
 * the lodash set on the new object. Although it's hopefully faster, since it clones only a sub-tree
 * and not the whole data structure.
 * @param {object} obj
 * @param {string[]|string} keys
 * @param {*} value
 * @returns {object}
 */
export const set = (obj, keys, value) => {
    if (typeof keys === 'string') {
        return set(obj, keys.split('.'), value);
    }
    if (!isArray(keys)) {
        throw new TypeError('The keys argument must be a string or an array!');
    }
    if (keys.length === 0) {
        return value;
    }
    const [key, ...nKeys] = keys;
    if (isArray(obj)) {
        const res = [...obj];
        res[key] = set(obj[key], nKeys, value);
        return res;
    }
    return {
        ...(isObject(obj) ? obj : {}),
        [key]: set(get(obj, key), nKeys, value),
    };
};

export default set;
