/* global CssTools */

import appModulePath from 'app-module-path';
import Future from 'fibers/future';
import fs from 'fs';
import path from 'path';
import postCSS from 'postcss';
import sourcemap from 'source-map';

appModulePath.addPath(`${ process.cwd() }/node_modules/`);

const postCSSConfigFile = path.resolve(process.cwd(), '.postcssrc');

const postCSSConfig = JSON.parse(fs.readFileSync(postCSSConfigFile));

const getPostCSSPlugins = () => {
	const plugins = [];
	if (postCSSConfig.plugins) {
		Object.keys(postCSSConfig.plugins).forEach((pluginName) => {
			const postCSSPlugin = require(pluginName);
			if (postCSSPlugin && postCSSPlugin().postcssPlugin) {
				plugins.push(postCSSPlugin(postCSSConfig.plugins ? postCSSConfig.plugins[pluginName] : {}));
			}
		});
	}

	return plugins;
};

const getExcludedPackages = () => {
	if (postCSSConfig.excludedPackages && postCSSConfig.excludedPackages instanceof Array) {
		return postCSSConfig.excludedPackages;
	}

	return false;
};

const isNotInExcludedPackages = (excludedPackages, pathInBundle) => {
	let exclArr = [];
	if (excludedPackages && excludedPackages instanceof Array) {
		exclArr = excludedPackages.map((packageName) => pathInBundle && pathInBundle.indexOf(`packages/${ packageName.replace(':', '_') }`) > -1);
	}

	return exclArr.indexOf(true) === -1;
};

const isNotImport = (inputFileUrl) => !(/\.import\.css$/.test(inputFileUrl) || /(?:^|\/)imports\//.test(inputFileUrl));

const mergeCss = (css) => {
	const originals = {};
	const excludedPackagesArr = getExcludedPackages();

	const cssAsts = css.map((file) => {
		const filename = file.getPathInBundle();
		originals[filename] = file;

		const f = new Future;

		let css;
		let postres;
		const isFileForPostCSS = isNotInExcludedPackages(excludedPackagesArr, file.getPathInBundle());
		postCSS(isFileForPostCSS ? getPostCSSPlugins() : [])
			.process(file.getContentsAsString(), {
				from: process.cwd() + file._source.url.replace('_', '-'),
			})
			.then((result) => {
				result.warnings().forEach((warn) => {
					process.stderr.write(warn.toString());
				});
				f.return(result);
			})
			.catch((error) => {
				if (error.name === 'CssSyntaxError') {
					error.message = `${ error.message }\n\nCss Syntax Error.\n\n${ error.message }${ error.showSourceCode() }`;
				}
				f.return(error);
			});

		try {
			const parseOptions = {
				source: filename,
				position: true,
			};

			postres = f.wait();

			if (postres.name === 'CssSyntaxError') {
				throw postres;
			}

			css = postres.css;

			const ast = CssTools.parseCss(css, parseOptions);
			ast.filename = filename;

			return ast;
		} catch (e) {
			if (e.name === 'CssSyntaxError') {
				file.error({
					message: e.message,
					line: e.line,
					column: e.column,
				});
			} else if (e.reason) {
				file.error({
					message: e.reason,
					line: e.line,
					column: e.column,
				});
			} else {
				file.error({
					message: e.message,
				});
			}

			return {
				type: 'stylesheet',
				stylesheet: { rules: [] },
				filename,
			};
		}
	});
	const mergedCssAst = CssTools.mergeCssAsts(cssAsts, (filename, msg) => {
		console.log(`${ filename }: warn: ${ msg }`);
	});

	const stringifiedCss = CssTools.stringifyCss(mergedCssAst, {
		sourcemap: true,
		inputSourcemaps: false,
	});

	if (!stringifiedCss.code) {
		return { code: '' };
	}

	stringifiedCss.map.sourcesContent =
		stringifiedCss.map.sources.map((filename) => originals[filename].getContentsAsString());

	const newMap = sourcemap.SourceMapGenerator.fromSourceMap(new sourcemap.SourceMapConsumer(stringifiedCss.map));

	Object.keys(originals).forEach((name) => {
		const file = originals[name];
		if (!file.getSourceMap()) {
			return false;
		}
		try {
			newMap.applySourceMap(new sourcemap.SourceMapConsumer(file.getSourceMap()), name);
		} catch (err) {
			// If can't apply the source map, silently drop it.
		}
	});

	return {
		code: stringifiedCss.code,
		sourceMap: newMap.toString(),
	};
};

class CssToolsMinifier {
	processFilesForBundle(files, options) {
		const mode = options.minifyMode;

		if (!files.length) {
			return false;
		}

		const filesToMerge = [];

		files.forEach((file) => {
			if (isNotImport(file._source.url)) {
				filesToMerge.push(file);
			}
		});

		const merged = mergeCss(filesToMerge);

		if (mode === 'development') {
			files[0].addStylesheet({
				data: merged.code,
				sourceMap: merged.sourceMap,
				path: 'merged-stylesheets.css',
			});
			return false;
		}

		const minifiedFiles = CssTools.minifyCss(merged.code);

		if (files.length) {
			minifiedFiles.forEach((minified) => {
				files[0].addStylesheet({
					data: minified,
				});
			});
		}
	}
}

Plugin.registerMinifier({ extensions: ['css'] }, () => new CssToolsMinifier);
