
const lodashGet = require('lodash.get');
const { setProp, unsetProp } = 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
        .flatMap(arrObj => getUnwindablePaths(arrObj, newPath))
        .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
      .flatMap(row => {
        const unwindArray = lodashGet(row, unwindPath);

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

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

        const baseNewRow = blankOut ? {} : row;
        const [firstRow, ...restRows] = unwindArray;
        return [
          setProp(row, unwindPath, firstRow),
          ...restRows.map(unwindRow => setProp(baseNewRow, unwindPath, unwindRow))
        ];
      });
  }

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

module.exports = unwind;