const mdeps = require('module-deps-sortable');
const path = require('path');
const babelify = require('babelify');
const concat = require('concat-stream');
const moduleFilters = require('../module_filters');
const smartGlob = require('../smart_glob.js');

/**
 * Returns a readable stream of dependencies, given an array of entry
 * points and an object of options to provide to module-deps.
 *
 * This stream requires filesystem access, and thus isn't suitable
 * for a browser environment.
 *
 * @param indexes paths to entry files as strings
 * @param config optional options passed
 * @returns results
 */
function dependencyStream(indexes, config) {
  const md = mdeps({
    /**
     * Determine whether a module should be included in documentation
     * @param {string} id path to a module
     * @returns {boolean} true if the module should be included.
     */
    filter: id => !!config.external || moduleFilters.internalOnly(id),
    extensions: []
      .concat(config.requireExtension || [])
      .map(ext => '.' + ext.replace(/^\./, ''))
      .concat(['.mjs', '.js', '.json', '.es6', '.jsx']),
    transform: [
      babelify.configure({
        sourceMaps: false,
        compact: false,
        cwd: path.resolve(__dirname, '../../'),
        presets: [
          '@babel/preset-react',
          '@babel/preset-env',
          '@babel/preset-flow'
        ],
        plugins: [
          // Stage 0
          '@babel/plugin-proposal-function-bind',
          // Stage 1
          '@babel/plugin-proposal-export-default-from',
          '@babel/plugin-proposal-logical-assignment-operators',
          '@babel/plugin-proposal-optional-chaining',
          ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }],
          [
            '@babel/plugin-proposal-nullish-coalescing-operator',
            { loose: false }
          ],
          '@babel/plugin-proposal-do-expressions',
          // Stage 2
          ['@babel/plugin-proposal-decorators', { legacy: true }],
          '@babel/plugin-proposal-function-sent',
          '@babel/plugin-proposal-export-namespace-from',
          '@babel/plugin-proposal-numeric-separator',
          '@babel/plugin-proposal-throw-expressions',
          // Stage 3
          '@babel/plugin-syntax-dynamic-import',
          '@babel/plugin-syntax-import-meta',
          ['@babel/plugin-proposal-class-properties', { loose: false }],
          '@babel/plugin-proposal-json-strings'
        ]
      })
    ],
    postFilter: moduleFilters.externals(indexes, config),
    resolve:
      config.resolve === 'node' &&
      ((id, opts, cb) => {
        const r = require('resolve');
        opts.basedir = path.dirname(opts.filename);
        r(id, opts, cb);
      })
  });
  smartGlob(indexes, config.parseExtension).forEach(index => {
    md.write(path.resolve(index));
  });
  md.end();

  return new Promise((resolve, reject) => {
    md.once('error', reject);
    md.pipe(
      concat(function(inputs) {
        resolve(
          inputs
            .filter(
              input =>
                // At this point, we may have allowed a JSON file to be caught by
                // module-deps, or anything else allowed by requireExtension.
                // otherwise module-deps would complain about
                // it not being found. But Babel can't parse JSON, so we filter non-JavaScript
                // files away.
                config.parseExtension.indexOf(
                  path.extname(input.file).replace(/^\./, '')
                ) > -1
            )
            .map(input => {
              // remove source file, since it's transformed anyway
              delete input.source;
              return input;
            })
        );
      })
    );
  });
}

module.exports = dependencyStream;
