
const lodashGet = require('lodash.get');
const { setProp, unsetProp, flattenReducer } = require('../utils');

function getUnwindablePaths(obj, currentPath) {
  return Object.keys(obj).reduce((unwindablePaths, key) => {
    const newPath = currentPath ? `${currentPath}.${key}` : key;
    const value = obj[key];

    if (typeof value === 'object'
      && value !== null
      && !Array.isArray(value)
      && Object.prototype.toString.call(value.toJSON) !== '[object Function]'
      && Object.keys(value).length) {
      unwindablePaths = unwindablePaths.concat(getUnwindablePaths(value, newPath));
    } else if (Array.isArray(value)) {
      unwindablePaths.push(newPath);
      unwindablePaths = unwindablePaths.concat(value
        .map(arrObj => getUnwindablePaths(arrObj, newPath))
        .reduce(flattenReducer, [])
        .filter((item, index, arr) => arr.indexOf(item) !== index));
    }

    return unwindablePaths;
  }, []);
}

/**
 * Performs the unwind recursively in specified sequence
 *
 * @param {String[]} unwindPaths The paths as strings to be used to deconstruct the array
 * @returns {Object => Array} Array of objects containing all rows after unwind of chosen paths
*/
function unwind({ paths = undefined, blankOut = false } = {}) {
  function unwindReducer(rows, unwindPath) {
    return rows
      .map(row => {
        const unwindArray = lodashGet(row, unwindPath);

        if (!Array.isArray(unwindArray)) {
          return row;
        }

        if (!unwindArray.length) {
          return unsetProp(row, unwindPath);
        }

        return unwindArray.map((unwindRow, index) => {
          const clonedRow = (blankOut && index > 0)
            ? {}
            : row;

          return setProp(clonedRow, unwindPath, unwindRow);
        });
      })
      .reduce(flattenReducer, []);
  }

  paths = Array.isArray(paths) ? paths : (paths ? [paths] : undefined);
  return dataRow => (paths || getUnwindablePaths(dataRow)).reduce(unwindReducer, [dataRow]);
}

module.exports = unwind;