/**
 * Stable JSON stringify implementation
 * Based on fast-json-stable-stringify by Evgeny Poberezkin
 * https://github.com/epoberezkin/fast-json-stable-stringify
 * 
 * This implementation ensures consistent JSON serialization by sorting object keys,
 * which is critical for generating stable hashes from fingerprint data.
 */

/**
 * Converts data to a stable JSON string with sorted keys
 * 
 * @param data - The data to stringify
 * @returns Stable JSON string representation
 * @throws TypeError if circular reference is detected
 * 
 * @example
 * ```typescript
 * const obj = { b: 2, a: 1 };
 * stableStringify(obj); // '{"a":1,"b":2}'
 * ```
 */
export function stableStringify(data: any): string {

    // Use a Set for O(1) cycle detection instead of O(N) array indexOf.
    // The serialisation logic (key sorting, value recursion, output format) is unchanged.
    const seen = new Set<any>();

    return (function stringify(node: any): string | undefined {
        if (node && node.toJSON && typeof node.toJSON === 'function') {
            node = node.toJSON();
        }

        if (node === undefined) return;
        if (typeof node === 'number') return isFinite(node) ? '' + node : 'null';
        if (typeof node !== 'object') return JSON.stringify(node);

        let i: number;
        let out: string;

        if (Array.isArray(node)) {
            out = '[';
            for (i = 0; i < node.length; i++) {
                if (i) out += ',';
                out += stringify(node[i]) || 'null';
            }
            return out + ']';
        }

        if (node === null) return 'null';

        if (seen.has(node)) {
            throw new TypeError('Converting circular structure to JSON');
        }

        seen.add(node);
        const keys = Object.keys(node).sort();
        out = '';

        for (i = 0; i < keys.length; i++) {
            const key = keys[i];
            const value = stringify(node[key]);

            if (!value) continue;
            if (out) out += ',';
            out += JSON.stringify(key) + ':' + value;
        }

        seen.delete(node);
        return '{' + out + '}';
    })(data) || '';
}
