/**
 * @license Copyright (c) 2017-2025, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md.
 */

'use strict';

const upath = require( 'upath' );
const fs = require( 'fs-extra' );
const { globSync } = require( 'glob' );
const globToRegexp = require( '../helpers/glob-to-regexp' );

/**
 * Copies src files to dst.
 *
 * @param {String} src Path to source file or directory.
 * @param {String} dst Path to destination file or directory.
 * @param {Object} [options={}]
 * @param {Array.<String>} options.ignored Glob ignore option.
 * @param {Array.<String>} options.allOutputPaths Stores paths of all files, which were already copied to watch for conflicts.
 * @param {Array.<String>|null} [options.match=null] An array containing names of files that should be copied. It allows specifying
 * name of files that will be processed by Umberto.
 * @returns {Promise}
 */
module.exports = ( src, dst, options = {} ) => {
	const {
		ignored = [],
		allOutputPaths = [],
		match = null
	} = options;

	// if src is a single file
	if ( upath.extname( src ) ) {
		// If there already was a file written to this destination, inform about an error.
		if ( allOutputPaths.indexOf( dst ) !== -1 ) {
			return Promise.reject( `File: ${ src } tried to overwrite another file under location: ${ dst }. Try renaming the file.` );
		}

		allOutputPaths.push( dst );

		// Don't copy a file if size and modification time have not changed.
		if ( fs.existsSync( dst ) ) {
			const oldStats = fs.statSync( dst );
			const newStats = fs.statSync( src );

			if ( oldStats.size === newStats.size && oldStats.mtime >= newStats.mtime ) {
				return Promise.resolve();
			}
		}

		if ( !fs.existsSync( upath.dirname( dst ) ) ) {
			fs.mkdirsSync( upath.dirname( dst ) );
		}

		return new Promise( ( resolve, reject ) => {
			const stream = fs.createReadStream( src )
				.pipe( fs.createWriteStream( dst ) );

			stream.on( 'finish', resolveCopiedFile( resolve, src, dst ) );
			stream.on( 'error', reject );
		} );
	}

	// If the `src` is a path to a directory, copy files from the directory.
	const sourceGlobPath = upath.join( src, '**/*' );

	let sourceFilePaths = globSync( sourceGlobPath, {
		ignore: ignored.map( ignoredPath => upath.join( src, ignoredPath ) ),
		nodir: true
	} )
		.map( path => upath.normalize( path ) );

	// If the `match` is an array, remove files that should not be processed.
	if ( match ) {
		// Map specified patterns to RegExps.
		const patterns = match.map( globToRegexp );

		sourceFilePaths = sourceFilePaths.filter( filePath => {
			// Guides are markdown files (`*.md`), and only those will be checked.
			if ( !filePath.endsWith( '.md' ) ) {
				return true;
			}

			return patterns.some( matchPattern => filePath.match( matchPattern ) );
		} );
	}

	const promises = [];

	for ( const filePath of sourceFilePaths ) {
		const relativePath = upath.relative( src, upath.dirname( filePath ) );
		const dirName = upath.join( dst, relativePath );
		const baseName = upath.basename( filePath );
		const outputPath = upath.join( dirName, baseName );

		// If there already was a file written to this destination, inform about an error.
		if ( allOutputPaths.indexOf( outputPath ) !== -1 ) {
			return Promise.reject( [
				`File: ${ filePath } tried to overwrite another file under location: ${ outputPath }.`,
				'Try renaming the file.'
			].join( ' ' ) );
		}

		allOutputPaths.push( outputPath );

		// Don't copy a file if size and modification time have not changed.
		if ( fs.existsSync( outputPath ) ) {
			const oldStats = fs.statSync( outputPath );
			const newStats = fs.statSync( filePath );

			if ( oldStats.size === newStats.size && oldStats.mtime >= newStats.mtime ) {
				continue;
			}
		}

		if ( !fs.existsSync( dirName ) ) {
			fs.mkdirsSync( dirName );
		}

		promises.push( new Promise( ( resolve, reject ) => {
			const stream = fs.createReadStream( filePath )
				.pipe( fs.createWriteStream( outputPath ) );

			stream.on( 'finish', resolveCopiedFile( resolve, filePath, outputPath ) );
			stream.on( 'error', reject );
		} ) );
	}

	return Promise.all( promises );
};

// TODO: Could be replaced with the `copyFile()` util?
function resolveCopiedFile( resolve, sourcePath, destinationPath ) {
	return () => resolve( { sourcePath, destinationPath } );
}
