/*
 this file contains code from strip-bom and strip-json-comments by Sindre Sorhus
 https://github.com/sindresorhus/strip-json-comments/blob/v4.0.0/index.js
 https://github.com/sindresorhus/strip-bom/blob/v5.0.0/index.js
 licensed under MIT, see ../LICENSE
*/

/**
 * convert content of tsconfig.json to regular json
 *
 * @param {string} tsconfigJson - content of tsconfig.json
 * @returns {string} content as regular json, comments and dangling commas have been replaced with whitespace
 */
export function toJson(tsconfigJson: string): string {
	const stripped = stripDanglingComma(stripJsonComments(stripBom(tsconfigJson)));
	if (stripped.trim() === '') {
		// only whitespace left after stripping, return empty object so that JSON.parse still works
		return '{}';
	} else {
		return stripped;
	}
}

/**
 * replace dangling commas from pseudo-json string with single space
 * implementation heavily inspired by strip-json-comments
 */
function stripDanglingComma(pseudoJson: string) {
	let insideString = false;
	let offset = 0;
	let result = '';
	let danglingCommaPos = null;
	for (let i = 0; i < pseudoJson.length; i++) {
		const currentCharacter = pseudoJson[i];
		if (currentCharacter === '"') {
			const escaped = isEscaped(pseudoJson, i);
			if (!escaped) {
				insideString = !insideString;
			}
		}
		if (insideString) {
			danglingCommaPos = null;
			continue;
		}
		if (currentCharacter === ',') {
			danglingCommaPos = i;
			continue;
		}
		if (danglingCommaPos) {
			if (currentCharacter === '}' || currentCharacter === ']') {
				result += pseudoJson.slice(offset, danglingCommaPos) + ' ';
				offset = danglingCommaPos + 1;
				danglingCommaPos = null;
			} else if (!currentCharacter.match(/\s/)) {
				danglingCommaPos = null;
			}
		}
	}
	return result + pseudoJson.substring(offset);
}

// start strip-json-comments
function isEscaped(jsonString: string, quotePosition: number) {
	let index = quotePosition - 1;
	let backslashCount = 0;

	while (jsonString[index] === '\\') {
		index -= 1;
		backslashCount += 1;
	}

	return Boolean(backslashCount % 2);
}

function strip(string: string, start?: number, end?: number) {
	return string.slice(start, end).replace(/\S/g, ' ');
}

const singleComment = Symbol('singleComment');
const multiComment = Symbol('multiComment');

function stripJsonComments(jsonString: string) {
	let isInsideString = false;
	let isInsideComment: false | symbol = false;
	let offset = 0;
	let result = '';

	for (let index = 0; index < jsonString.length; index++) {
		const currentCharacter = jsonString[index];
		const nextCharacter = jsonString[index + 1];

		if (!isInsideComment && currentCharacter === '"') {
			const escaped = isEscaped(jsonString, index);
			if (!escaped) {
				isInsideString = !isInsideString;
			}
		}

		if (isInsideString) {
			continue;
		}

		if (!isInsideComment && currentCharacter + nextCharacter === '//') {
			result += jsonString.slice(offset, index);
			offset = index;
			isInsideComment = singleComment;
			index++;
		} else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
			index++;
			isInsideComment = false;
			result += strip(jsonString, offset, index);
			offset = index;
		} else if (isInsideComment === singleComment && currentCharacter === '\n') {
			isInsideComment = false;
			result += strip(jsonString, offset, index);
			offset = index;
		} else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
			result += jsonString.slice(offset, index);
			offset = index;
			isInsideComment = multiComment;
			index++;
		} else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
			index++;
			isInsideComment = false;
			result += strip(jsonString, offset, index + 1);
			offset = index + 1;
		}
	}

	return result + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
}
// end strip-json-comments

// start strip-bom
function stripBom(string: string) {
	// Catches EFBBBF (UTF-8 BOM) because the buffer-to-string
	// conversion translates it to FEFF (UTF-16 BOM).
	if (string.charCodeAt(0) === 0xfeff) {
		return string.slice(1);
	}
	return string;
}
// end strip-bom
