#!/usr/bin/env node

const { readFile, exists } = require('mz/fs');
const sgf = require('staged-git-files');
const execa = require('execa');
const awaitify = require('apr-awaitify');
const asyncFilter = require('apr-filter');
const main = require('apr-main');
const map = require('apr-map');
const reduce = require('apr-reduce');
const globby = require('globby');
const path = require('path');
const uniq = require('lodash.uniq');
const argv = require('yargs').argv;
const checksum = require('checksum');
const { lstatSync, readdirSync } = require('fs');
const { join } = require('path');

const ROOT = path.join(__dirname, '..');
const getStaged = awaitify(sgf);

const statuses = [
  'Added',
  'Copied',
  'Deleted',
  'Modified',
  'Renamed',
  'Unmerged'
];

const exec = (args = []) =>
  execa('lerna', args, {
    stdio: 'inherit'
  });

const lint = scope => exec(['run', 'lint', '--scope', scope]);
const test = scope => exec(['run', 'test', '--scope', scope]);

const run = async scope => {
  if (argv.lint) {
    await lint(scope);
  }

  if (argv.test) {
    await test(scope);
  }
};

const runLint = async scope => {
  await lint(scope);
};

const runTest = async scope => {
  await test(scope);
};

const getUnstaged = async () => {
  const unstaged = await execa('git', ['ls-files', '-m']);
  return unstaged.stdout.split('\n');
};

const asyncChecksum = awaitify(checksum.file);

const add = async filename => execa('git', ['add', filename]);

const runLintAndGitAdd = async (staged, pkgs) => {
  const unstaged = (await getUnstaged()).map(file => path.resolve(ROOT, file));

  const existing = await asyncFilter(staged, ({ filename }) =>
    exists(filename)
  );

  const checksums = await map(existing, async file => {
    const checksum = await asyncChecksum(file.filename);
    return Object.assign({}, file, { checksum });
  });

  await map(pkgs.map(({ name }) => name), runLint);

  const changed = await asyncFilter(
    checksums,
    async ({ filename, checksum }) => {
      const newChecksum = await asyncChecksum(filename);
      return checksum != newChecksum;
    }
  );

  const modifieds = await reduce(
    changed,
    async (modifieds, file) => {
      const isUnstaged = unstaged.filter(f => f === file.filename).length;
      if (
        (file.status === 'Modified' || file.status === 'Added') &&
        isUnstaged
      ) {
        modifieds.push(file);
      } else {
        await add(file.filename);
      }
      return modifieds;
    },
    []
  );

  if (modifieds.length) {
    modifieds.forEach(modified =>
      console.log('PARTIALLY STAGED FILE ', modified.filename)
    );
    process.exit(0);
  }
};

const gather = async () => {
  const isDirectory = source => lstatSync(source).isDirectory();
  const getDirectories = source =>
    readdirSync(source)
      .map(name => join(source, name))
      .filter(isDirectory);

  const locations = await globby(
    getDirectories('./packages').concat(getDirectories('./prototypes')),
    { cwd: ROOT }
  );

  const staged = (await getStaged())
    .filter(({ status }) => statuses.indexOf(status) >= 0)
    // .map(({ filename }) => path.resolve(ROOT, filename));
    .map(file =>
      Object.assign({}, file, { filename: path.resolve(ROOT, file.filename) })
    );

  const folders = uniq(
    locations
      .map(folder => path.resolve(ROOT, folder))
      .filter(folder => staged.some(i => i.filename.indexOf(folder) >= 0))
  );

  const pkgs = await map(folders, async folder =>
    JSON.parse(await readFile(path.join(folder, 'package.json'), 'utf-8'))
  );

  if (argv.lint) {
    await runLintAndGitAdd(staged, pkgs);
  }

  if (argv.test) {
    await await map(pkgs.map(({ name }) => name), runTest);
  }
};

main(gather());
