var findSymbolNameRegex = /^Symbol\((.*)\)$/;
function getSymbolDescription(s) {
    if (s.description !== undefined)
        return s.description;
    var m = findSymbolNameRegex.exec(String(s));
    return m && m[1].length ? m[1] : null;
}
function stringifyNumber(numValue) {
    switch (numValue) {
        case 0:
            return 1 / numValue === Number.NEGATIVE_INFINITY ? '-0' : '0';
        case Number.NEGATIVE_INFINITY:
            return 'Number.NEGATIVE_INFINITY';
        case Number.POSITIVE_INFINITY:
            return 'Number.POSITIVE_INFINITY';
        default:
            return numValue === numValue ? String(numValue) : 'Number.NaN';
    }
}
export function stringifyInternal(value, previousValues) {
    var currentValues = previousValues.concat([value]);
    if (typeof value === 'object') {
        if (previousValues.indexOf(value) !== -1)
            return '[cyclic]';
    }
    switch (Object.prototype.toString.call(value)) {
        case '[object Array]':
            return "[" + value.map(function (v) { return stringifyInternal(v, currentValues); }).join(',') + "]";
        case '[object BigInt]':
            return value + "n";
        case '[object Boolean]':
            return typeof value === 'boolean' ? JSON.stringify(value) : "new Boolean(" + JSON.stringify(value) + ")";
        case '[object Date]': {
            var d = value;
            return Number.isNaN(d.getTime()) ? "new Date(NaN)" : "new Date(" + JSON.stringify(d.toISOString()) + ")";
        }
        case '[object Map]':
            return "new Map(" + stringifyInternal(Array.from(value), currentValues) + ")";
        case '[object Null]':
            return "null";
        case '[object Number]':
            return typeof value === 'number' ? stringifyNumber(value) : "new Number(" + stringifyNumber(Number(value)) + ")";
        case '[object Object]': {
            try {
                var toStringAccessor = value.toString;
                if (typeof toStringAccessor === 'function' && toStringAccessor !== Object.prototype.toString) {
                    return value.toString();
                }
            }
            catch (err) {
                return '[object Object]';
            }
            var rawRepr = '{' +
                Object.keys(value)
                    .map(function (k) {
                    return (k === '__proto__' ? '["__proto__"]' : JSON.stringify(k)) + ":" + stringifyInternal(value[k], currentValues);
                })
                    .join(',') +
                '}';
            if (Object.getPrototypeOf(value) === null) {
                return rawRepr === '{}' ? 'Object.create(null)' : "Object.assign(Object.create(null)," + rawRepr + ")";
            }
            return rawRepr;
        }
        case '[object Set]':
            return "new Set(" + stringifyInternal(Array.from(value), currentValues) + ")";
        case '[object String]':
            return typeof value === 'string' ? JSON.stringify(value) : "new String(" + JSON.stringify(value) + ")";
        case '[object Symbol]': {
            var s = value;
            if (Symbol.keyFor(s) !== undefined) {
                return "Symbol.for(" + JSON.stringify(Symbol.keyFor(s)) + ")";
            }
            var desc = getSymbolDescription(s);
            return desc !== null ? "Symbol(" + JSON.stringify(desc) + ")" : "Symbol()";
        }
        case '[object Undefined]':
            return "undefined";
        default:
            try {
                return value.toString();
            }
            catch (_a) {
                return Object.prototype.toString.call(value);
            }
    }
}
export function stringify(value) {
    return stringifyInternal(value, []);
}
