text: /**
   * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
   */
  
  /* eslint-env node */
  
  const path = require( 'path' );
  const fs = require( 'fs' );
  const webpack = require( 'webpack' );
  const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
  const { version } = require( '../../package.json' );
  
  const DESTINATION_DIRECTORY = path.join( __dirname, '..', '..', 'build', 'content-styles' );
  const DOCUMENTATION_URL = 'https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html';
  const VARIABLE_DEFINITION_REGEXP = /(--[\w-]+):\s+(.*);/g;
  const VARIABLE_USAGE_REGEXP = /var\((--[\w-]+)\)/g;
  
  const contentRules = {
  	selector: [],
  	variables: []
  };
  
  const webpackConfig = getWebpackConfig();
  const packagesPath = path.join( process.cwd(), 'packages' );
  
  runWebpack( webpackConfig )
  	.then( () => {
  		// All variables are placed inside the `:root` selector. Let's extract their names and values as a map.
  		const cssVariables = new Map( contentRules.variables
  			.map( rule => {
  				// Let's extract all of them as an array of pairs: [ name, value ].
  				const allRules = [];
  				let match;
  
  				while ( ( match = VARIABLE_DEFINITION_REGEXP.exec( rule.css ) ) ) {
  					allRules.push( [ match[ 1 ], match[ 2 ] ] );
  				}
  
  				return allRules;
  			} )
  			.reduce( ( previousValue, currentValue ) => {
  				// And simplify nested arrays as a flattened array.
  				previousValue.push( ...currentValue );
  
  				return previousValue;
  			}, [] ) );
  
  		// CSS variables that are used by the `.ck-content` selector.
  		const usedVariables = new Set();
  
  		const selectorCss = contentRules.selector
  			.map( rule => {
  				// Removes all comments from the rule definition.
  				const cssAsArray = rule.css.replace( /\/\*[^*]+\*\//g, '' ).split( '\n' );
  
  				// We want to fix invalid indentations. We need to find a number of how many indentations we want to remove.
  				// Because the last line ends the block, we can use this value.
  				const lastLineIndent = cssAsArray[ cssAsArray.length - 1 ].length - 1;
  
  				const css = cssAsArray
  					.filter( line => line.trim().length > 0 )
  					.map( ( line, index ) => {
  						// Do not touch the first line. It is always correct.
  						if ( index === 0 ) {
  							return line;
  						}
  
  						return line.slice( lastLineIndent );
  					} )
  					.join( '\n' );
  
  				return `/* ${ rule.file.replace( packagesPath + path.sep, '' ) } */\n${ css }`;
  			} )
  			.filter( rule => {
  				// 1st: path to the CSS file, 2nd: selector definition - start block, 3rd: end block
  				// If the rule contains only 3 lines, it means that it does not define any rules.
  				return rule.split( '\n' ).length > 3;
  			} )
  			.join( '\n' );
  
  		// Find all CSS variables inside the `.ck-content` selector.
  		let match;
  
  		while ( ( match = VARIABLE_USAGE_REGEXP.exec( selectorCss ) ) ) {
  			usedVariables.add( match[ 1 ] );
  		}
  
  		// We need to also look at whether any of the used variables requires the value of other variables.
  		let clearRun = false;
  
  		// We need to process all variables as long as the entire collection won't be changed.
  		while ( !clearRun ) {
  			clearRun = true;
  
  			// For every used variable...
  			for ( const variable of usedVariables ) {
  				const value = cssVariables.get( variable );
  
  				let match;
  
  				// ...find its value and check whether it requires another variable.
  				while ( ( match = VARIABLE_USAGE_REGEXP.exec( value ) ) ) {
  					// If so, mark the entire `while()` block as it should be checked once again.
  					// Also, add the new variable to the used variables collection.
  					if ( !usedVariables.has( match[ 1 ] ) ) {
  						clearRun = false;
  						usedVariables.add( match[ 1 ] );
  					}
  				}
  			}
  		}
  
  		// Build the final content of the CSS file.
  		let data = [
  			'/*',
  			` * CKEditor 5 (v${ version }) content styles.`,
  			` * Generated on ${ new Date().toUTCString() }.`,
  			` * For more information, check out ${ DOCUMENTATION_URL }`,
  			' */\n\n',
  		].join( '\n' );
  
  		data += ':root {\n';
  
  		for ( const variable of [ ...usedVariables ].sort() ) {
  			data += `\t${ variable }: ${ cssVariables.get( variable ) };\n`;
  		}
  
  		data += '}\n\n';
  		data += selectorCss;
  
  		return writeFile( path.join( DESTINATION_DIRECTORY, 'content-styles.css' ), data );
  	} )
  	.then( () => {
  		console.log( `Content styles have been extracted to ${ path.join( DESTINATION_DIRECTORY, 'content-styles.css' ) }` );
  	} )
  	.catch( err => {
  		console.log( err );
  	} );
  
  /**
   * Prepares the configuration for webpack.
   *
   * @returns {Object}
   */
  function getWebpackConfig() {
  	const postCssConfig = styles.getPostCssConfig( {
  		themeImporter: {
  			themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
  		},
  		minify: false
  	} );
  
  	const contentStylesPlugin = require( './content-styles/list-content-styles' )( { contentRules } );
  
  	postCssConfig.plugins.push( contentStylesPlugin );
  
  	return {
  		mode: 'development',
  
  		devtool: 'source-map',
  
  		entry: {
  			ckeditor5: path.join( __dirname, 'content-styles', 'ckeditor.js' )
  		},
  
  		output: {
  			path: DESTINATION_DIRECTORY,
  			filename: '[name].js'
  		},
  
  		// Configure the paths so building CKEditor 5 snippets work even if the script
  		// is triggered from a directory outside `ckeditor5` (e.g. in a multi-project case).
  		resolve: {
  			modules: getModuleResolvePaths()
  		},
  
  		resolveLoader: {
  			modules: getModuleResolvePaths()
  		},
  
  		module: {
  			rules: [
  				{
  					test: /\.svg$/,
  					use: [ 'raw-loader' ]
  				},
  				{
  					test: /\.css$/,
  					use: [
  						'style-loader',
  						{
  							loader: 'postcss-loader',
  							options: postCssConfig
  						}
  					]
  				}
  			]
  		}
  	};
  }
  
  /**
   * @param {Object} webpackConfig
   * @returns {Promise}
   */
  function runWebpack( webpackConfig ) {
  	return new Promise( ( resolve, reject ) => {
  		webpack( webpackConfig, ( err, stats ) => {
  			if ( err ) {
  				reject( err );
  			} else if ( stats.hasErrors() ) {
  				reject( new Error( stats.toString() ) );
  			} else {
  				resolve();
  			}
  		} );
  	} );
  }
  
  /**
   * @returns {Array.<String>}
   */
  function getModuleResolvePaths() {
  	return [
  		path.resolve( __dirname, '..', '..', 'node_modules' ),
  		'node_modules'
  	];
  }
  
  function writeFile( file, data ) {
  	return new Promise( ( resolve, reject ) => {
  		fs.writeFile( file, data, err => {
  			if ( err ) {
  				return reject( err );
  			}
  
  			return resolve();
  		} );
  	} );
  }
  