#!/usr/bin/env node
require('babel-polyfill'); // eslint-disable-line import/no-unassigned-import

// npm modules
const core = require('@commitlint/core');
const chalk = require('chalk');
const meow = require('meow');
const pick = require('lodash').pick;
const stdin = require('get-stdin');

const pkg = require('./package');
const help = require('./help');

/**
 * Behavioural rules
 */
const rules = {
	fromStdin: (input, flags) =>
		input.length === 0 &&
		typeof flags.from !== 'string' &&
		typeof flags.to !== 'string' &&
		!flags.edit
};

const configuration = {
	string: ['cwd', 'from', 'to', 'edit', 'extends', 'parser-preset'],
	boolean: ['help', 'version', 'quiet', 'color'],
	alias: {
		c: 'color',
		d: 'cwd',
		e: 'edit',
		f: 'from',
		t: 'to',
		q: 'quiet',
		h: 'help',
		v: 'version',
		x: 'extends',
		p: 'parser-preset'
	},
	description: {
		color: 'toggle colored output',
		cwd: 'directory to execute in',
		edit:
			'read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG',
		extends: 'array of shareable configurations to extend',
		from: 'lower end of the commit range to lint; applies if edit=false',
		to: 'upper end of the commit range to lint; applies if edit=false',
		quiet: 'toggle console output',
		'parser-preset':
			'configuration preset to use for conventional-commits-parser'
	},
	default: {
		color: true,
		cwd: process.cwd(),
		edit: false,
		from: null,
		to: null,
		quiet: false
	},
	unknown(arg) {
		throw new Error(`unknown flags: ${arg}`);
	}
};

const cli = meow(
	{
		help: `[input] reads from stdin if --edit, --from and --to are omitted\n${help(
			configuration
		)}`,
		description: `${pkg.name}@${pkg.version} - ${pkg.description}`
	},
	configuration
);

const load = (seed, opts) => core.load(seed, opts);

function main(options) {
	normalizeOptions(options);

	const raw = options.input;
	const flags = options.flags;
	const fromStdin = rules.fromStdin(raw, flags);

	const range = pick(flags, 'edit', 'from', 'to');
	const input = fromStdin ? stdin() : core.read(range, {cwd: flags.cwd});
	const fmt = new chalk.constructor({enabled: flags.color});

	return input.then(raw => (Array.isArray(raw) ? raw : [raw])).then(messages =>
		Promise.all(
			messages.map(commit => {
				return load(getSeed(flags), {cwd: flags.cwd})
					.then(loaded => {
						const parserOpts = selectParserOpts(loaded.parserPreset);
						const opts = parserOpts ? {parserOpts} : undefined;
						return core.lint(commit, loaded.rules, opts);
					})
					.then(report => {
						const formatted = core.format(report, {color: flags.color});

						if (!flags.quiet) {
							console.log(
								`${fmt.grey('⧗')}   input: ${fmt.bold(commit.split('\n')[0])}`
							);
							console.log(formatted.join('\n'));
						}

						if (report.errors.length > 0) {
							const error = new Error(formatted[formatted.length - 1]);
							error.type = pkg.name;
							throw error;
						}
						return console.log('');
					});
			})
		)
	);
}

function normalizeOptions(options) {
	const flags = options.flags;

	// The `edit` flag is either a boolean or a string but we are only allowed
	// to specify one of them in minimist
	if (flags.edit === '') {
		flags.edit = true;
		flags.e = true;
	}
}

function getSeed(seed) {
	const e = Array.isArray(seed.extends) ? seed.extends : [seed.extends];
	const n = e.filter(i => typeof i === 'string');
	return n.length > 0
		? {extends: n, parserPreset: seed.parserPreset}
		: {parserPreset: seed.parserPreset};
}

// Start the engine
main(cli).catch(err =>
	setTimeout(() => {
		if (err.type === pkg.name) {
			process.exit(1);
		}
		throw err;
	})
);

function selectParserOpts(parserPreset) {
	if (typeof parserPreset !== 'object') {
		return undefined;
	}

	const opts = parserPreset.opts;

	if (typeof opts !== 'object') {
		return undefined;
	}

	return opts.parserOpts;
}

// Catch unhandled rejections globally
process.on('unhandledRejection', (reason, promise) => {
	console.log('Unhandled Rejection at: Promise ', promise, ' reason: ', reason);
	throw reason;
});