Require.js

/* eslint-disable global-require, import/no-dynamic-require */
import path from 'path';
import {execSync} from 'child_process';

/**
 * Helpers for requiring global & other files
 * @namespace Require
 */

// assign properties to global to avoid issues in case of multiple sm-utils in node_modules
// NOTE: don't change globalDataKey or globalData properties
// it should be consistent across multiple sm-utils versions
const globalDataKey = '_SmUtils_Require';
if (!global[globalDataKey]) global[globalDataKey] = {};
const globalData = global[globalDataKey];

function getNpmPrefix() {
	if (globalData.npmPrefix != null) return globalData.npmPrefix;

	let npmPrefix;
	try {
		npmPrefix = execSync('npm config get prefix').toString().trim();
		if (npmPrefix) npmPrefix = path.join(npmPrefix, 'lib/node_modules');
	}
	catch (e) {
		npmPrefix = '';
	}

	globalData.npmPrefix = npmPrefix;
	return npmPrefix;
}

function getYarnPrefix() {
	if (globalData.yarnPrefix != null) return globalData.yarnPrefix;

	let yarnPrefix;
	try {
		yarnPrefix = execSync('yarn global dir').toString().trim();
		if (yarnPrefix) yarnPrefix = path.join(yarnPrefix, 'node_modules');
	}
	catch (e) {
		yarnPrefix = '';
	}

	globalData.yarnPrefix = yarnPrefix;
	return yarnPrefix;
}

/**
 * Resolve path of a global module
 * @memberof Require
 * @param {string} moduleName
 * @return {string} path of module
 */
function resolveGlobal(moduleName) {
	const npmPrefix = getNpmPrefix();
	if (npmPrefix) {
		try {
			return require.resolve(path.join(npmPrefix, moduleName));
		}
		catch (e) {
			// Ignore errors
		}
	}

	const yarnPrefix = getYarnPrefix();
	if (yarnPrefix) {
		try {
			return require.resolve(path.join(yarnPrefix, moduleName));
		}
		catch (e) {
			// Ignore errors
		}
	}

	throw new Error(`[Require Global] Module Not Found ${moduleName}`);
}

/**
 * Resolve path of a local or global module
 * @memberof Require
 * @param {string} moduleName
 * @param {object} [options={}]
 * @param {boolean} [options.useNative=true] Use local module if available
 * @return {string} path of module
 */
function resolve(moduleName, options = {}) {
	if (options.useNative !== false) {
		options.useNative = true;
	}

	if (options.useNative) {
		try {
			return require.resolve(moduleName);
		}
		catch (e) {
			// Ignore errors
		}
	}

	return resolveGlobal(moduleName);
}

/**
 * Require a module from local or global
 * @memberof Require
 * @param {string} moduleName
 * @param {object} [options={}]
 * @param {boolean} [options.useNative=true] Use local module if available
 * @return {any} the module required
 */
function requireModule(moduleName, options = {}) {
	return require(resolve(moduleName, options));
}

/**
 * Require a global module
 * @memberof Require
 * @param {string} moduleName
 * @return {any} the module required
 */
function requireGlobal(moduleName) {
	return require(resolveGlobal(moduleName));
}

module.exports = {
	resolve,
	resolveGlobal,
	requireGlobal,
	/**
	 * @type {Require.requireGlobal}
	 * @memberof Require
	 */
	global: requireGlobal,
	/**
	 * @type {Require.requireModule}
	 * @memberof Require
	 */
	require: requireModule,
};