#!/usr/bin/env node

const { config } = require('../package.json');
const { exists } = require('mz/fs');
const sgf = require('staged-git-files');
const forceArray = require('force-array');
const awaitify = require('apr-awaitify');
const asyncFilter = require('apr-filter');
const map = require('apr-map');
const reduce = require('apr-reduce');
const parallel = require('apr-parallel');
const execa = require('execa');
const globby = require('globby');
const main = require('apr-main');
const argv = require('yargs').argv;
const path = require('path');
const checksum = require('checksum');

const getStaged = awaitify(sgf);
const asyncChecksum = awaitify(checksum.file);

const ROOT = path.join(__dirname, '../');
const SCRIPTS = path.resolve(__dirname);

const optOut = forceArray(config['fmt-opt-out']).map(pkg =>
  path.join(ROOT, `packages/${pkg}`)
);

const statuses = ['Added', 'Modified'];

const filter = (files = []) =>
  files
    .filter(file => !/node_modules|dist/.test(file))
    .map(file => path.resolve(ROOT, file))
    .filter(file => !optOut.some(pkg => file.indexOf(pkg) === 0));

const run = async (files = []) => {
  const filteredFiles = filter(files);

  const _files = filteredFiles.reduce(
    (files, file) => {
      const ext = path.extname(file).replace(/^./, '') || 'js';
      return Object.assign(files, {
        [ext]: files[ext].concat(file)
      });
    },
    {
      js: [],
      gql: [],
      json: []
    }
  );

  return parallel({
    js: () =>
      _files.js.length
        ? execa(
            'prettier',
            ['--write', '--single-quote', '--parser=babylon'].concat(_files.js),
            {
              stdio: 'inherit'
            }
          )
        : null,
    gql: () =>
      _files.gql.length
        ? execa(
            'prettier',
            ['--write', '--single-quote', '--parser=graphql'].concat(
              _files.gql
            ),
            {
              stdio: 'inherit'
            }
          )
        : null,
    json: () =>
      _files.json.length
        ? execa(
            'prettier',
            ['--write', '--single-quote', '--parser=json'].concat(_files.json),
            {
              stdio: 'inherit'
            }
          )
        : null
  });
};

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

const all = async () => {
  const files = await globby(['packages/**/*.{js,gql,json}', 'scripts/*'], {
    cwd: path.join(__dirname, '..')
  });

  return run(files);
};

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

const staged = async () => {
  const unstaged = (await getUnstaged())
    .map(file => path.resolve(ROOT, file))
    .filter(file => /\.js|gql|json$/.test(file) || file.indexOf(SCRIPTS) === 0);

  const files = (await getStaged())
    .filter(({ status }) => statuses.indexOf(status) >= 0)
    .map(file =>
      Object.assign({}, file, { filename: path.resolve(ROOT, file.filename) })
    )
    .filter(
      file =>
        /\.js|gql|json$/.test(file.filename) ||
        file.filename.indexOf(SCRIPTS) === 0
    );

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

  if (!existing.length) {
    return;
  }

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

  const filenames = existing.map(file => file.filename);
  await run(filenames);

  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(1);
  }
};

main(argv._.length ? run(argv._) : argv.staged ? staged() : all());