const path = require('path');
const fs = require('fs-extra');
const fse = require('fs-extra');
const shell = require('shelljs');
const glob = require('glob');
const symlink = require('symlink-dir');
const log = require('npmlog');

const targetPath = path.join(__dirname, '..', 'node_modules', '@storybook');
const prefix = 'hoist-internals';
const cwd = path.join(__dirname, '..');

log.heading = 'lerna+';
log.addLevel('success', 3001, { fg: 'green', bold: true });
log.info(prefix, 'Hoisting internal packages');

const getLernaPackages = () =>
  fse.readJson(path.join(__dirname, '..', 'lerna.json')).then(json => json.packages);
const passingLog = fn => i => {
  fn(i);
  return i;
};
const getPackageOfFolder = sourcePath => fse.readJsonSync(path.join(sourcePath, 'package.json'));

const task = getLernaPackages()
  .then(
    passingLog(packages => {
      log.verbose(prefix, 'working dir paths: %j', cwd);
      log.verbose(prefix, 'source paths: %j', packages);
      log.verbose(prefix, 'target paths: %j', targetPath);
    })
  )
  .then(packages => `@(${packages.map(s => s.replace('/*', '')).join('|')})/*/`)
  .then(
    passingLog(pattern => {
      log.silly(prefix, 'pattern to look for packages: %j', pattern);
    })
  )
  .then(
    pattern =>
      new Promise((resolve, reject) => {
        glob(pattern, { cwd }, (error, results) => (error ? reject(error) : resolve(results)));
      })
  )
  .then(results =>
    Promise.all(
      results
        .map(sourcePath => path.resolve(fs.realpathSync(sourcePath)))
        .reduce((acc, item) => {
          if (!acc.includes(item)) {
            acc.push(item);
          }
          return acc;
        }, [])
        .map(
          passingLog(item => {
            log.silly(prefix, 'found package path', item);
          })
        )
        .map(sourcePath => ({
          sourcePath,
          packageJson: getPackageOfFolder(sourcePath),
        }))
        .filter(({ packageJson }) => !packageJson.private)
        .map(({ sourcePath, packageJson }) =>
          Promise.resolve(packageJson.name.replace('@storybook/', ''))
            .then(packageName => {
              log.silly(prefix, 'found package name', packageName);
              return path.join(targetPath, packageName);
            })
            .then(localTargetPath =>
              fse
                .remove(localTargetPath)
                .then(() => symlink(sourcePath, localTargetPath))
                .then(
                  passingLog(() => {
                    log.silly(prefix, 'symlinked ', [sourcePath, localTargetPath]);
                  })
                )
                .then(() => localTargetPath)
                .catch(error => {
                  log.error(prefix, 'symlink', error, [sourcePath, localTargetPath]);
                  throw new Error('failed symlink');
                })
            )
        )
    )
  )
  .then(locations =>
    Promise.all(
      locations
        .map(location => path.join(location, 'node_modules', '@storybook'))
        .map(
          passingLog(removePath => {
            log.verbose(prefix, 'removing ', removePath);
          })
        )
        .map(removePath => shell.rm('-rf', removePath))
        .map(
          (item, index) =>
            item.code === 0 ? Promise.resolve(locations[index]) : Promise.reject(item)
        )
    )
  );

task
  .then(packages => {
    log.verbose(prefix, packages.map(dir => dir.replace(cwd, '')).join(',\n'));
    log.success(prefix, `Hoisted ${packages.length} packages`);
  })
  .catch(error => {
    log.error(prefix, 'failed', error);
    shell.exit(1);
  });