/**
 * Runtime-detected global built-ins (cached for performance)
 */
let cachedGlobals = null;
export class Serializer {
    refCounter = 0;
    refMap = new WeakMap();
    customGlobalBuiltIns;
    keywordCache = new Map();
    globalCache = null;
    enableCaching;
    constructor(options = {}) {
        this.customGlobalBuiltIns = new Set(options.customGlobalBuiltIns || []);
        this.enableCaching = options.enableCaching !== false;
    }
    /**
     * Deep serialize a value with circular reference detection
     */
    serialize(value, scope = {}) {
        const visited = new WeakSet();
        return this.deepSerialize(value, scope, visited);
    }
    /**
     * Deserialize a value back to its original form
     */
    deserialize(serialized, refRegistry = new Map()) {
        return this.deepDeserialize(serialized, refRegistry);
    }
    deepSerialize(value, scope, visited) {
        if (value === null || value === undefined) {
            return { type: 'primitive', value };
        }
        const type = typeof value;
        if (type === 'string' ||
            type === 'number' ||
            type === 'boolean' ||
            type === 'bigint' ||
            type === 'symbol') {
            if (type === 'bigint') {
                return { type: 'primitive', value: value.toString() + 'n' };
            }
            if (type === 'symbol') {
                return { type: 'primitive', value: value.toString() };
            }
            return { type: 'primitive', value };
        }
        if (type === 'function') {
            return this.serializeFunction(value, scope, visited);
        }
        if (type === 'object' && value !== null) {
            if (visited.has(value)) {
                let refId = this.refMap.get(value);
                if (!refId) {
                    refId = `ref_${this.refCounter++}`;
                    this.refMap.set(value, refId);
                }
                return { type: 'circular', refId };
            }
            visited.add(value);
            if (value instanceof Date) {
                return {
                    type: 'date',
                    value: value.toISOString(),
                };
            }
            if (value instanceof RegExp) {
                return {
                    type: 'regexp',
                    pattern: value.source,
                    flags: value.flags,
                };
            }
            if (Array.isArray(value)) {
                return {
                    type: 'array',
                    value: value.map((item) => this.deepSerialize(item, scope, visited)),
                };
            }
            if (value instanceof Map) {
                const entries = [];
                for (const [k, v] of value.entries()) {
                    entries.push([
                        this.deepSerialize(k, scope, visited),
                        this.deepSerialize(v, scope, visited),
                    ]);
                }
                return { type: 'map', entries };
            }
            if (value instanceof Set) {
                const items = [];
                for (const item of value.values()) {
                    items.push(this.deepSerialize(item, scope, visited));
                }
                return { type: 'set', items };
            }
            try {
                const properties = {};
                for (const [k, v] of Object.entries(value)) {
                    properties[k] = this.deepSerialize(v, scope, visited);
                }
                return {
                    type: 'object',
                    className: value.constructor?.name,
                    properties,
                };
            }
            catch (e) {
                return { type: 'nonserializable' };
            }
        }
        return { type: 'nonserializable' };
    }
    serializeFunction(fn, scope, visited) {
        try {
            const source = fn.toString();
            if (source.includes('[native code]')) {
                return {
                    type: 'function',
                    source: 'native',
                    className: fn.name,
                };
            }
            const referencedVars = this.extractReferencedVars(source);
            const closure = {};
            for (const varName of referencedVars) {
                if (varName in scope) {
                    closure[varName] = this.deepSerialize(scope[varName], scope, visited);
                }
            }
            return {
                type: 'function',
                source,
                closure,
                isAsync: source.startsWith('async'),
                isGenerator: fn.constructor.name === 'GeneratorFunction',
                isArrow: source.includes('=>'),
            };
        }
        catch (e) {
            return { type: 'nonserializable' };
        }
    }
    extractReferencedVars(source) {
        const vars = new Set();
        const identifierRegex = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g;
        let match;
        while ((match = identifierRegex.exec(source)) !== null) {
            const name = match[1];
            if (!name)
                continue;
            if (this.isReservedKeyword(name)) {
                continue;
            }
            if (this.isGlobalBuiltIn(name)) {
                continue;
            }
            vars.add(name);
        }
        return vars;
    }
    /**
     * Check if a name is a reserved JavaScript keyword
     * Uses caching for performance when enabled
     */
    isReservedKeyword(name) {
        if (this.enableCaching && this.keywordCache.has(name)) {
            return this.keywordCache.get(name);
        }
        let isKeyword = false;
        try {
            new Function(`var ${name};`);
            isKeyword = false;
        }
        catch {
            isKeyword = true;
        }
        if (this.enableCaching) {
            this.keywordCache.set(name, isKeyword);
        }
        return isKeyword;
    }
    /**
     * Check if a name is a global built-in
     * Dynamically detects built-ins at runtime with caching for performance
     */
    isGlobalBuiltIn(name) {
        if (this.customGlobalBuiltIns.has(name)) {
            return true;
        }
        if (this.enableCaching && !this.globalCache) {
            this.globalCache = this.detectGlobalBuiltIns();
        }
        if (this.enableCaching && this.globalCache) {
            return this.globalCache.has(name);
        }
        return this.isGlobalProperty(name);
    }
    /**
     * Detect all global built-ins at runtime
     * This is called once and cached for performance
     */
    detectGlobalBuiltIns() {
        const globals = new Set();
        const globalObj = typeof globalThis !== 'undefined' ? globalThis : global;
        try {
            const ownProps = Object.getOwnPropertyNames(globalObj);
            for (const prop of ownProps) {
                globals.add(prop);
            }
            let proto = Object.getPrototypeOf(globalObj);
            while (proto) {
                const protoProps = Object.getOwnPropertyNames(proto);
                for (const prop of protoProps) {
                    globals.add(prop);
                }
                proto = Object.getPrototypeOf(proto);
            }
        }
        catch (e) {
            const fallbackGlobals = [
                'undefined',
                'null',
                'true',
                'false',
                'Math',
                'Date',
                'Array',
                'Object',
                'String',
                'Number',
                'Boolean',
                'Promise',
                'Set',
                'Map',
                'WeakMap',
                'WeakSet',
                'JSON',
                'Error',
                'TypeError',
                'RegExp',
                'parseInt',
                'parseFloat',
                'isNaN',
                'isFinite',
                'console',
                'process',
                'Buffer',
                'global',
                'globalThis',
            ];
            for (const g of fallbackGlobals) {
                globals.add(g);
            }
        }
        return globals;
    }
    /**
     * Check if a name is a property of the global object
     * Used when caching is disabled
     */
    isGlobalProperty(name) {
        const globalObj = typeof globalThis !== 'undefined' ? globalThis : global;
        try {
            return name in globalObj;
        }
        catch {
            return false;
        }
    }
    deepDeserialize(serialized, refRegistry) {
        switch (serialized.type) {
            case 'primitive':
                return serialized.value;
            case 'date':
                return new Date(serialized.value);
            case 'regexp':
                return new RegExp(serialized.pattern || '', serialized.flags || '');
            case 'array':
                return serialized.value.map((item) => this.deepDeserialize(item, refRegistry));
            case 'map': {
                const map = new Map();
                if (serialized.entries) {
                    for (const [k, v] of serialized.entries) {
                        map.set(this.deepDeserialize(k, refRegistry), this.deepDeserialize(v, refRegistry));
                    }
                }
                return map;
            }
            case 'set': {
                const set = new Set();
                if (serialized.items) {
                    for (const item of serialized.items) {
                        set.add(this.deepDeserialize(item, refRegistry));
                    }
                }
                return set;
            }
            case 'object': {
                const obj = {};
                if (serialized.properties) {
                    for (const [k, v] of Object.entries(serialized.properties)) {
                        obj[k] = this.deepDeserialize(v, refRegistry);
                    }
                }
                return obj;
            }
            case 'function':
                return this.deserializeFunction(serialized, refRegistry);
            case 'circular':
                return refRegistry.get(serialized.refId) || null;
            case 'nonserializable':
            default:
                return undefined;
        }
    }
    deserializeFunction(serialized, refRegistry) {
        try {
            if (serialized.source === 'native' || !serialized.source) {
                return undefined;
            }
            const closureNames = Object.keys(serialized.closure || {});
            const closureValues = closureNames.map((name) => {
                const closureValue = serialized.closure?.[name];
                return closureValue ? this.deepDeserialize(closureValue, refRegistry) : undefined;
            });
            const fnFactory = new Function(...closureNames, `return ${serialized.source}`);
            return fnFactory(...closureValues);
        }
        catch (e) {
            return undefined;
        }
    }
}
//# sourceMappingURL=serializer.js.map