'use strict';

const object = {}; const hasOwnProperty = object.hasOwnProperty; const forOwn = (object, callback) => {

for (const key in object) {
        if (hasOwnProperty.call(object, key)) {
                callback(key, object[key]);
        }
}

};

const extend = (destination, source) => {

if (!source) {
        return destination;
}
forOwn(source, (key, value) => {
        destination[key] = value;
});
return destination;

};

const forEach = (array, callback) => {

const length = array.length;
let index = -1;
while (++index < length) {
        callback(array[index]);
}

};

const toString = object.toString; const isArray = Array.isArray; const isBuffer = Buffer.isBuffer; const isObject = (value) => {

// This is a very simple check, but it’s good enough for what we need.
return toString.call(value) == '[object Object]';

}; const isString = (value) => {

return typeof value == 'string' ||
        toString.call(value) == '[object String]';

}; const isNumber = (value) => {

return typeof value == 'number' ||
        toString.call(value) == '[object Number]';

}; const isFunction = (value) => {

return typeof value == 'function';

}; const isMap = (value) => {

return toString.call(value) == '[object Map]';

}; const isSet = (value) => {

return toString.call(value) == '[object Set]';

};

/————————————————————————–/

// mathiasbynens.be/notes/javascript-escapes#single const singleEscapes = {

'"': '\\"',
'\'': '\\\'',
'\\': '\\\\',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t'
// `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'.
// '\v': '\\x0B'

}; const regexSingleEscape = /[“'\bfnrt]/;

const regexDigit = /[0-9]/; const regexWhitelist = /[ !#-&(-[]-_a-~]/;

const jsesc = (argument, options) => {

const increaseIndentation = () => {
        oldIndent = indent;
        ++options.indentLevel;
        indent = options.indent.repeat(options.indentLevel)
};
// Handle options
const defaults = {
        'escapeEverything': false,
        'minimal': false,
        'isScriptContext': false,
        'quotes': 'single',
        'wrap': false,
        'es6': false,
        'json': false,
        'compact': true,
        'lowercaseHex': false,
        'numbers': 'decimal',
        'indent': '\t',
        'indentLevel': 0,
        '__inline1__': false,
        '__inline2__': false
};
const json = options && options.json;
if (json) {
        defaults.quotes = 'double';
        defaults.wrap = true;
}
options = extend(defaults, options);
if (
        options.quotes != 'single' &&
        options.quotes != 'double' &&
        options.quotes != 'backtick'
) {
        options.quotes = 'single';
}
const quote = options.quotes == 'double' ?
        '"' :
        (options.quotes == 'backtick' ?
                '`' :
                '\''
        );
const compact = options.compact;
const lowercaseHex = options.lowercaseHex;
let indent = options.indent.repeat(options.indentLevel);
let oldIndent = '';
const inline1 = options.__inline1__;
const inline2 = options.__inline2__;
const newLine = compact ? '' : '\n';
let result;
let isEmpty = true;
const useBinNumbers = options.numbers == 'binary';
const useOctNumbers = options.numbers == 'octal';
const useDecNumbers = options.numbers == 'decimal';
const useHexNumbers = options.numbers == 'hexadecimal';

if (json && argument && isFunction(argument.toJSON)) {
        argument = argument.toJSON();
}

if (!isString(argument)) {
        if (isMap(argument)) {
                if (argument.size == 0) {
                        return 'new Map()';
                }
                if (!compact) {
                        options.__inline1__ = true;
                        options.__inline2__ = false;
                }
                return 'new Map(' + jsesc(Array.from(argument), options) + ')';
        }
        if (isSet(argument)) {
                if (argument.size == 0) {
                        return 'new Set()';
                }
                return 'new Set(' + jsesc(Array.from(argument), options) + ')';
        }
        if (isBuffer(argument)) {
                if (argument.length == 0) {
                        return 'Buffer.from([])';
                }
                return 'Buffer.from(' + jsesc(Array.from(argument), options) + ')';
        }
        if (isArray(argument)) {
                result = [];
                options.wrap = true;
                if (inline1) {
                        options.__inline1__ = false;
                        options.__inline2__ = true;
                }
                if (!inline2) {
                        increaseIndentation();
                }
                forEach(argument, (value) => {
                        isEmpty = false;
                        if (inline2) {
                                options.__inline2__ = false;
                        }
                        result.push(
                                (compact || inline2 ? '' : indent) +
                                jsesc(value, options)
                        );
                });
                if (isEmpty) {
                        return '[]';
                }
                if (inline2) {
                        return '[' + result.join(', ') + ']';
                }
                return '[' + newLine + result.join(',' + newLine) + newLine +
                        (compact ? '' : oldIndent) + ']';
        } else if (isNumber(argument)) {
                if (json) {
                        // Some number values (e.g. `Infinity`) cannot be represented in JSON.
                        return JSON.stringify(argument);
                }
                if (useDecNumbers) {
                        return String(argument);
                }
                if (useHexNumbers) {
                        let hexadecimal = argument.toString(16);
                        if (!lowercaseHex) {
                                hexadecimal = hexadecimal.toUpperCase();
                        }
                        return '0x' + hexadecimal;
                }
                if (useBinNumbers) {
                        return '0b' + argument.toString(2);
                }
                if (useOctNumbers) {
                        return '0o' + argument.toString(8);
                }
        } else if (!isObject(argument)) {
                if (json) {
                        // For some values (e.g. `undefined`, `function` objects),
                        // `JSON.stringify(value)` returns `undefined` (which isn’t valid
                        // JSON) instead of `'null'`.
                        return JSON.stringify(argument) || 'null';
                }
                return String(argument);
        } else { // it’s an object
                result = [];
                options.wrap = true;
                increaseIndentation();
                forOwn(argument, (key, value) => {
                        isEmpty = false;
                        result.push(
                                (compact ? '' : indent) +
                                jsesc(key, options) + ':' +
                                (compact ? '' : ' ') +
                                jsesc(value, options)
                        );
                });
                if (isEmpty) {
                        return '{}';
                }
                return '{' + newLine + result.join(',' + newLine) + newLine +
                        (compact ? '' : oldIndent) + '}';
        }
}

const string = argument;
// Loop over each code unit in the string and escape it
let index = -1;
const length = string.length;
result = '';
while (++index < length) {
        const character = string.charAt(index);
        if (options.es6) {
                const first = string.charCodeAt(index);
                if ( // check if it’s the start of a surrogate pair
                        first >= 0xD800 && first <= 0xDBFF && // high surrogate
                        length > index + 1 // there is a next code unit
                ) {
                        const second = string.charCodeAt(index + 1);
                        if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
                                // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
                                const codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
                                let hexadecimal = codePoint.toString(16);
                                if (!lowercaseHex) {
                                        hexadecimal = hexadecimal.toUpperCase();
                                }
                                result += '\\u{' + hexadecimal + '}';
                                ++index;
                                continue;
                        }
                }
        }
        if (!options.escapeEverything) {
                if (regexWhitelist.test(character)) {
                        // It’s a printable ASCII character that is not `"`, `'` or `\`,
                        // so don’t escape it.
                        result += character;
                        continue;
                }
                if (character == '"') {
                        result += quote == character ? '\\"' : character;
                        continue;
                }
                if (character == '`') {
                        result += quote == character ? '\\`' : character;
                        continue;
                }
                if (character == '\'') {
                        result += quote == character ? '\\\'' : character;
                        continue;
                }
        }
        if (
                character == '\0' &&
                !json &&
                !regexDigit.test(string.charAt(index + 1))
        ) {
                result += '\\0';
                continue;
        }
        if (regexSingleEscape.test(character)) {
                // no need for a `hasOwnProperty` check here
                result += singleEscapes[character];
                continue;
        }
        const charCode = character.charCodeAt(0);
        if (options.minimal && charCode != 0x2028 && charCode != 0x2029) {
                result += character;
                continue;
        }
        let hexadecimal = charCode.toString(16);
        if (!lowercaseHex) {
                hexadecimal = hexadecimal.toUpperCase();
        }
        const longhand = hexadecimal.length > 2 || json;
        const escaped = '\\' + (longhand ? 'u' : 'x') +
                ('0000' + hexadecimal).slice(longhand ? -4 : -2);
        result += escaped;
        continue;
}
if (options.wrap) {
        result = quote + result + quote;
}
if (quote == '`') {
        result = result.replace(/\$\{/g, '\\\$\{');
}
if (options.isScriptContext) {
        // https://mathiasbynens.be/notes/etago
        return result
                .replace(/<\/(script|style)/gi, '<\\/$1')
                .replace(/<!--/g, json ? '\\u003C!--' : '\\x3C!--');
}
return result;

};

jsesc.version = '2.5.2';

module.exports = jsesc;