// RFC 8259 calls the following "two character sequence escape representations of some popular characters"
const popularCharsEscapes = {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '"': '\\"',
    '\\': '\\\\'
};
// Escape a single character to be used in a JSON string
// We follow RFC 8259 suggestions and produce the shortest possible escape sequence:
// * for "popular characters", we use the two character sequence escape representation
// * for other characters, we use the \uXXXX numeric escape representation
function escapeChar(char) {
    const popularEscape = popularCharsEscapes[char];
    if (popularEscape !== undefined) {
        // We found a "popular character", use the short two character escape sequence
        return popularEscape;
    }
    else {
        // All other characters should be represented as a numeric \uXXXX escape
        // We are in the Basic Multilingual Plane, so `.charCodeAt` will return the same value as `.codePointAt`
        // We use `.charCodeAt` because it is faster
        const codePoint = char.charCodeAt(0);
        return '\\u' + codePoint.toString(16).padStart(4, '0');
    }
}
// The set of characters that need to be escaped in a JSON string
// We only escape the characters that need to be escaped according to RFC 8259
// This consists of:
// * C0 control characters (U+0000 through U+001F)
// * quotation mark (U+0022) and reverse solidus (U+005C), which are special characters in JSON
// * surrogate code points (U+D800 through U+DFFF), which should only match when unpaired, which is are actually invalid in unicode, but RFC 8259 suggests to escape them when they appear because of errors
// We use the `u` flag to match surrogates only when they are unpaired
// Note: this is one of the rare occasions where we do want control characters in a regex, so we disable the eslint rule
// eslint-disable-next-line no-control-regex
const charsToEscape = /[\u0000-\u001f\ud800-\udfff"\\]/gu;
function JSONStringifyString(str) {
    return `"` + str.replace(charsToEscape, escapeChar) + `"`;
}
function isIndexKey(key) {
    // EcmaScript defines an index key as a string that is a base-10 unsigned integer
    // that does not have a leading zero or is '0'
    return key === '0' || /^[1-9]\d*$/.test(key);
}
function compareObjectKeys(a, b) {
    const aIsIndexKey = isIndexKey(a);
    const bIsIndexKey = isIndexKey(b);
    // follow EcmaScript for sorting keys that could be interpreted as indices:
    // sort keys that could be interpreted as indices before other keys
    // these keys are sorted by numeric value
    return aIsIndexKey && bIsIndexKey ? Math.sign(Number(a) - Number(b)) : aIsIndexKey ? -1 : bIsIndexKey ? 1 : a < b ? -1 : a > b ? 1 : 0;
}
// adapted from https://github.com/epoberezkin/fast-json-stable-stringify
// export so that it can be used in tests
export function stableJSONStringify_portable(data) {
    // track the objects being serialized, so that we don't get into an infinite cycle
    const seen = new Set();
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (function stringify(node) {
        if (typeof node?.toJSON === 'function') {
            node = node.toJSON();
        }
        if (typeof node == 'number')
            return isFinite(node) ? '' + node : 'null';
        if (typeof node === 'string')
            return JSONStringifyString(node);
        if (typeof node === 'boolean')
            return `${node}`;
        if (node === null)
            return 'null';
        if (typeof node === 'object') {
            if (seen.has(node)) {
                throw new TypeError('Converting circular structure to JSON');
            }
            seen.add(node);
            if (Array.isArray(node)) {
                let out = '[';
                for (let idx = 0; idx < node.length; idx++) {
                    if (idx > 0)
                        out += ',';
                    out += stringify(node[idx]) ?? 'null';
                }
                seen.delete(node);
                return out + ']';
            }
            else {
                const keys = Object.keys(node).sort(compareObjectKeys);
                let out = '';
                for (const key of keys) {
                    const value = stringify(node[key]);
                    if (!value)
                        continue;
                    if (out)
                        out += ',';
                    out += JSONStringifyString(key) + ':' + value;
                }
                seen.delete(node);
                return '{' + out + '}';
            }
        }
        if (typeof node === 'bigint') {
            // The ECMAScript JSON.stringify function cannot serialize BigInts
            throw new TypeError('Do not know how to serialize a BigInt');
        }
        if (node === undefined || typeof node === 'function')
            return;
    })(data);
}
//# sourceMappingURL=portable.js.map