/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

//@ts-check

const fs = require('fs');
const path = require('path');
const cp = require('child_process');
const esbuild = require('esbuild');
/** @type {any} */
const alias = require('esbuild-plugin-alias');

const REPO_ROOT = path.join(__dirname, '../');
exports.REPO_ROOT = REPO_ROOT;

const existingDirCache = new Set();
/**
 * @param {string} dirname
 */
function ensureDir(dirname) {
	/** @type {string[]} */
	const dirs = [];

	while (dirname.length > REPO_ROOT.length) {
		dirs.push(dirname);
		dirname = path.dirname(dirname);
	}
	dirs.reverse();
	dirs.forEach((dir) => {
		if (!existingDirCache.has(dir)) {
			try {
				fs.mkdirSync(dir);
			} catch (err) {}
			existingDirCache.add(dir);
		}
	});
}
exports.ensureDir = ensureDir;

/**
 * Copy a file.
 *
 * @param {string} _source
 * @param {string} _destination
 */
function copyFile(_source, _destination) {
	const source = path.join(REPO_ROOT, _source);
	const destination = path.join(REPO_ROOT, _destination);

	ensureDir(path.dirname(destination));
	fs.writeFileSync(destination, fs.readFileSync(source));

	console.log(`Copied ${_source} to ${_destination}`);
}
exports.copyFile = copyFile;

/**
 * Remove a directory and all its contents.
 *
 * @param {string} _dirPath
 */
function removeDir(_dirPath) {
	const dirPath = path.join(REPO_ROOT, _dirPath);
	if (!fs.existsSync(dirPath)) {
		return;
	}
	rmDir(dirPath);
	console.log(`Deleted ${_dirPath}`);

	/**
	 * @param {string} dirPath
	 */
	function rmDir(dirPath) {
		const entries = fs.readdirSync(dirPath);
		for (const entry of entries) {
			const filePath = path.join(dirPath, entry);
			if (fs.statSync(filePath).isFile()) {
				fs.unlinkSync(filePath);
			} else {
				rmDir(filePath);
			}
		}
		fs.rmdirSync(dirPath);
	}
}
exports.removeDir = removeDir;

/**
 * Launch the typescript compiler synchronously over a project.
 *
 * @param {string} _projectPath
 */
function tsc(_projectPath) {
	const projectPath = path.join(REPO_ROOT, _projectPath);
	console.log(`Launching compiler at ${_projectPath}...`);
	cp.spawnSync(
		process.execPath,
		[path.join(__dirname, '../node_modules/typescript/lib/tsc.js'), '-p', projectPath],
		{ stdio: 'inherit' }
	);
	console.log(`Compiled ${_projectPath}`);
}
exports.tsc = tsc;

/**
 * Launch prettier on a specific file.
 *
 * @param {string} _filePath
 */
function prettier(_filePath) {
	const filePath = path.join(REPO_ROOT, _filePath);
	cp.spawnSync(
		process.execPath,
		[path.join(__dirname, '../node_modules/prettier/bin-prettier.js'), '--write', filePath],
		{ stdio: 'inherit' }
	);

	console.log(`Ran prettier over ${_filePath}`);
}
exports.prettier = prettier;

/**
 * Transform an external .d.ts file to an internal .d.ts file
 *
 * @param {string} _source
 * @param {string} _destination
 * @param {string} namespace
 */
function dts(_source, _destination, namespace) {
	const source = path.join(REPO_ROOT, _source);
	const destination = path.join(REPO_ROOT, _destination);

	const lines = fs
		.readFileSync(source)
		.toString()
		.split(/\r\n|\r|\n/);

	let result = [
		`/*---------------------------------------------------------------------------------------------`,
		` *  Copyright (c) Microsoft Corporation. All rights reserved.`,
		` *  Licensed under the MIT License. See License.txt in the project root for license information.`,
		` *--------------------------------------------------------------------------------------------*/`,
		``,
		`declare namespace ${namespace} {`
	];
	for (let line of lines) {
		if (/^import/.test(line)) {
			continue;
		}
		if (line === 'export {};') {
			continue;
		}
		line = line.replace(/    /g, '\t');
		line = line.replace(/declare /g, '');
		if (line.length > 0) {
			line = `\t${line}`;
			result.push(line);
		}
	}
	result.push(`}`);
	result.push(``);

	ensureDir(path.dirname(destination));
	fs.writeFileSync(destination, result.join('\n'));

	prettier(_destination);
}
exports.dts = dts;

/**
 * @param {import('esbuild').BuildOptions} options
 */
function build(options) {
	esbuild.build(options).then((result) => {
		if (result.errors.length > 0) {
			console.error(result.errors);
		}
		if (result.warnings.length > 0) {
			console.error(result.warnings);
		}
	});
}
exports.build = build;

/**
 * @param {{
 *   base: string;
 *   entryPoints: string[];
 *   external: string[];
 * }} options
 */
function buildESM(options) {
	build({
		entryPoints: options.entryPoints,
		bundle: true,
		target: 'esnext',
		format: 'esm',
		define: {
			AMD: 'false'
		},
		banner: {
			js: bundledFileHeader
		},
		external: options.external,
		outbase: `src/${options.base}`,
		outdir: `out/release/${options.base}/esm/`,
		plugins: [
			alias({
				'vscode-nls': path.join(__dirname, 'fillers/vscode-nls.ts')
			})
		]
	});
}
exports.buildESM = buildESM;

/**
 * @param {'dev'|'min'} type
 * @param {{
 *   base: string;
 *   entryPoint: string;
 *   amdModuleId: string;
 *   amdDependencies?: string[];
 * }} options
 */
function buildOneAMD(type, options) {
	/** @type {import('esbuild').BuildOptions} */
	const opts = {
		entryPoints: [options.entryPoint],
		bundle: true,
		target: 'esnext',
		format: 'iife',
		define: {
			AMD: 'true'
		},
		globalName: 'moduleExports',
		banner: {
			js: `${bundledFileHeader}define("${options.amdModuleId}",[${(options.amdDependencies || [])
				.map((dep) => `"${dep}"`)
				.join(',')}],()=>{`
		},
		footer: {
			js: 'return moduleExports;\n});'
		},
		outbase: `src/${options.base}`,
		outdir: `out/release/${options.base}/${type}/`,
		plugins: [
			alias({
				'vscode-nls': path.join(__dirname, '../build/fillers/vscode-nls.ts'),
				'monaco-editor-core': path.join(__dirname, '../build/fillers/monaco-editor-core-amd.ts')
			})
		]
	};
	if (type === 'min') {
		opts.minify = true;
	}
	build(opts);
}

/**
 * @param {{
 *   base: string;
 *   entryPoint: string;
 *   amdModuleId: string;
 *   amdDependencies?: string[];
 * }} options
 */
function buildAMD(options) {
	buildOneAMD('dev', options);
	buildOneAMD('min', options);
}
exports.buildAMD = buildAMD;

function getGitVersion() {
	const git = path.join(REPO_ROOT, '.git');
	const headPath = path.join(git, 'HEAD');
	let head;

	try {
		head = fs.readFileSync(headPath, 'utf8').trim();
	} catch (e) {
		return void 0;
	}

	if (/^[0-9a-f]{40}$/i.test(head)) {
		return head;
	}

	const refMatch = /^ref: (.*)$/.exec(head);

	if (!refMatch) {
		return void 0;
	}

	const ref = refMatch[1];
	const refPath = path.join(git, ref);

	try {
		return fs.readFileSync(refPath, 'utf8').trim();
	} catch (e) {
		// noop
	}

	const packedRefsPath = path.join(git, 'packed-refs');
	let refsRaw;

	try {
		refsRaw = fs.readFileSync(packedRefsPath, 'utf8').trim();
	} catch (e) {
		return void 0;
	}

	const refsRegex = /^([0-9a-f]{40})\s+(.+)$/gm;
	let refsMatch;
	const refs = {};

	while ((refsMatch = refsRegex.exec(refsRaw))) {
		refs[refsMatch[2]] = refsMatch[1];
	}

	return refs[ref];
}

const bundledFileHeader = (() => {
	const sha1 = getGitVersion();
	const semver = require('../package.json').version;
	const headerVersion = semver + '(' + sha1 + ')';

	const BUNDLED_FILE_HEADER = [
		'/*!-----------------------------------------------------------------------------',
		' * Copyright (c) Microsoft Corporation. All rights reserved.',
		' * Version: ' + headerVersion,
		' * Released under the MIT license',
		' * https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt',
		' *-----------------------------------------------------------------------------*/',
		''
	].join('\n');

	return BUNDLED_FILE_HEADER;
})();
exports.bundledFileHeader = bundledFileHeader;
